排序x10

插入排序

直接插入

分析
空间复杂度O(1)
时间复杂度:
从第二个元素依次往后,每个需要for循环,也就是循环n-1趟。
每趟都要进行关键字对比、移动元素
最好:成序,只需要对比不需要移动,为O(n)
最坏:逆序排放,每趟都需要将当前元素与之前元素进行关键字对比、并将成序元素后移 O(n^n)
在这里插入图片描述

在这里插入图片描述
将10移动到A【0】,然后与绿字对比并后移一位…

平均时间复杂度:O(n^n)
是否稳定:稳定

一般做法:
分为 有序|当前|无序
首先,我们先遍历一下列表。
如果当前的元素 li[i] 比它前边的元素也就是 li[i-1]小的话:
先把当前元素存起来(入temp),再开始比较当前元素左边的元素 也就是 j=i-1
如果左边的元素li[j]比当前元素(被temp存贮的)大的话:左边的元素进行右移
右移后j–。
注意:当左边的元素不满足 li[j]>当前,j还是右移到了相应的位置,进行比较;比较失败(位置已经正好)才退出了while
所以空位置在j+1也就是此时的右1位置
让li[j+1]=temp

def insert_sort(li):
  for i in range(1, len(li)):
    if li[i]<li[i-1]:
      tem = li[i]
      j = i - 1
      while j >= 0 and li[j] > tem:
        li[j + 1] = li[j]
        j -= 1
      li[j + 1] = tem
list = [1, 3, 5, 6, 24, 58, 24, 67, 1, 3, 5]
insert_sort(list)
print(list)

【不推荐】哨兵做法:(且待改进)
第一个位置留出来设立为哨兵位置,定义序列时要多给一个空间

list = [1, 3, 5, 6, 24, 58, 24, 67, 1, 3, 5]
list.insert(0, 0)
def insert_sort(li):
    for i in range(2,len(li)):
        if(li[i]<li[i-1]):
            li[0]=li[i]
            j=i-1
            while j>0 and li[j]>li[0]:
                li[j+1]=li[j]
                j-=1
            li[j+1]=li[0]
    list.pop(0)
insert_sort(list)
print(list)

此处不太适宜,因为insert和pop都是消耗时空的
注:调用函数的时候,先函数排序,再输出函数

折半插入排序(优化)

已经有成序的元素,先折半查找,再移动元素
在这里插入图片描述
在这里插入图片描述

首先我们知道【0,3】是有序的,此时i=4,存储temp=A[4]

一般做法

通过代码以下代码

if A[mid] > tem:
    right = mid - 1
else:
    left = mid + 1

我们不断缩小目标在已知列表中的范围:

left=0,right=3,A[mid]<temp,则left=mid+1;缩小范围至【1,3】
left=1,right=3,A[mid]>temp,则right=mid-1;缩小范围至【1,2】
left=1,right=2,A[mid]=11<temp,left=mid+1;缩小范围至【2,2】 指针重合
###############################################################
left=2,right=2,A[mid]=20>temp,right=mid-1=1 此时不满足while 退出循环

在这里插入图片描述

此时我们可以看到左右指针已经重合,目标到底在2的左边还是右边呢?
答:想要成序, 范围不断缩小至左右指针重合

参考的标准是right,因为后边代码要 A[high+1]=temp

只可能 插入元素【temp】<重合元素【mid】:   插入在重合的左边 
if A[mid] > tem:
    right= mid - 1
将high减一,随后大数们后移,正好将A[high+1]赋值给重合元素
不可能 插入元素【temp】>=重合元素【mid】:        插入在重合的右边 
因为会在之前几步将 这所谓的 小于temp 的元素 归结到[temp 之前的有序列表]
else:
     left= mid + 1
high不变

从right的右边 到 排序i左1 都要后移一位

 for j in range(right+1,i-1):
     li[j]=alist[j-1]

完整代码:

def insert_sort(li):
    for i in range(1, len(li)):
        left = 0
        right = i - 1
        tem = li[i]
        while left <= right:
            mid = (left + right)//2
            if li[mid] > tem:
                right = mid - 1
            else:
                left = mid + 1
        for j in range(right+1,i-1):
            li[j]=alist[j-1]
        li[right+1] = tem

alist = [-11, 0, 1, 8, 7, 11, -1, 2, 10, 90]
insert_sort(alist)
print(alist)

