[toc]
排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序
等。用一张图概括:
关于时间复杂度:
平方阶 ($ O(n^2) $) 排序 各类简单排序:直接插入、直接选择和冒泡排序。
线性对数阶 ($O(nlog_2(n))$) 排序 快速排序、堆排序和归并排序。
($O(n+§)$) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序。
线性阶 ($O(n)$) 排序 基数排序,此外还有桶、箱排序。
关于稳定性:
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序
。
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序
。
名词解释:
1 2 import randomimport time
1 2 3 4 5 6 7 8 9 10 11 12 def counter (sort ): nums = [] time_sum = 0 for _ in range (1000 ): nums = [] for i in range (100 ): nums.append(i) time_start = time.time() sort(nums) time_end = time.time() time_sum += (time_end - time_start) return time_sum
冒泡排序
冒泡排序算法的运作如下:
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
以上节选自维基百科
1 2 3 4 5 6 7 8 9 10 11 def bubble_sort (nums ): length = len (nums) for i in range (length): change_flag = False for j in range (1 , length - i): if nums[j - 1 ] > nums[j]: change_flag = True nums[j], nums[j - 1 ] = nums[j - 1 ], nums[j] if change_flag == False : return nums return nums
1 2 3 4 5 6 7 8 9 10 nums = [] for i in range (20 ): nums.append(i) random.shuffle(nums) print ("未排序数组:" )print (nums)print ("排序后数组:" )print (bubble_sort(nums))print (f"运行时间测试: {counter(bubble_sort)} (s)" )
1 2 3 4 5 未排序数组: [11, 5, 8, 12, 9, 6, 1, 19, 17, 3, 18, 4, 13, 10, 14, 2, 7, 0, 15, 16] 排序后数组: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 运行时间测试: 0.00996851921081543(s)
选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
以上节选自维基百科
1 2 3 4 5 6 7 8 9 10 11 def selectionSort (arr ): for i in range (len (arr) - 1 ): minIndex = i for j in range (i + 1 , len (arr)): if arr[j] < arr[minIndex]: minIndex = j if i != minIndex: arr[i], arr[minIndex] = arr[minIndex], arr[i] return arr
1 2 3 4 5 6 7 8 9 10 nums = [] for i in range (20 ): nums.append(i) random.shuffle(nums) print ("未排序数组:" )print (nums)print ("排序后数组:" )print (selectionSort(nums))print (f"运行时间测试: {counter(selectionSort)} (s)" )
1 2 3 4 5 未排序数组: [10, 13, 0, 18, 12, 2, 8, 17, 7, 16, 15, 11, 6, 3, 9, 5, 1, 19, 4, 14] 排序后数组: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 运行时间测试: 0.4408740997314453(s)
插入排序
步骤如下
从第一个元素开始,该元素可以认为已经被排序
取出下一个元素,在已经排序的元素序列中从后向前扫描
如果该元素(已排序)大于新元素,将该元素移到下一位置
重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置
将新元素插入到该位置后
重复步骤 2~5
以上节选自维基百科
1 2 3 4 5 6 7 8 9 def insertionSort (arr ): for i in range (len (arr)): preIndex = i - 1 current = arr[i] while preIndex >= 0 and arr[preIndex] > current: arr[preIndex + 1 ] = arr[preIndex] preIndex -= 1 arr[preIndex + 1 ] = current return arr
1 2 3 4 5 6 7 8 9 10 nums = [] for i in range (20 ): nums.append(i) random.shuffle(nums) print ("未排序数组:" )print (nums)print ("排序后数组:" )print (insertionSort(nums))print (f"运行时间测试: {counter(insertionSort)} (s)" )
1 2 3 4 5 未排序数组: [10, 15, 14, 7, 9, 8, 6, 3, 11, 16, 19, 18, 0, 4, 2, 17, 1, 12, 5, 13] 排序后数组: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 运行时间测试: 0.024000167846679688(s)
希尔排序
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
以上节选自维基百科
1 2 3 4 5 6 7 8 9 10 11 12 13 def shell_sort (numberlist ): length = len (numberlist) gap = length // 2 while gap > 0 : for i in range (gap, length): temp = numberlist[i] j = i while j >= gap and numberlist[j - gap] > temp: numberlist[j] = numberlist[j - gap] j -= gap numberlist[j] = temp gap = gap // 2 return numberlist
1 2 3 4 5 6 7 8 9 10 nums = [] for i in range (20 ): nums.append(i) random.shuffle(nums) print ("未排序数组:" )print (nums)print ("排序后数组:" )print (shell_sort(nums))print (f"运行时间测试: {counter(shell_sort)} (s)" )
1 2 3 4 5 未排序数组: [17, 9, 12, 18, 5, 14, 15, 2, 19, 13, 0, 16, 11, 6, 4, 7, 1, 8, 10, 3] 排序后数组: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 运行时间测试: 0.11478352546691895(s)
归并排序
原理如下(假设序列共有{displaystyle n}个元素):
将序列每相邻两个数字进行归并操作,形成{displaystyle ceil(n/2)}个序列,排序后每个序列包含两/一个元素
若此时序列数不是 1 个则将上述序列再次归并,形成{displaystyle ceil(n/4)}个序列,每个序列包含四/三个元素
重复步骤 2,直到所有元素排序完毕,即序列数为 1
以上节选自维基百科
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def merge_sort (arr ): if (len (arr) < 2 ): return arr middle = len (arr) // 2 left, right = arr[0 :middle], arr[middle:] return merge(mergeSort(left), mergeSort(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
1 2 3 4 5 6 7 8 9 10 nums = [] for i in range (20 ): nums.append(i) random.shuffle(nums) print ("未排序数组:" )print (nums)print ("排序后数组:" )print (merge_sort(nums))print (f"运行时间测试: {counter(merge_sort)} (s)" )
1 2 3 4 5 未排序数组: [17, 0, 15, 18, 11, 5, 2, 1, 10, 9, 7, 19, 12, 6, 4, 3, 14, 13, 16, 8] 排序后数组: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 运行时间测试: 0.2873060703277588(s)
快速排序
从数列中挑出一个元素,称为“基准”(pivot),
重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分割结束之后,该基准就处于数列的中间位置。这个称为分割(partition)操作。
递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
以上节选自维基百科
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def quick_sort (nums ): def randomized_partition (nums, l, r ): pivot = random.randint(l, r) nums[pivot], nums[r] = nums[r], nums[pivot] i = l - 1 for j in range (l, r): if nums[j] < nums[r]: i += 1 nums[j], nums[i] = nums[i], nums[j] i += 1 nums[i], nums[r] = nums[r], nums[i] return i def randomized_quicksort (nums, l, r ): if r - l <= 0 : return mid = randomized_partition(nums, l, r) randomized_quicksort(nums, l, mid - 1 ) randomized_quicksort(nums, mid + 1 , r) randomized_quicksort(nums, 0 , len (nums) - 1 ) return nums
1 2 3 4 5 6 7 8 9 10 nums = [] for i in range (20 ): nums.append(i) random.shuffle(nums) print ("未排序数组:" )print (nums)print ("排序后数组:" )print (quick_sort(nums))print (f"运行时间测试: {counter(quick_sort)} (s)" )
1 2 3 4 5 未排序数组: [13, 5, 14, 2, 19, 7, 11, 4, 3, 0, 12, 16, 8, 15, 1, 6, 10, 18, 9, 17] 排序后数组: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 运行时间测试: 0.31716418266296387(s)
堆排序
若以升序排序说明,把数组转换成最大堆积(Max-Heap Heap),这是一种满足最大堆积性质(Max-Heap Property)的二叉树:对于除了根之外的每个节点 i, A[parent(i)] ≥ A[i]。
重复从最大堆积取出数值最大的结点(把根结点和最后一个结点交换,把交换后的最后一个结点移出堆),并让残余的堆积维持最大堆积性质。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 def heap_sort (numberlist ): def sift_down (start, end ): root = start while True : child = 2 * root + 1 if child > end: break if child + 1 <= end and numberlist[child] < numberlist[child + 1 ]: child += 1 if numberlist[root] < numberlist[child]: numberlist[root], numberlist[child] = numberlist[child], numberlist[root] root = child else : break length = len (numberlist) for start in range ((length - 2 ) // 2 , -1 , -1 ): sift_down(start, length - 1 ) for end in range (length - 1 , 0 , -1 ): numberlist[0 ], numberlist[end] = numberlist[end], numberlist[0 ] sift_down(0 , end - 1 ) return numberlist
1 2 3 4 5 6 7 8 9 10 nums = [] for i in range (20 ): nums.append(i) random.shuffle(nums) print ("未排序数组:" )print (nums)print ("排序后数组:" )print (heap_sort(nums))print (f"运行时间测试: {counter(heap_sort)} (s)" )
1 2 3 4 5 未排序数组: [18, 2, 11, 1, 9, 10, 13, 8, 14, 7, 17, 6, 16, 4, 3, 5, 15, 12, 0, 19] 排序后数组: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 运行时间测试: 0.4019196033477783(s)
python heapq
堆的定义:
堆是一种特殊的数据结构,它的通常的表示是它的根结点的值最大或者是最小。
python 中heapq
的使用
列出一些常见的用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 heap = [] heappush(heap,item) item = heappop(heap) item = heap[0 ] heapify(x) item = heapreplace(heap,item) heappoppush() merge() nlargest(n , iterbale, key=None )
1 2 3 4 5 6 7 8 from heapq import *def heapsort (iterable ): h = [] for value in iterable: heappush(h, value) return [heappop(h) for i in range (len (h))]
计数排序
以上节选自维基百科
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def counting_sort (input_list ): length = len (input_list) if length < 2 : return input_list max_num = max (input_list) count = [0 ] * (max_num + 1 ) for element in input_list: count[element] += 1 output_list = [] for i in range (max_num + 1 ): for j in range (count[i]): output_list.append(i) return output_list
1 2 3 4 5 6 7 8 9 10 11 12 nums = [] for i in range (20 ): nums.append(i) random.shuffle(nums) print ("未排序数组:" )print (nums)print ("排序后数组:" )print (counting_sort(nums))print (f"运行时间测试: {counter(counting_sort)} (s)" )
1 2 3 4 5 未排序数组: [6, 19, 5, 7, 17, 0, 12, 9, 3, 15, 18, 13, 14, 10, 11, 1, 2, 16, 8, 4] 排序后数组: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 运行时间测试: 0.039923667907714844(s)
桶排序
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
在额外空间充足的情况下,尽量增大桶的数量
使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
(1)什么时候最快
当输入的数据可以均匀的分配到每一个桶中。
(2)什么时候最慢
当输入的数据被分配到了同一个桶中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def bucket_sort (s ): """桶排序""" min_num = min (s) max_num = max (s) bucket_range = (max_num - min_num) / len (s) count_list = [ [] for i in range (len (s) + 1 )] for i in s: count_list[int ((i-min_num) // bucket_range)].append(i) s.clear() for i in count_list: for j in sorted (i): s.append(j)
1 2 3 4 5 6 7 8 9 10 11 12 nums = [] for i in range (20 ): nums.append(i) random.shuffle(nums) print ("未排序数组:" )print (nums)print ("排序后数组:" )print (bucket_sort(nums))print (f"运行时间测试: {counter(bucket_sort)} (s)" )
1 2 3 4 5 未排序数组: [17, 6, 3, 19, 11, 12, 15, 9, 4, 0, 16, 18, 14, 8, 13, 7, 1, 10, 5, 2] 排序后数组: None 运行时间测试: 0.05981326103210449(s)
基数排序
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
基数排序 vs 计数排序 vs 桶排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶;
计数排序:每个桶只存储单一键值;
桶排序:每个桶存储一定范围的数值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def radix_sort (list ): i = 0 n = 1 max_num = max (list ) while max_num > 10 **n: n += 1 while i < n: bucket = {} for x in range (10 ): bucket.setdefault(x, []) for x in list : radix =int ((x / (10 **i)) % 10 ) bucket[radix].append(x) j = 0 for k in range (10 ): if len (bucket[k]) != 0 : for y in bucket[k]: list [j] = y j += 1 i += 1 return list
1 2 3 4 5 6 7 8 9 10 11 12 nums = [] for i in range (20 ): nums.append(i) random.shuffle(nums) print ("未排序数组:" )print (nums)print ("排序后数组:" )print (radix_sort(nums))print (f"运行时间测试: {counter(radix_sort)} (s)" )
1 2 3 4 5 未排序数组: [11, 4, 9, 7, 2, 13, 15, 5, 19, 10, 1, 18, 16, 17, 6, 3, 0, 8, 14, 12] 排序后数组: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 运行时间测试: 0.09178805351257324(s)