注:原理大部分是从菜鸟教程搬运的
十种常见排序算法可以分为两大类
相邻位置比较大小,互换位置,一次遍历后能保证最后的值为最大或最小,简单直观,适用于小规模数据
def bubble_sort(arr): for i in range(len(arr)): for j in range(0, len(arr)-1-i): if arr[j] > arr [j+1]: # 第一个比第二个大 arr[j], arr[j+1] = arr[j+1], arr[j] # 交换 return arr
利用二次遍历选择最小值放在当前位置,简单直观,但是算法复杂度最好都是O(n²),效率很低,只适用于小规模数据
def select_sort(arr): for i in range(len(arr)): min_index = i # 最小值的下标 for j in range(i + 1, len(arr)): if arr[j] < arr[min_index]: min_index = j if (min_index != i): arr[i], arr[min_index] = arr[min_index], arr[i] # 将最小值放在当前位置 return arr
工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
def insert_sort(arr): for i in range(1, len(arr)): for j in range(i, 0, -1): # 从当前位置向前遍历 if arr[j] < arr[j - 1]: # 遇到比自己大的,交换;遇到比自己小的,循环结束 arr[j], arr[j - 1] = arr[j - 1], arr[j] else: break return arr
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
def shell_sort(arr): gap = len(arr) // 2 while gap > 0: for g in range(gap): for i in range(g + gap, len(arr), gap): for j in range(i, g, -gap): if arr[j] < arr[j - gap]: arr[j], arr[j - gap] = arr[j - gap], arr[j] else: break gap = gap // 2 return arr
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
把长度为n的输入序列分成两个长度为n/2的子序列;
对这两个子序列分别采用归并排序;
将两个排序好的子序列合并成一个最终的排序序列。
def merge_sort(arr): if len(arr) < 2: return arr middle = len(arr) // 2 left = arr[0:middle] right = arr[middle:] return merge(merge_sort(left), merge_sort(right)) def merge(left, right): result = [] while left and right: if left[0] <= right[0]: result.append(left.pop(0)) else: result.append(right.pop(0)) while left: result.append(left.pop(0)) while right: result.append(right.pop(0)) return result
是冒泡算法的分治版
def quick_sort(arr, left=None, right=None): left = 0 if left is None else left right = len(arr) - 1 if right is None else right if left < right: partition_index = partition(arr, left, right) quick_sort(arr, left, partition_index - 1) quick_sort(arr, partition_index + 1, right) return arr def partition(arr, left, right): pivot = left index = pivot + 1 i = index while i <= right: if arr[i] < arr[pivot]: swap(arr, i, index) index += 1 i += 1 swap(arr, pivot, index - 1) return index - 1
def build_max_heap(arr): import math for i in range(math.floor(len(arr)/2),-1,-1): heapify(arr,i) def heapify(arr, i): left = 2*i+1 right = 2*i+2 largest = i if left < arrLen and arr[left] > arr[largest]: largest = left if right < arrLen and arr[right] > arr[largest]: largest = right if largest != i: swap(arr, i, largest) heapify(arr, largest) def swap(arr, i, j): arr[i], arr[j] = arr[j], arr[i] def heap_sort(arr): global arrLen arrLen = len(arr) buildMaxHeap(arr) for i in range(len(arr)-1,0,-1): swap(arr,0,i) arrLen -=1 heapify(arr, 0) return arr
适用于数组元素在一个较小的区间,并且值不为负数
def count_sort(arr, max_value): bucket = [0] * (max_value + 1) for num in arr: bucket[num]+=1 index = 0 for i in range(len(bucket)): if bucket[i]: for j in range(bucket[i]): arr[index] = i index += 1 return arr
桶排序是计数排序的升级版,高效与否取决于桶的分布函数和桶内的排序方法
设置一个定量的数组当作空桶;
遍历输入数据,并且把数据一个一个放到对应的桶里去;
对每个不是空的桶进行排序;
从不是空的桶里把排好序的数据拼接起来。
将元素分布到桶中
元素在桶中排序
def bucket_sort(arr, min_value, max_value): BUCKET_SIZE = 3 bucket_size = (max_value-min_value) // BUCKET_SIZE + 1 buckets = [[] for i in range(bucket_size)] for i in range(len(arr)): buckets[(arr[i]-min_value)//BUCKET_SIZE].append(arr[i]) # 利用映射函数分配 index = 0 for bucket in buckets: # 对每个桶进行排序 quick_sort(bucket) # 这里使用快速排序 for num in bucket: arr[index] = num index += 1 return arr
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。
取得数组中的最大数,并取得位数;
arr为原始数组,从最低位开始取每个位组成radix数组;
对radix进行计数排序(利用计数排序适用于小范围数的特点);
def radix_sort(arr, max_digit): for digit in range(max_digit): buckets = [[] for i in range(10)] for i in range(len(arr)): buckets[int(arr[i] / (10 ** digit)) % 10].append(arr[i]) index = 0 for bucket in buckets: for num in bucket: arr[index] = num index += 1 return arr
import copy import time import sys import random from sort.bubble_sort import bubble_sort from sort.bucket_sort import bucket_sort from sort.count_sort import count_sort from sort.heap_sort import heap_sort from sort.insert_sort import insert_sort from sort.merge_sort import merge_sort from sort.quick_sort import quick_sort from sort.radix_sort import radix_sort from sort.select_sort import select_sort from sort.shell_sort import shell_sort sys.setrecursionlimit(100000000) def time_count(func): def wrapper(*args, **kwargs): start = time.clock() func(*args, **kwargs) end = time.clock() print(f'耗时:{end - start}秒') return wrapper class Executor(object): def __init__(self, func, func_name, *args, **kwargs): self.func = func self.func_name = func_name self.args = args self.kwargs = kwargs self.start() @time_count def start(self): print(self.func_name + '开始执行') self.func(*self.args, **self.kwargs) class TestCase: digit = 6 def __init__(self): self.list = [random.randint(0, 10**self.digit-1) for i in range(10**self.digit)] print(f'测试{10 ** self.digit}条数据排序') def test_bubble_sort(self): Executor(bubble_sort, '冒泡排序', copy.deepcopy(self.list)) def test_select_sort(self): Executor(select_sort, '选择排序', copy.deepcopy(self.list)) def test_insert_sort(self): Executor(insert_sort, '插入排序', copy.deepcopy(self.list)) def test_shell_sort(self): Executor(shell_sort, '希尔排序', copy.deepcopy(self.list)) def test_merge_sort(self): Executor(merge_sort, '归并排序', copy.deepcopy(self.list)) def test_quick_sort(self): Executor(quick_sort, '快速排序', copy.deepcopy(self.list)) def test_heap_sort(self): Executor(heap_sort, '堆排序', copy.deepcopy(self.list)) def test_count_sort(self): Executor(count_sort, '计数排序', copy.deepcopy(self.list), 10**self.digit) def test_bucket_sort(self): Executor(bucket_sort, '桶排序', copy.deepcopy(self.list), 0, 10**self.digit) def test_radix_sort(self): Executor(radix_sort, '基数排序', copy.deepcopy(self.list), self.digit) def main(self): # self.test_bubble_sort() # self.test_select_sort() # self.test_insert_sort() self.test_shell_sort() self.test_merge_sort() self.test_quick_sort() self.test_heap_sort() self.test_count_sort() self.test_bucket_sort() self.test_radix_sort() if __name__ == '__main__': TestCase().main()
自己电脑测的,结果可能有偏差,以下结果均为测试5次取平均
算法 | 1,000条(s) | 10,000条(s) | 100,000条(s) | 1,000,000条(s) |
---|---|---|---|---|
冒泡排序 | 0.07 | 8.18 | 略 | 略 |
选择排序 | 0.06 | 4.14 | 略 | 略 |
插入排序 | 0.03 | 1.62 | 略 | 略 |
希尔排序 | 0.01 | 0.03 | 0.53 | 8.30 |
归并排序 | 0.01 | 0.06 | 1.68 | 15.12 |
快速排序 | 0.05 | 0.07 | 0.85 | 9.61 |
堆排序 | 0.01 | 0.11 | 1.39 | 17.18 |
计数排序 | 0.01 | 0.01 | 0.05 | 0.46 |
桶排序 | 0.01 | 0.02 | 0.15 | 1.79 |
基数排序 | 0.01 | 0.05 | 0.55 | 6.39 |
数据量较大时,如果不考虑空间复杂度,个人推荐基数排序,空间占用相比计数排序小很多,速度也快;如果考虑空间复杂度,个人推荐希尔排序和快速排序。