哨兵
暂时不写,没意义

小结
虽然比较关键字次数减少,但整体的时间复杂度依然是O(n^n),

希尔排序

是插入排序的优化,缩小增量排序,适用于局部比较成序的列表

while(i-gap)>=0:
    if li[i]<li[i-gap]:
        li[i],li[i-gap]=li[i-gap],li[i]
        i-=gap

第一趟
538是怎么比较的范围?
因为9个元素,i的范围是【0,8】
i=4时,i-gap=0 与 i= 8,i-gap=4 是两两交换的关系,所以看起来是一组内
而且i=8,与i-gap=4 进行交换后,执行i-=gap 使此时i=4
i=4,i-gap=0 进行判断大小关系是否合适与是否交换
在这里插入图片描述
第二趟
在这里插入图片描述
第三天:d=1
直接插入排序
分析
空间复杂度:O(1)
时间复杂度
最坏O(n&n)
在某范围O(n^1.3)

完整代码

def shell_sort(li):
    gap=len(li)//2
    while gap>=1:
        for i in range(gap,len(li)):
            while(i-gap)>=0:
                if li[i]<li[i-gap]:
                    li[i],li[i-gap]=li[i-gap],li[i]
                    i-=gap
                else:
                    break
        gap//=2
    return li
list=[1,3 ,2,12 , 4,99, 4, 5, 6, 7, 8]
shell_sort(list)
print(list)

交换排序

冒泡排序

内层排序:

for j in range(0,?):
	if li[j]>li[j+1]:
		li[j],li[j+1]=li[j+1],li[j]

请问这里的“?”是多少呢?
因为j要和j+1进行交换,所以“?”是不可以到达最后一个元素(length-1)的,只能到达倒数第二个【这样倒2与倒1进行比较与交换】
ps:range是左闭右开的。这里range(len(li)-1) 是包含倒2不包含倒1

第一轮比较后,最大的数字到达最后
想接着排序:
随着成序的数字越来越多,右边的大越来越多
每趟循环所需排序的数字越来越少:len(li)-1 -i
实现方式:在外层添加循环

for i in range(length-1):			#外层循环
    for j in range(len(li)-1-i):	#内层循环

分析:
空间复杂度O(1)
时间复杂度:
最好:O(n) 原本是有序的
只需比较n-1次,不需要交换
最坏:O(n&n)原本是逆序的
比较次数:(n-1)+(n-2)+(n-3)+…+1=n(n-1)/2==交换次数
冒完第一个是n-1,第二个需要对比的就只有n-2

def buttle_sort(li):
    for i in range(len(li)-1):
        exchange= False
        for j in range(len(li)-1-i):
            if li[j]>li[j+1]:
                li[j],li[j+1]=li[j+1],li[j]
                exchange= True
        if not exchange:
            return
list=[1,3 ,2,12 , 4,99, 4, 5, 6, 7, 8]
buttle_sort(list)
print(list)

快速排序

算法思想:找一个数(第一个)作为基准元素,使基准归序后:比它小的在左边,比他大的在右边

while left<right:
    while left<right and li[right]>=tem:	#执行1
        right-=1
    li[left]=li[right]						#交换1
    while left<right and li[left]<=tem:		#执行2
        left+=1
    li[right]=li[left]						#交换2

在这里插入图片描述

left=0,right=7,且满足执行1的>=:right-- ,right到6
left=0,right=6,不满足执行1的>=, 退出“执行1”,进入交换1:27赋值给li[low]
结果如下
在这里插入图片描述

left=0,right=6,且满足执行2的<=:27<49,38<49 left移到li[2]
left=2,right=6,并不满足执行2的<=,进入交换2
在这里插入图片描述

left=2,right=6,满足执行1,right–
left=2,right=5,不满足执行一,退出while,进入交换1
在这里插入图片描述

left=2,right=5,满足执行2,left++
left=3,right=5,不满足执行2,进入交换2,
在这里插入图片描述

left=3,right=5,满足执行1:49<97,49<76 right–到3
也就算说:left=right=3 退出while(left<right)

 tem=li[left]
 while left<right:
     while left<right and li[right]>=tem:
         right-=1
     li[left]=li[right]
     while left<right and li[left]<=tem:
         left+=1
     li[right]=li[left]
 li[left]=tem
 return left

