排序算法总结(python实现)
目录
排序:将集合中的元素按照一定的顺序进行排列(本文所举例均按从小到大顺序排列)。
稳定性:如果集合中有相同值的元素,在排序后两者的相对顺序不变,则该排序算法是稳定的。
排序算法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
简单插入排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 稳定 |
简单选择排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 不稳定 |
堆排序 | O ( n log 2 n ) O(n\log_2n) O(nlog2n) | O ( n log 2 n ) O(n\log_2n) O(nlog2n) | O ( n log 2 n ) O(n\log_2n) O(nlog2n) | O ( 1 ) O(1) O(1) | 不稳定 |
冒泡排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 稳定 |
归并排序 | O ( n log 2 n ) O(n\log_2n) O(nlog2n) | O ( n log 2 n ) O(n\log_2n) O(nlog2n) | O ( n log 2 n ) O(n\log_2n) O(nlog2n) | O ( n ) O(n) O(n) | 稳定 |
快速排序 | O ( n log 2 n ) O(n\log_2n) O(nlog2n) | O ( n 2 ) O(n^2) O(n2) | O ( n log 2 n ) O(n\log_2n) O(nlog2n) | O ( n log 2 n ) O(n\log_2n) O(nlog2n) | 不稳定 |
待定 | $$ | $$ | $$ | $$ | |
$$ | $$ | $$ | $$ | ||
$$ | $$ | $$ | $$ |
插入排序
直接插入排序(逐个插入到前面的有序子序中)
在前面元素已经排好序的情况下,依次将后边的未排序元素 v v v插入到有序子序中,从有序子序的最末端开始比较,找到第一个值小于等于 v v v的元素 a [ i ] a[i] a[i],将 v v v插入到第 i + 1 i+1 i+1的位置。如果比较完都没有这样的元素,则 v v v是最小元,插入到第 0 0 0个位置。从第二个元素拆入开始,直到最后一个元素插入,总共进行 n − 1 n-1 n−1次插入,每次插入比较 i i i次。
- 最佳情况:序列已经排序好,每次插入只比较一次, O ( n ) O(n) O(n),例如: [ 1 , 2 , 3 , 4 , 5 ] [1,2,3,4,5] [1,2,3,4,5]
- 最坏情况:序列按倒序排好,每次都要和有序子序的所有元素比较一次, O ( n 2 ) O(n^2) O(n2),例如: [ 5 , 4 , 3 , 2 , 1 ] [5,4,3,2,1] [5,4,3,2,1]
- 平均: O ( n 2 ) O(n^2) O(n2)
- 稳定性:插入排序是有序插入,是稳定的。
# 直接插入排序
#法一:create a new list: res
def direct_insertion_sort(list_):
if len(list_)==1:
return list_
res = [list_[0]] #return list
for v in list_[1:]:
Ins = False
for i in range(len(res)-1,-1,-1):
if res[i]<=v:
res.insert(i+1,v)
Ins = True
break
if not Ins:
res.insert(0,v) # v是最小元
return res
a = [2, 15, 5, 9, 7, 6, 4, 12, 5, 4, 2, 64, 5, 6, 4, 2, 3, 54, 45, 4, 44] # 原始乱序
a_sort = [2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64] # 正确排序
res = direct_insertion_sort(a)
print(res==a_sort)
或
# 直接插入排序
# without new list
def Insertion(list_,i,j):
if i==j: return
tmp = list_[j]
for q in range(j,i,-1):
list_[q]=list_[q-1]
list_[i]=tmp
def direct_insertion_sort(list_):
if len(list_)==1:
return list_
for j in range(1,len(list_)):
v = list_[j]
Ins = False
for i in range(j-1,-1,-1):
if list_[i]<=v:
Insertion(list_,i+1,j)
#将第j个元素插入到i+1的位置,i+1到j中间的元素依次向右移动
Ins = True
break
if not Ins:
Insertion(list_,0,v)
return res
a = [2, 15, 5, 9, 7, 6, 4, 12, 5, 4, 2, 64, 5, 6, 4, 2, 3, 54, 45, 4, 44] # 原始乱序
a_sort = [2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64] # 正确排序
res = direct_insertion_sort(a)
print(res==a_sort)
希尔排序
选择排序
直接选择排序(选择最小元放在前面)
直接选择排序每次选择未排序中的最小元,放在未排序元素的第一个位置,共进行 n − 1 n-1 n−1次,第 i i i次排序可以得到第 i i i小/大元素,每次都要比较所有元素,因此时间复杂度均为 O ( n 2 ) O(n^2) O(n2)。
- 稳定性:直接选择排序是不稳定的,在排序的过程中,发生了元素位置的调换,会破坏相同值元素的相对位置,例如,
[
5
,
8
,
5
,
3
,
9
]
[5,8,5,3,9]
[5,8,5,3,9]。
# 直接选择排序
def select_sort(list_):
for i in range(len(list_)-1):
min_v = a[i]
min_id = i
for j in range(i+1,len(list_)):
if list_[j]<min_v:
min_v = list_[j]
min_id = j
list_[i], list_[min_id] = list_[min_id],list_[i]
return list_
a = [2, 15, 5, 9, 7, 6, 4, 12, 5, 4, 2, 64, 5, 6, 4, 2, 3, 54, 45, 4, 44] # 原始乱序
a_sort = [2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64] # 正确排序
res = select_sort(a)
print(res==a_sort)
堆排序(建堆不断取根节点调整堆)
将初始待排序列 R = ( r 1 , r 2 , … , r n ) R=(r_1,r_2,\ldots,r_n) R=(r1,r2,…,rn)构建成大根堆,此堆为初始的无序区,堆顶元素 R [ 1 ] R[1] R[1]是最大元,将 R [ 1 ] R[1] R[1]与 R [ n ] R[n] R[n]调换位置,得到无序区 ( r 1 , r 2 , … , r n − 1 ) (r_1,r_2,\ldots,r_{n-1}) (r1,r2,…,rn−1)和新的有序区 ( r n ) (r_n) (rn),且满足 R [ 1 , 2 , … , n − 1 ] ≤ R [ n ] R[1,2,\ldots,n-1] \leq R[n] R[1,2,…,n−1]≤R[n]。交换后的新堆顶可能不满足大根堆的条件,因此需要对新堆进行调整,再次将 R [ 1 ] R[1] R[1]与无序区最后一个元素交换,得到新的无序区 ( r 1 , r 2 , … , r n − 2 ) (r_1,r_2,\ldots,r_{n-2}) (r1,r2,…,rn−2)和新的有序区 ( r n − 1 , r n ) (r_{n-1},r_n) (rn−1,rn),不断重复该过程,直到有序区的元素个数为 n − 1 n-1 n−1,则整个排序过程结束。
- 建堆的复杂度:
O
(
n
)
O(n)
O(n)
设共 k k k层,每层 2 k − 1 2^{k-1} 2k−1个节点,第 1 1 1到 k − 1 k-1 k−1层,均需要调整,第 i i i层的节点最多需要调整 k − i k-i k−i次,则 s = 2 0 ⋅ ( k − 1 ) + 2 1 ⋅ ( k − 2 ) + 2 2 ⋅ ( k − 3 ) + … + 2 k − 2 ⋅ 1 = 2 k − k − 1 s=2^0\cdot(k-1)+2^1\cdot(k-2)+2^2\cdot(k-3)+\ldots+ 2^{k-2}\cdot1 =2^k-k-1 s=20⋅(k−1)+21⋅(k−2)+22⋅(k−3)+…+2k−2⋅1=2k−k−1,而 n = 2 k − 1 , k = log 2 n n=2^k-1, k=\log_2n n=2k−1,k=log2n,也即 s = O ( n ) s=O(n) s=O(n) - 单次调整堆的复杂度: O ( log 2 n ) O(\log_2n) O(log2n),也即堆的层数
- 总调整堆的复杂度: O ( n log 2 n ) O(n\log_2n) O(nlog2n),总共需要调整 n − 1 n-1 n−1次,堆的节点个数依次减1,总的复杂度为 log 2 n + log 2 ( n − 1 ) + … + log 2 3 + log 2 2 = log 2 ( n ! ) ≈ n log 2 n \log_2n+\log_2(n-1)+\ldots+\log_23+\log_22=\log_2(n!)\approx n\log_2n log2n+log2(n−1)+…+log23+log22=log2(n!)≈nlog2n。
- 稳定性:堆排序是不稳定的,例如:
[
98
,
86
,
48
,
54
,
42
,
42
‾
]
[98,86,48,54,42,\underline{42}]
[98,86,48,54,42,42],排序后为
[
98
,
86
,
54
,
48
,
42
‾
,
42
]
[98,86,54,48,\underline{42},42]
[98,86,54,48,42,42]。
# 堆排序
def heap_sort(list_):
build_heap(list_)
for i in range(len(list_)-1,0,-1):
list_[0],list_[i] = list_[i],list_[0]
adjust_heap(list_,i,0) # length of heap is i, adjust node is list_[0], and children of list_[0]
return list_
def build_heap(list_):
last_node_id = (len(list_)-1)//2 # chidren:2i+1,2i+2, parent: (child-1)//2
for i in range(last_node_id,-1,-1):
adjust_heap(list_,len(list_),i)
def adjust_heap(list_,length,adjust_id):
left_id = 2*adjust_id + 1
right_id = 2*adjust_id + 2
max_id = adjust_id
if left_id<length and list_[left_id]>list_[max_id]:
max_id = left_id
if right_id<length and list_[right_id]>list_[max_id]:
max_id = right_id
if max_id != adjust_id:
list_[max_id],list_[adjust_id] = list_[adjust_id],list_[max_id]
adjust_heap(list_,length,max_id)
a = [2, 15, 5, 9, 7, 6, 4, 12, 5, 4, 2, 64, 5, 6, 4, 2, 3, 54, 45, 4, 44] # 原始乱序
a_sort = [2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64] # 正确排序
res = heap_sort(a)
print(res==a_sort)
交换排序
交换排序:两两比较待排序的元素值,交换不满足顺序要求的偶对,直到全部满足为止。
冒泡排序(前后比较调换直到全部有序)
每次遍历,从第一个元素开始,依次比较相邻两元素的大小,如果 a [ i ] > a [ i + 1 ] a[i]>a[i+1] a[i]>a[i+1],则调整两元素的位置,一直比较到最后一个元素,遍历一次得到一个未排序序列的最大值,停止条件:总共遍历 n − 1 n-1 n−1次,或者是此次遍历没有发生元素位置的调整,说明已经排序成功,返回。
- 最好情况:元素已经排好序,不需调整, O ( n ) O(n) O(n)
- 最坏情况: O ( n 2 ) O(n^2) O(n2)
- 稳定性:冒泡排序是在两相邻元素之间发生,当两元素值相同时,不会调换位置,因此是稳定的
# 冒泡排序
def bubble_sort(list_):
for length in range(len(list_),1,-1):
Change = False
for i in range(length-1):
if list_[i]>list_[i+1]:
list_[i],list_[i+1] = list_[i+1],list_[i]
Change = True
if not Change: break
return list_
a = [2, 15, 5, 9, 7, 6, 4, 12, 5, 4, 2, 64, 5, 6, 4, 2, 3, 54, 45, 4, 44] # 原始乱序
a_sort = [2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64] # 正确排序
res = bubble_sort(a)
print(res==a_sort)
快速排序(选取一个基准值,小数在左大数在右)
最经常考的排序算法,其次是堆排序。快速排序每次取一个基准数(一般取 a [ 0 ] a[0] a[0]),将比它小的元素放在左区间,比它大的元素放右区间,此为一趟排序。再分别对这两个区间做二次快排,如此这般递归下去,直到区间内只有一个元素,此时排序完成。快速排序是对冒泡排序的一种改进,每次的交换都是跳跃的。
- 时间复杂度:在每次分区以后,所有的元素都会遍历一遍,比较其与基准数的大小,是 O ( n ) O(n) O(n)的,关键在于分区的次数(也即排序趟数),平均复杂度为 O ( n log 2 n ) O(n\log_2n) O(nlog2n)
- 最佳情况:每次分区都可以将序列分为长度均匀的两个子序(基准数在序列中间),如此这般只需 log 2 n \log_2n log2n次即可分区结束,复杂度为 O ( n log 2 n ) O(n\log_2n) O(nlog2n)
- 最坏情况:当序列本身已经有序时(每次选择的基准数刚好是最大/小元素),每次分区子序的长度减一,则需要 n n n趟划分,复杂度为 O ( n 2 ) O(n^2) O(n2)
- 稳定性:在左右交换时,不能保证相同元素值的相对位置不变,不稳定。
# 快速排序
#法一:挖坑法
def quick_sort(list_,left,right):
if left>=right: return
i,j = left,right
key = list_[left]
while i<j:
while i<j and list_[j]>=key:
j -= 1
if i<j:
list_[i] = list_[j]
i += 1
while i<j and list_[i]<=key:
i += 1
if i<j:
list_[j] = list_[i]
j -= 1
list_[i]=key
quick_sort(list_,left,i-1)
quick_sort(list_,i+1,right)
# 简化版本
def quick_sort(list_,left,right):
if left>=right: return
i,j = left,right
key = list_[left]
while i<j:
while i<j and list_[j]>=key:
j -= 1
list_[i] = list_[j]
while i<j and list_[i]<=key:
i += 1
list_[j] = list_[i]
list_[i]=key
quick_sort(list_,left,i-1)
quick_sort(list_,i+1,right)
a = [2, 15, 5, 9, 7, 6, 4, 12, 5, 4, 2, 64, 5, 6, 4, 2, 3, 54, 45, 4, 44] # 原始乱序
a_sort = [2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64] # 正确排序
quick_sort(a,0,len(a)-1)
print(a==a_sort)
# 快速排序
#法二:交换法
def quick_sort(list_,left,right):
if left>=right: return
i,j = left,right
key = list_[left]
while i<j:
while i<j and list_[j]>=key:
j -= 1
while i<j and list_[i]<=key:
i += 1
list_[j],list_[i] = list_[i],list_[j]
list_[i],list_[left] = key,list_[i]
quick_sort(list_,left,i-1)
quick_sort(list_,i+1,right)
a = [2, 15, 5, 9, 7, 6, 4, 12, 5, 4, 2, 64, 5, 6, 4, 2, 3, 54, 45, 4, 44] # 原始乱序
a_sort = [2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64] # 正确排序
quick_sort(a,0,len(a)-1)
print(a==a_sort)
归并排序(分治比较合并)
首先对序列做若干次二分,直到所有子序列均为长度为1或2的小序列,再将小序列两两合并为有序序列,如此这般,直到只剩下两个有序子序列,将其合并为最后的有序序列。
- 时间复杂度:总共做 log 2 n \log_2n log2n次二分,在合并时,针对每次二分,都进行 n / / 2 n//2 n//2次比较,总的时间复杂度为 O ( n log 2 n ) O(n\log_2n) O(nlog2n)
- 空间复杂度:在每次合并的时候都需要新的位置存储合并后的序列,是 O ( n ) O(n) O(n)的
- 稳定性:在合并时,若两个元素值相等,不会交换其位置,相对位置不变,是稳定的
# 归并排序
def merge_sort(list_):
if len(list_)<=1:
return list_
mid_id = len(list_)//2
left = merge_sort(list_[:mid_id])
right = merge_sort(list_[mid_id:])
list_ = merge(left,right)
return list_
def merge(left,right):
res = []
i,j = 0,0
while i<len(left) and j<len(right):
if left[i]<=right[j]: # must be <=, which is stable
res.append(left[i])
i += 1
else:
res.append(right[j])
j += 1
if j<len(right):
res += right[j:]
elif i<len(left):
res += left[i:]
return res
a = [2, 15, 5, 9, 7, 6, 4, 12, 5, 4, 2, 64, 5, 6, 4, 2, 3, 54, 45, 4, 44] # 原始乱序
a_sort = [2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 9, 12, 15, 44, 45, 54, 64] # 正确排序
res = merge_sort(a)
print(res==a_sort)
非比较排序
计数排序
桶排序
基数排序
参考文献
十大经典排序算法(python实现)
排序算法百度百科
《算法与数据结构》——C语言描述(第三版)
Python实现合并排序(归并排序)(一文看懂)
快速排序 - python版超详细讲解
快速排序法(详解)