python排序算法之十兄弟

注意:排序方式:从左至右按照由小到大的顺序,即升序。

十兄弟之lowB三人组:

平均时间复杂度为O\left ( n^{2} \right )

1.冒泡排序

#冒泡排序
def bubble_sort(li):
    #最多走n-1趟
    for i in range(len(li)-1):
        flag = False #标志位判断是否已经是有序序列
        #每趟从位置0走到位置n-i-1
        for j in range(len(li)-i-1):
            if li[j]>li[j+1]:
                li[j+1],li[j] = li[j],li[j+1]
                flag = True
        print("第{}趟排序为{}".format(i,li))
        if not flag:
            break

if __name__ == "__main__":
    li = [2,3,4,9,7,6,5,10,1]
    bubble_sort(li)

      

2.选择排序:

#选择排序
def select_sort(li):
    #排序一共走了n-1趟
    for i in range(len(li)-1):
        #最小值的位置为本轮的第一个值位置
        min_loc = i
        #每趟排序从未排序的第一个值到末尾
        for j in range(i+1,len(li)):
            #如果某值小于min_loc位置的值,就让min_loc指向该值
            if li[j] < li[min_loc]:
                min_loc = j
        #交换位置
        li[i],li[min_loc] = li[min_loc],li[i]
        print("第{}趟排序为{}".format(i,li))
        
if __name__ == "__main__":
    li = [2,3,4,9,7,6,5,10,1]
    select_sort(li)

输出结果:

3.插入排序:

#插入排序
def insert_sort(li):
    #从第二个数开始往左面插入,一共有n-1趟
    for i in range(1,len(li)):
        #j指向i-1位置
        j = i-1
        #temp保存li[i]位置的值,由于Python的列表中存储的是元素的地址,可以认为temp和li[i]都指向同一个值。
        temp = li[i]
        #当j往左移动时j<0会退出,temp指向的值比li[j]指向的值大时退出循环
        while j >=0 and temp < li[j]:
            #li[j]指向的那一个值传递给li[j+1]
            li[j+1] = li[j]
            #j往左边移动
            j = j-1
        #退出循环,li[j+1]指向temp指向的那个值,即需要插入的那个值
        li[j+1] = temp
        print("第{}趟排序为{}".format(i,li))
        
if __name__ == "__main__":
    li = [2,3,4,9,7,6,5,10,1]
    insert_sort(li)

输出结果:

 

十兄弟之NB三人组:

1.快速排序:平均时间复杂度为O\left ( nlogn \right ),当序列正好是逆排序时,对其进行正排序(由小到大),为最坏情况,时间复杂度为O\left ( n^{2} \right ),但这种情况出现次数非常少,一般通过先随机选择中间值降低其出现概率。

#快速排序
def partition(li,left,right):
    #temp存放初始中间值,默认指向列表第一个元素
    temp = li[left]
    while left < right:
        #当左指针小于右指针且li[right]指向的元素大于等于temp指向的值时,右指针向左移动
        while left<right and li[right]>=temp:
            right -= 1
        把li[right]指向的值传递给li[left]
        li[left] = li[right]
        #当左指针小于右指针且li[left]指向的元素小于等于temp指向的值时,左指针向右移动
        while left<right and li[left]<=temp:
            left += 1
        li[right] = li[left]
    #当左右指针指向同一个位置时,把temp指向的值传递给li[left]或者li[right]   
    li[left] = temp
    #返回left或者right指向的位置
    return left
#快排,用递归
def quick_sort(li,left,right):
    if left < right:
        mid = partition(li,left,right)
        quick_sort(li,left,mid-1)
        quick_sort(li,mid+1,right)
        
if __name__ == "__main__":
    li = [2,3,4,9,7,6,5,10,1]
    quick_sort(li,0,len(li)-1)
    print(li)

输出结果:

改进版:随机快速排序法

#随机快速排序
import random
def partition(li,left,right):
    #index表示在列表的下标范围内随机选择一个位置
    index = random.randint(left,right)
    #让随机选择的这个位置的值和li[left]指向的值做交换,实际上是两个指针的指向改变
    li[left],li[index] = li[index],li[left]
   #temp存放初始中间值,默认指向列表第一个元素
    temp = li[left]
    while left < right:
        #当左指针小于右指针且li[right]指向的元素大于等于temp指向的值时,右指针向左移动
        while left<right and li[right]>=temp:
            right -= 1
        把li[right]指向的值传递给li[left]
        li[left] = li[right]
        #当左指针小于右指针且li[left]指向的元素小于等于temp指向的值时,左指针向右移动
        while left<right and li[left]<=temp:
            left += 1
        li[right] = li[left]
    #当左右指针指向同一个位置时,把temp指向的值传递给li[left]或者li[right]   
    li[left] = temp
    #返回left或者right指向的位置
    return left