#################################################################

上一步就算步骤1的左右 最终return left赋给递归中的mid,来进一步划分与归序
左边是【left,mid-1】,右边是【mid+1,right】 很好理解
第一个元素最终位置已经确定,那么我们就不需要管这个49,直接看左子表(步骤2)和右子表(步骤3)

if left<right:
    mid =partition(li,left,right) #步骤1
    quick_sork(li,left,mid-1)	  #步骤2
    quick_sork(li,mid+1,right)	  #步骤3

在这里插入图片描述
进入步骤2:第二层处理【0,2】
在这里插入图片描述
很快排出13,27,38 ,第二层的步骤1得:mid=2,
进入第二层的步骤2,划分其左子表,即进入第三层的步骤一 :只有元素0 ,传入的left,right是0,0. 因为不满足left<right 直接退出第三层,返回上一层
直接进入第二层的步骤3,进第三层,出第三层 返回第二层的步骤3
在这里插入图片描述

进入二层的步骤3,划分【4,7】
第二层的步骤1得mid=6,
进入第二层步骤2,即第三层的步骤1:return 4
进入第三层的步骤2:进第四层 传入left,right为4,3,直接退出了
则进入第三层的步骤3:因为只有5一个元素,直接退出
在这里插入图片描述
到此位置,进入第二层的步骤3:
返回。返回第一层。排序完成。

分析:
在这里插入图片描述
每一层quicksork只需要处理剩余的待排元素,时间复杂度不超过O(n)
因为元素越多,递归工作栈就会越深,开辟空间就会越多

空间复杂度:O(递归层数)
最好O(logn) 最坏O(n)
时间复杂度:O(n递归层数)
最好O(nlogn) 最坏O(n
n)
在这里插入图片描述
若li=[1,2,3,4,5,6,7] 【完全有序,完全逆序】
则是最坏情况,因为一次快排后right的指针一直移动到left,并不是双向奔赴,而是划分为不均匀的部分

选择排序

简单选择排序

有序|无序
每趟在待排序元素中选取最小的元素 加入有序子序列

def select_sort(li):
    for i in range(len(li)-1):
        min_log=i
        for j in range(i+1,len(li)):
            if li[j]<li[min_log]:
                min_log=j
        li[i],li[min_log]=li[min_log],li[i]

空间复杂度:O(1)
时间复杂度:O(n*n)
无论有序、逆序、乱序,一定要n-1趟

比较次数:(n-1)+(n-2)+(n-3)+…+1=n(n-1)/2
交换次数<n-1
不稳定

堆排序

low:根节点
high:最后一个节点

i=low 开始是根节点
j=2*i+1 设j为左孩子
temp=li[low] 堆顶保存起来

编写sift函数

在这里插入图片描述
若i=5的位置没有节点了,则其左孩子 j=11 会大于high

if j+1<high and li[j+1]>li[j]:
    j=j+1

首先看右孩子比左孩子大的话,就此时指向右孩子
li[1]<li[2],将指向右孩子,即j=1+1=2

        if li[j]>temp:
            li[i]=li[j]
            i=j
            j=2*i+1
        else:
            li[i]=temp

如果li[j]大于temp【否则就把temp放回到li[i]】
li[2]>根节点:先将li[i]的值赋成 li[j] 的值
在这里插入图片描述

进入下一段:
i=2,j=5时,不满足while里面的条件:
在这里插入图片描述
################################################################
另起一图:解释最后的 【li[i]=temp】
在这里插入图片描述
图1是初始状态
图2 开始i指向空位,j指向数字6【通过代码】—> 图3 数字6上去
图3 i指向空位【下标7】,新的j指向新的空位
在这里插入图片描述
i=7的时候,j>high : 还是要把li[i]放回来


def sift(li,low,high):
    i=low
    j=2*low+1
    temp=li[low]
    while j <=high:
        if j+1<high and li[j+1]>li[j]:
            j=j+1
        if li[j]>temp:
            li[i]=li[j]
            i=j
            j=2*i+1
        else:
            li[i]=temp		#
    else:
        li[li]=temp

编写堆排序函数

