排序算法总结(附动图解释和python实现)

排序算法总结(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 n1次插入,每次插入比较 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] [54321]
  • 平均: 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 n1次,第 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,,rn1)和新的有序区 ( r n ) (r_n) (rn),且满足 R [ 1 , 2 , … , n − 1 ] ≤ R [ n ] R[1,2,\ldots,n-1] \leq R[n] R[1,2,,n1]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,,rn2)和新的有序区 ( r n − 1 , r n ) (r_{n-1},r_n) (rn1,rn),不断重复该过程,直到有序区的元素个数为 n − 1 n-1 n1,则整个排序过程结束。

  • 建堆的复杂度: O ( n ) O(n) O(n)
    设共 k k k层,每层 2 k − 1 2^{k-1} 2k1个节点,第 1 1 1 k − 1 k-1 k1层,均需要调整,第 i i i层的节点最多需要调整 k − i k-i ki次,则 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(k1)+21(k2)+22(k3)++2k21=2kk1,而 n = 2 k − 1 , k = log ⁡ 2 n n=2^k-1, k=\log_2n n=2k1,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 n1次,堆的节点个数依次减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(n1)++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 n1次,或者是此次遍历没有发生元素位置的调整,说明已经排序成功,返回。

  • 最好情况:元素已经排好序,不需调整, 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版超详细讲解
快速排序法(详解)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值