主要排序算法:冒泡、选择、插入、希尔、归并、快速、堆排序。时间复杂度和空间复杂度及稳定性(原始序列中相同的两个元素经排序后前后顺序不改变)总结如下图。
1.冒泡排序
从序列第一个数开始,n-1趟遍历,每一趟比较相邻元素,逆序交换,目的在于使得每一趟最后一个元素是最大值。
def bubbleSort(alist):
for passnum in range(len(alist)-1,0,-1):#排序趟数,从n-1到1
for i in range(passnum):##每一趟比对相邻数据-逆序交换-比对passnum次
if alist[i]>alist[i+1]:
alist[i],alist[i+1]=alist[i+1],alist[i]
return alist
print(bubbleSort([3,6,8,10,4]))
可加入检测机制改进冒泡法,当检测到某一趟无数据交换发生说明已无逆序数,可结束循环得到有序序列。
def imBubbleSort(alist):
passnum=len(alist)-1
exchange=True
while passnum>0 and exchange:
exchange=False
for i in range(passnum):
if alist[i]>alist[i+1]:
#exchange=True
alist[i],alist[i+1]=alist[i+1],alist[i]
exchange=True#发生交换尚未排序结束
passnum-=1
return alist
print(imBubbleSort([3,89,10,45,6,8]))
2.选择排序
遍历序列,n-1趟,每一趟标记最大元素位置,选出最大元素与最后一个元素交换。相比于冒泡法交换次数减少。
def SelectionSore(alist):
for passnum in range(len(alist)-1,0,-1):#趟数:n-1到1
maxpos=0
for i in range(1,passnum+1):#每一趟找最大值位置
if alist[i]>alist[maxpos]:
maxpos=i
alist[maxpos],alist[passnum]=alist[passnum],alist[maxpos]#交换,使最大值在每一趟末尾位置
return alist
#print(SelectionSore([1,9,30,78,3]))
3.插入排序
类似于整理扑克牌,从第二个元素开始,一次处理一个元素,将该元素与已已排序列比较并寻找插入位置(若当前元素小于已排序中元素i,则i开始整体后移,腾出一个空位给当前元素)。共需要处理n-1(2~末尾)个元素。
def InsertSort(alist):
for i in range(1,len(alist)):
newdata=alist[i]#每一项数据进行比对插入
pos=i
while pos>0 and alist[pos-1]>newdata:#比对,依次后移比当前项大的数据,空出位置
alist[pos]=alist[pos-1]
pos-=1
alist[pos]=newdata#插入当前项
return alist
print(InsertSort([1,78,34,10,96]))
4.希尔排序
对于插入排序而言,原始序列越短或列表越接近有序,插入比对次数越少,插入排序越高效。希尔排序在插入排序的基础上改进,将无序表进行间隔划分(逻辑划分,可理解为等间隔采样)形成子列表,再对子列表进行插入排序,这样做的目的是构造短的和接近有序的子列表以提升插入排序效率。间隔从n/2开始依次减半,直到为1。
def gapInsertSort(alist,start,gap):
for i in range(start+gap,len(alist)):#间隔划分子列表插入排序
newdata=alist[i]
pos=i
while pos>=gap and alist[pos-gap]>newdata:#间隔为gap
alist[pos]=alist[pos-gap]#移动
pos-=gap
alist[pos]=newdata#插入
return alist
def ShellSort(alist):
gap=len(alist)//2#初始gap选择一半长
while gap>0:
for start in range(gap):
gapInsertSort(alist,start,gap)
#print(gap,alist)
gap=gap//2
return alist
print(ShellSort([1,56,79,90,3,59,78,33,45,66,99]))
5.归并排序
分治思想,讲原始序列分为一个个子序列,对子序列进行排序合并,直到整个序列有序。
有递归和迭代两种方法。
def recMergeSort(alist):
#对半分裂o(log n)
if len(alist)<=1:
return alist
else:
mid=len(alist)//2
left=recMergeSort(alist[:mid])
#print('left:',left)
right=recMergeSort(alist[mid:])
#print('right:',right)
#合并过程-O(n)
merged=[]
while left and right:
if left[0]<right[0]:#小的先添加到结果
merged.append(left.pop(0))
else:
merged.append(right.pop(0))
#print(merged)
merged.extend(left if left else right)#左半部分或右半部分有剩余
#print('final:',merged)
return merged
#print(MergeSort([1,56,78,90,3,4,55]))
def iterMergeSort(alist):
n=len(alist)
i=1
while i<n:
left_start=left_end=right_start=right_end=0#初始化游标
while left_start<=n-i:
merged=[]
right_start=left_end=left_start+i
right_end=left_end+i
if right_end>n:
right_end=n
left=alist[left_start:left_end]#会需要额外空间开销,可以不要left和right,直接在原列表切片
right=alist[right_start:right_end]
while left and right:
if left[0]<right[0]:#小的先添加到结果
merged.append(left.pop(0))
else:
merged.append(right.pop(0))
#print(merged)
merged.extend(left if left else right)#剩余元素添加
alist[left_start:right_end]=merged##中间排序结果返回给alist-类似于return的作用
#print(alist,left_start)
left_start+=i*2##右移游标,依次处理剩余元素
i*=2##进入下一次merge
return alist
print(iterMergeSort([5,3,2,7,9,0,77,90,45,44]))
6.快速排序
(1)选择原始序列中任意元素(一般为头、尾或中间)作为基值。
(2)一趟排序:将所有小于基值的元素移动到左边,左右大于基值的元素移动到右边,基值放中间。
(3)重复(2)直到所有元素都排好
def myQuickSort(alist,start,end):
if start>end:
return
else:
mid=alist[start]
left=start+1
right=end
done=False
while not done:
while left<=right and alist[left]<=mid:#取等是因为列表可能包含相等元素
left+=1
while right>=left and alist[right]>=mid:
right-=1
if left>right:
done=True
else:
alist[left],alist[right]=alist[right],alist[left]
alist[start],alist[right]=alist[right],alist[start]#右标所指与中值交换,使左边都是小于中值的值,右边都是大于的
myQuickSort(alist,start,right-1)
myQuickSort(alist,right+1,end)
return alist
print(myQuickSort([1,3,9,8,22,43,9,3],0,7))
7.堆排序
应用二叉堆(满足堆次序的完全二叉树)实现排序。时间复杂度o(nlogn),不稳定。
#思路:建立大顶堆-堆顶与最后一个元素(最小值)交换减小规模形成不包含最后一个元素的子堆-堆调整函数实现子堆调整成大顶堆-重复直到堆规模为1。
def heapSort(ilst):
def heapAdjust(alst,parent_index,length):#堆调整函数
child_index=parent_index*2+1#左子节点,有一个偏移量因为下标从0开始
cur_node=alst[parent_index]#当前节点设置为当前父节点
while child_index<length:#遍历
if child_index+1<length and alst[child_index]<alst[child_index+1]:#并非叶子节点(最后一个元素)且左子节点小于右子节点-符合大顶堆规则
child_index+=1#子节点设置为大的那个的下标
if cur_node>alst[child_index]:#符合大顶堆,不做调整
break
alst[parent_index]=alst[child_index]#不符合,父节点与子节点交换,此时的child_index是左右子节点中较大的那个
parent_index=child_index
child_index=child_index*2+1
alst[parent_index]=cur_node
if ilst==[]:
return
rlst=ilst#结果列表赋初值
length=len(ilst)
#创建大顶堆
for i in range(0,length//2)[::-1]:#等于for i in range(length//2-1,-1,-1) 从中间处依次递减直至0位置
heapAdjust(rlst,i,length)
#交换堆顶和子堆最后一个叶节点并调整子堆成大顶堆
for j in range(1,length)[::-1]:#等于 for j in range(length-1,0,-1) 从最末依次递减至1位置
rlst[j],rlst[0]=rlst[0],rlst[j]#交换子堆的最后一个元素和堆顶
heapAdjust(rlst,0,j)#调整子堆成大顶堆
return rlst
ilst=[9,3,4,2,89,10,87]
#print(ilst)
print(heapSort(ilst))