11种排序算法 python实现
冒泡,选择,插入,快速,堆,希尔,归并,二分法(基于比较的)
计数, 桶,基数(基于非比较的)
- 排序算法时间复杂度
算法 | 最好情况 | 最坏情况 | 平均情况 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡 | n^2 | n^2 | n^2 | 1 | 稳定 (小于或者大于的才会交换,等于 不交换)) |
选择 | n^2 | n^2 | n^2 | 1 | 不稳定 |
插入 | n | n^2 | 1 | 稳定 | |
快速 | nlgn | 递归实现 基准 (以第一个元素为基准) | 不稳定 | ||
堆 | 建立堆,调整 调整 n 次 | 不稳定 | |||
希尔 | 分组插入排序 增量 减半直到增量为1 | 不稳定 | |||
归并 | 平均分成左右两部分、排序、再合并 递归调用 | 稳定 | |||
计数 | n+k,k为数组中最大的数 | ||||
桶 | |||||
基数 | |||||
二分插入 |
- 排序算法稳定性
1. 冒泡排序
冒泡排序的基本思想是通过与相邻元素的比较和交换,每次将最大元素放到数组尾部。整个过程类似于水中气泡的上升。
比较次数:为n-1,n-2,…, 1
时间复杂度 n^2
def sort_1(tinput): ## 冒泡排序
length =len(tinput)
if length<=0:
return None
if length ==1:
return tinput
for i in range(length):
for j in range(length-i-1): #每次将最大的放最后
if tinput[j]>tinput[j+1]: #将大的交换到后边
tinput[j],tinput[j+1]=tinput[j+1],tinput[j]
else:
pass
return tinput
2.选择排序
选择排序的基本思想也是比较和交换,但选择排序是通过对整体的选择。每次找出剩余数组中最小的元素与第一个元素交换。
时间复杂度为 n^2,与冒泡排序相比,交换次数减少。
def sort_2(self,tinput): #选择排序
length =len(tinput)
if length<=0:
return None
if length ==1:
return tinput
for i in range(length): #找出最小元素的下标
min_index =i
for j in range(i+1,length):
if tinput[j]<tinput[min_index]:
min_index =j
else:
pass
tinput[i],tinput[min_index] =tinput[min_index],tinput[i]
return tinput
3.插入排序
通过比较找到合适的位置,然后插入元素,从后往前比
将数组分为有序,无序两部分,每次将无序中的一个元素插入有序部分中。
最好情况,比较 n-1次
最坏情况1,2,……,n-1 .
def sort_3(tinput): #插入排序
length =len(tinput)
if length<=0:
return None
if length ==1:
return tinput
relist = [tinput[0]] #一个元素有序
for i in range(1,length):
for j in range(len(relist)): #找到插入的位置
if tinput[i] < relist[j]:
break
if tinput[i]== relist[j]:
j+=1
break
else:
pass
if j == len(relist) - 1:
relist.append(tinput[i]) ##加到末尾 ,j的最大值为len(relist)-1
else:
relist.insert(j, tinput[i])
return relist
4.快速排序(在实际应用中表现最好的算法)
快速排序的思想来自于冒泡排序,冒泡排序是通过相邻元素的比较和交换把最小的冒泡到最顶端,而快速排序是通过和基准数比较,交换小数和大数,这样一来不仅把小数冒泡到上面同时也把大数沉到下面。
每次都需要找一个基准数,从两边扫描。
递归,不稳定。
时间复杂度:nlgn。
def QuickSort(tinput,start,end):
i = start
j = end
if i >=j:
return
partion = tinput[start]
while i<j:
while i<j and tinput[j]>= partion: #往左扫描,找到第一个比partion小的数
j-=1
tinput[i] = tinput[j] #此时 i 的值以放入 partion中
while i<j and tinput[i]<=partion: #网友扫描,找到第一个比partion大的值,
i+=1
tinput[j]= tinput[i] #同上
tinput[i]= partion #将partion 放入它应该在的位置
QuickSort(tinput,start,i)
QuickSort(tinput,i+1,end)
return tinput
5.堆排序(升序(大顶堆),降序(小顶堆))
相关知识 :完全二叉树、堆、父子节点的索引关系
需要解决的两个问题
1,如何建立一个堆
2,删除堆顶元素后,如何调整堆
思路
1,先写出如何调整堆,
2,从最后一个根节点开始调整,直至第一个元素,调整完,大顶堆 就建立好了
3、将第一个元素(即堆顶元素(最大的元素))与最后一个元素交换,即(将最大元素放到末尾),然后调整堆 直至最后堆的大小为1
def heap_adjust(tinput,start,end):#调整最大堆
root = start
length = len(tinput)
son = int(2*root +1 ) ##左孩子
while son <= end:
if son+1<=end and tinput[son]<tinput[son+1]: #找出左右孩子中较大的那个
son+=1
if tinput[son]>tinput[root]: #如果孩子大于父节点 交换
tinput[son],tinput[root]=tinput[root],tinput[son] #交换
root = son
son = 2*root+1
else:
break ##如果父节点已经是大的,说明调整完整
def heap_sort(tinput):
lastroot = (len(tinput)-1-1)//2 #从最后一个根节点开始调整,即建立大顶堆
while lastroot >=0:
heap_adjust(tinput,lastroot,len(tinput)-1)
lastroot-=1
#大顶堆建立完成
lastnode = len(tinput)-1
while lastnode >=0:
tinput[0],tinput[lastnode] = tinput[lastnode],tinput[0] #将最后一个元素与第一个元素(即堆顶元素)交换
lastnode-=1
heap_adjust(tinput,0,lastnode) #调整堆
return tinput
6.希尔排序
希尔排序又称缩小增量排序,是插入排序的高阶实现。
1设置一个初始增量gap(一般为序列长的一半),将序列分为gap个子序列,对子序列进行插入排序,
2缩小增量,直至增量缩小为1,此时子序列就是整个序列,再进行一次直接插入排序即可
def ShellSort(tinput):
length = len(tinput)
gap = length//2 ##设置增量一般为序列长度的一半
while gap >0:
for i in range(gap,length):
while i >= gap and tinput[i]<tinput[i-gap]:
tinput[i],tinput[i-gap]= tinput[i-gap],tinput[i] #在子序列中找要插入的位置
i = i-gap
gap //=2 #增量减半
return tinput
7.归并排序
采用递归分治的思想,稳定,速度仅次于快排
1.合并两个有序序列
2.将原序列分为左右两个部分,对两个部分递归排序;
合并两个部分
def merge(left,right): ##合并两个有序的序列
i=0
j=0
list = []
while i< len(left) and j<len(right):
if right[j] < left[i]: #右边的数小,放入数组,指针右移
list.append(right[j])
j+=1
else:
list.append(left[i]) #
i+=1
list.extend(left[i:]) #将剩余部分 放入列表
list.extend(right[j:])
return list
def mergeSort(tinput):
if len(tinput)<=1:
return tinput
num = len(tinput)//2
left = mergeSort(tinput[:num]) #将左边部分排序
right = mergeSort(tinput[num:]) #将右边部分排序
list_ = merge(left,right) #合并左右两个部分
return list_
8.计数排序
计数排序是一种非基于比较的算法,计数排序的时间复杂度为n+k,k为数组中最大的数,但计数排序需要满足一定的前提条件,即待排序的数要满足一定的范围的整数,而且需要花费大量的空间。
算法示例
1.待排序序列 [1,3,1,3,4,3,3,5,2] 在 0到5的范围
2. 设置数组count = [0,0,0,0,0,0],即下标为0到5的数组
3.遍历元素组,元素对应位置加1,完成后count =[0,2,1,4,1,1]
4.根据count数组输出排序后的数组 [1,1,2,3,3,3,3,4,5]
def CountSort(tinput): #适用于一定范围的整数排序
max = tinput[0]
for i in tinput: # 找出最大值
if i>max:
max = i
count = [0 for x in range(max+1)] #创建一个下标最大为max 的数组
for i in tinput: #计数,每一个元素出现的次数
count[i]+=1
List1=[]
for i in range(len(count)):
if count[i] !=0: #出现次数不为0
for j in range(count[i]):
List1.append(i)
return List1
9.桶排序
非比较的算法,当待排序序列均匀分配时,时间复杂度为n,典型应用,高考分数。
1.创建M个桶
2.将序列按照每种映射函数映射到桶中
3.对每个桶中的子序列进行排序
4.从头到尾遍历每个桶
def BucketSort(tinput):
bucket =[[] for i in range(10)] #建立10个桶
for i in tinput:
index = i//10
if index<=9:
bucket[index].append(i) #将元素放入桶中
else:
bucket[9].append(i)
for i in range(10): #对每个桶中的元素,进行排序
bucket[i] = QuickSort(bucket[i],0,len(bucket[i])-1)
list =[]
for i in bucket:
if i:
list.extend(i)
return list
10.基数排序
一种借助多关键字排序的思想。
通过多次分配和收集实现关键字排序,一般情况下,关键字优先级高的先进行分配和收集。
借助桶
对于数字排序,LSD(适用于位数少的,先从低位开始),MSD(适用于位数多的,先从高位开始)
def RadixSort(tinput):
if len(tinput)<=0:
return
for i in range(1,6): #依次取个、十,百,千,万作为关键字
bucket = [[] for m in range(10)]
ii = 10**i
print(ii)
for j in tinput : #分配
index = (j//ii)%10 #去每一个数的相应位数上的数
bucket[index].append(j)
tinput =[]
for k in bucket: #收集
if k:
tinput.extend(k)
return tinput
11.二分法排序
二分法插入排序是在插入排序的基础上,使用二分法查找将元素插入的方法,二分插入排序的原理较为简单,但是二分边界的确定以及范围比较的实现较为繁琐。
基本原理:(升序)
1.将元素依次放入有序序列中
2.取出待排序元素,与有序序列的前半段进行比较
3.缩小有序序列范围,进一步划分比较,直至范围内仅有1或2个数字
4.将插入值与范围进行比较
3.重复实现升序
实现过程:外层循环控制循环次数,中层循环实现有序排列,内层循环实现查找插入。
最好情况:每次中间的刚好是插入位置,比较 n-1 稳定
最坏情况 :lg1+lg2+lg3+……+lg(n-1) nlgn
def BinaryInsertSort(tinput):
length = len(tinput)
if length<=1:
return
for i in range(1,length):
left = 0
right = i-1
while left <right: #二分查找插入位置
mid = (left+right)//2
# print(mid," ",i)
if mid == left: # 边界值已确定
break
if tinput[mid] == tinput[i]: ##如果与中间元素相等
break
if tinput[mid] <tinput[i]:
left=mid
else:
right = mid #left指针所指的位置,就是要插入的位置
#先确定是否因为找到相同值而提前终止
if tinput[i] == tinput[(left+right)//2]:
tinput.insert((left+right)//2,tinput[i])
tinput.pop(i+1)
else:
if left == right:#只有一个元素
if tinput[left]<tinput[i]:
pass
else:
tinput[left],tinput[i]=tinput[i],tinput[left]
if left<right: #有两个元素
if tinput[right]<tinput[i]:
tinput.insert(right+1,tinput[i])
else:
if tinput[left]<tinput[i]:
tinput.insert(right,tinput[i])
else:
tinput.insert(left,tinput[i])
tinput.pop(i+1)
return tinput