构造堆:
for i in range((n-2)//2,-1,-1):
    sift(li,i,n-1)

孩子为 i,则父亲是 (i-1)/2
而最后元素的下标是 n-1,那么最后一个元素的父亲节点的下标是 (n-2)//2
for 循环一直倒着遍历倒0 【-1是不包含的】
i代表着:建堆的时候调整部分的根下标
在这里插入图片描述
开始i=4,调整黄色
随后i=3,调整褐色
然后i=2,调整蓝色
i=1,调整左大枝;i=0调整 整大枝

在这里插入图片描述
若i=1,high=2i+2? 确实可以,但是你并不知道下面是否还有层
high位置确实不好求,那么high作用是什么?
因为high唯一作用:判断和保证不越界即可
所以设置high为n-1 是不会有别的影响的

for里进入sift函数 根节点为i没有疑问,high是n-1

挨个出数

在这里插入图片描述
9下来了,与3换位置;最后的位置前移1个【i前移】
在这里插入图片描述

for i in range(n-1,-1,-1):
    li[0],li[i]=li[i],li[0]	#先做交换
    sift(li,0,i-1)			#再做调整

最后一共开始,一直到0
i一直指向最后的位置
最后一个与第一个(即将出数)交换,high前移一共

sift(li,0,i-1)

调整:low是根节点,high是最后一个元素
因为还在当前循环,i还没有变。
此时最后一个是i的前一个【i位置元素是成序出堆的】

def heap_sort(li):
    n=len(li)
    for i in range((n-2)//2,-1,-1):
        sift(li,i,n-1)
	#堆建完
	
    for i in range(n-1,-1,-1):
        li[0],li[i]=li[i],li[0]
        sift(li,0,i-1)
    #挨个出数

完整代码:

def sift(li,low,high):
    i=low
    j=2*low+1
    temp=li[low]
    while j <=high:
        if j+1<high and li[j+1]>li[j]:
            j=j+1
        if li[j]>temp:
            li[i]=li[j]
            i=j
            j=2*i+1
        else:
            li[i]=temp		#
    else:
        li[li]=temp
def heap_sort(li):
    n=len(li)
    for i in range((n-2)//2,-1,-1):
        sift(li,i,n-1)
    for i in range(n-1,-1,-1):
        li[0],li[i]=li[i],li[0]
        sift(li,0,i-1)

应用:n个数,取前k大的数

归并和基数排序

归并排序

思想:将两个、多个有序列表进行合并
首先设置一个更大的列表

在这里插入图片描述
在两个列表【可能是放在一个列表了】的左端分别设置指针,比较A[i]与B[j],小的一个移入数组C,然后被移入的下标++
若其中一个表空了,就直接将另一个表剩余的元素直接移入

#若两个列表是有序的,那么实现一次归并代码如下
def merge(li,low,mid,high):
    i=low
    j=mid+1
    templi=[]
    while i<=mid and j<=high:
        if li[i]<li[j]:
            templi.append(li[i])
            i+=1
        else:
            templi.append(li[j])
            j+=1
    while i<=mid:
        templi.append(li[i])
        i+=1
    while j<=high:
        templi.append(li[j])
        j+=1
    li[low:high]=templi         #切片也是左闭右开

归并排序的使用:使用归并

分解: 列表越分越小,直到分成一个元素
终止条件:一个元素是有序的
合并: 将两个有序表合并,列表越来越大
哈哈哈

def mergr_sort(li,low,high):
    if low <high:
        mid=(low+high)//2
        mergr_sort(li,low,mid)      #执行1
        mergr_sort(li,mid+1,high)   #执行2
        merge(li,low,mid,high)      #执行3

此递归函数的执行1,2将列表逐渐分割为一个一个小块
执行三逐渐将小块还原回来

基数排序

原数组:【32,13,94,52,17,54,93】

第一趟分桶:按个位数排序,加入【0,9】的桶
在这里插入图片描述
第一趟收集:依次输出,一定可以保证个位数小的在个位数大的前边
得到:【32,52,13,93,94,54,17】

第二趟分桶:按十位数排序,再依次输出
在这里插入图片描述
【13,17,32,52,54,93,94】

def radix_sort(li):
    max_num=max(li)
    it=0
    while 10**it<max_num:					#只要最大的数位数合适就可以
        bucket=[[] for _ in range(10)]      #建10个桶
        for var in li:
            digit=(var//10**it)%10
            bucket[digit].append(var)
        li.clear()
        for buc in bucket:
            li.extend(buc)
        it+=1								#个位后十位,十位后百位

桶排序

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值