插入排序
直接插入
分析
空间复杂度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
第一趟
因为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(nn)
若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 #个位后十位,十位后百位