#快排,用递归
def quick_sort(li,left,right):
    if left < right:
        mid = partition(li,left,right)
        quick_sort(li,left,mid-1)
        quick_sort(li,mid+1,right)
        
if __name__ == "__main__":
    li = [2,3,4,9,7,6,5,10,1]
    quick_sort(li,0,len(li)-1)
    print(li)

非递归式:快排

#快速排序(非递归式)
import random
def partition(li,left,right):
    index = random.randint(left,right)
    li[left],li[index] = li[index],li[left]
    temp = li[left]
    while left < right:
        while left<right and li[right]>=temp:
            right -= 1
        li[left] = li[right]
        while left<right and li[left]<=temp:
            left += 1
        li[right] = li[left]
    li[left] = temp
    return left
#做一个栈,从栈中先取出right,后取出left,分块排序
def quick_sort(li,left,right):
    temp = [left,right]
    while temp:
        right = temp.pop()
        left = temp.pop()
        index = partition(li,left,right)
        if left < index-1:
            temp.append(left)
            temp.append(index-1)
        if right > index+1:
            temp.append(index+1)
            temp.append(right)
    return li
       
        
if __name__ == "__main__":
    li = [2,3,12,9,7,6,5,10,14]
    quick_sort(li,0,len(li)-1)
    print(li)

输出结果:

2.归并排序:时间复杂度为O\left ( nlogn \right ),空间复杂度为O\left ( n \right )

#归并排序
def merge(li,low,mid,high):
    i = low
    #由中点mid分为两块,一块为(low,mid),另一块为(mid+1,high)
    j = mid+1
    #新建一个列表用于存储排好序的值
    ltemp = []
    #要求i<=mid并且j<=high
    while i<=mid and j<=high:
        #如果li[i]指向的值小于等于li[j]指向的值,将li[i]指向的值添加进ltemp列表中,我认为这里应该加上=等于号,为了稳定性。
        if li[i] <= li[j]:
            ltemp.append(li[i])
            i += 1
        else:
            ltemp.append(li[j])
            j += 1
    #当i>mid或者j>high时,没遍历完的依次放入ltmp列表中
    while i <= mid:
        ltemp.append(li[i])
        i += 1
    while j <= high:
        ltemp.append(li[j])
        j += 1
    #这里是要把排好序的部分由ltemp传递给li中对应位置,切不可li=ltemp
    li[low:high+1] = ltemp

def merge_sort(li,low,high):
    #这部分递归,我不太理解,总感觉递归终止条件没有,即终止时没有返回值,我知道最后肯定是low==high,那会返回low和high共同指向的那个值么?感觉应该得返回这个值,但是程序中没给指明
    if low < high:
        #选择中间值
        mid = (low+high)//2
        #分成左右两部分,再对这两部分比较排序
        merge_sort(li,low,mid)
        merge_sort(li,mid+1,high)
        merge(li,low,mid,high)
    return li

if __name__ == "__main__":
    li = [2,3,12,9,7,6,5,10,14]
    li = merge_sort(li,0,len(li)-1)
    print(li)

输出结果:

3.堆排序:由列表(顺序表)构造

过程:(1)构建大顶堆(由于要使最终的列表按照升序排列)

(2)取出堆顶元素,将堆末尾非叶子节点放在堆顶位置,然后向下调整堆

(3)按照步骤(2)依次进行,直至堆为空(实际上每次从堆顶下来的元素会存储在堆末尾与其对应的节点位置,不再开辟新的存储空间,为了节省内存,将排好序的元素放在原列表中)

#堆排序
#向下调整堆
def sift(li,low,high):
    #low为堆顶值的下标
    i = low
    #j是下标为i的值的左孩子
    j = 2*i+1
    #保存li[i]指向的值
    temp = li[i]
    #high为堆末尾值的下标
    while j <= high:
        #注意:在列表中如果索引对应的值为空,则用该索引调用时会出错,例:li=[],则li[0]会出错。
        if (j+1)<=high and li[j+1]>li[j]:
            #让j指向j+1
            j = j+1
        #如果li[j]指向的值比temp指向的值大,让li[i]指向大的值
        if li[j]>temp:
            li[i] = li[j]
            #i下移指向j
            i = j
            j = 2*i+1
        else:
            li[i] = temp
            break
    #while...else组合,当while中不执行break时会调用else的语句,当li[j]>temp导致的j>high时,执行else       
    else:
        li[i] = temp
        
def heap_sort(li):
    n = len(li)
    #构建大顶堆
    for i in range((n-2)//2,-1,-1):
        sift(li,i,n-1)
    #取出堆顶元素,将堆末尾非叶子节点放在堆顶位置,交换位置,然后向下调整堆
    for j in range((n-1),-1,-1):
        li[0],li[j] = li[j],li[0]
        #sift()函数中的high为j-1
        sift(li,0,j-1)

if __name__ == "__main__":
    li = [2,3,12,9,7,6,5,10,14]
    heap_sort(li)
    print(li)

输出:

针对以上六种算法的小总结:  

                  

7.lowB三人组中插入排序的进化版即希尔排序(时间复杂度依gap设置不同分情况讨论)

#希尔排序
#gap为需排序两数的间隔
def insert_gap(li,gap):
    for i in range(gap,len(li)):
        temp = li[i]
        j = i-gap
        while j>=0 and temp<li[j]:
            li[j+gap] = li[j]
            j = j-gap
        li[j+gap] = temp

def shell_sort(li):
    d = len(li)//2
    #间隔d每次除以2
    while d>=1:
        insert_gap(li,d)
        d //= 2

if __name__ == "__main__":
    li = [2,3,12,9,7,6,5,10,14,20,17,16,18,19]
    shell_sort(li)
    print(li)

输出:

十兄弟之高大上三人组

平均时间复杂度为O\left ( n \right )

1.计数排序:将要排序的值作为新列表的下标,缺点:取值范围应为要排序的值的最大值+1,需开辟较大存储空间

#计数排序\
#设置存储范围max_count
def count_sort(li,max_count=100):
    count = [0 for _ in range(max_count+1)]
    #把要排序的值作为count列表的下标
    for val in li:
        count[val] += 1
    li.clear()
    #data为index的出现次数
    for index,data in enumerate(count):
        for i in range(data):
            li.append(index)

if __name__ == "__main__":
    li = [2,3,12,9,7,6,5,10,14,20,17,16,18,19]
    count_sort(li)
    print(li)

2.桶排序:平均时间复杂度为O\left ( k+n \right ),其中k为列表的长度n与桶的个数求出的值,最坏时间复杂度为O\left ( n^{2} k\right ), 空间复杂度为O\left ( kn \right )

#桶排序
def bucket_sort(li, n=4,max_length=20):
    #构建二维数组,在列表内建多个桶
    buckets = [[] for _ in range(n)]
    for val in li:
        i = min(val//(max_length//n),n-1)
        buckets[i].append(val)
        #每个桶内进行冒泡排序
        for j in range(len(buckets[i])-1,0,-1):
            if buckets[i][j]<buckets[i][j-1]:
                buckets[i][j-1],buckets[i][j] = buckets[i][j],buckets[i][j-1]
            else:
                break
    sorted_li = []
    for buc in buckets:
        #extend函数可以在列表内添加多个值
        sorted_li.extend(buc)
    return sorted_li

if __name__ == "__main__":
    li = [2,3,12,9,7,6,5,10,14,20,17,16,18,19]
    li = bucket_sort(li)
    print(li)

3.基数排序:注意:当要排序的数中最大值很大,而要排序的这些数的个数较少时,时间复杂度会变大。因为时间复杂度为(kn),k为log_{10}max\left ( list \right ),其中k由最大值影响,而不受个数影响。

基数排序的时间复杂度为O\left ( kn \right ),空间复杂度为O\left ( kn \right )

而快速排序的时间复杂度为O\left ( nlog_{2}n} \right ),n为排序总个数。

#基数排序
def radix_sort(li):
    max_num = max(li)
    k = 0
    #例如:1000->4(个十百千),100000->6位
    while 10**k <= max_num:
        #依次从个位开始排序,个位排好后再排十位。。。
        #每次设置10个桶
        buckets = [[] for _ in range(10)]
        for val in li:
            #例如:987,取个位:987//1%10;取十位:987//10%10;取百位:987//100%10
            i = val//(10**k)%10
            buckets[i].append(val)
        li.clear()
        for buc in buckets:
            li.extend(buc)
        k += 1
        
if __name__ == "__main__":
    li = [2,3,12,9,7,6,5,10,14,20,17,16,18,19]
    radix_sort(li)
    print(li)

总结:以上十种排序算法中前7种针对实数(小数、正负整数)都可以进行排序,而后三种排序算法只能针对正整数,对于小数以及负数的排序,这三种算法得稍作改进,有时间的话我会再整理出来。

注:我正在复习这些排序算法准备职场笔试,通过看一些视频和其他资料在博客上进行了整理,非常欢迎大家给我提出问题,与我积极讨论,让我们共同进步!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值