常见的7种比较类排序算法(python详解)
常见的7种比较类的排序算法
冒泡排序、插入排序、选择排序、快速排序、希尔排序、归并排序、堆排序详解以及python实现
算法稳定:对于序列中元素相等的值,若排序后前后顺序不变,则算法稳定
1. 冒泡排序
时间复杂度O(n2)
稳定
主要思想:
每次排序时,从头开始两两进行比较,较小的数向前“冒”,较大的数向后“沉”
python实现:
def bubble_sort(arr):
for i in range(0,len(arr)):
#每排序一次,经过两两比较最大值“沉”到最后
for j in range(1,len(arr)-i):
#将第一个元素到未排序好的元素进行两两比较,大数靠后
if arr[j]<arr[j-1]:
arr[j-1],arr[j]=arr[j],arr[j-1]
return arr
2.插入排序
时间复杂度O(n2)
稳定
主要思想:将元素插入到排好序序列的合适的位置(在排好序的序列中从后向前依次两两比较)
python实现:
def insert_sort(arr):
for i in range(1,len(arr)):
#i为当前要插入元素下标,与已经排好序的arr[:i]从后向前依次两两比较
pre_index=i-1
current=arr[i]
#如果待插入的值小,则比他大的值后移,直到找到插入点
while pre_index>=0 and arr[pre_index]>current:
arr[pre_index+1]=arr[pre_index]
pre_index-=1
arr[pre_index+1]=current
return arr
3.选择排序
时间复杂度O(n2)
不稳定
主要思想:从未排好序的选择最小的元素放在最前面(交换)
python实现:
def select_sort(arr):
for i in range(0,len(arr)):
#每一次排序后将最小的值放到最前面:从后面未排序的序列中找到最小值的索引,与当前arr[i]交换
min_index=i
for j in range(i+1,len(arr)):
if arr[j]<arr[min_index]:
min_index=j
if min_index!=i:
arr[i],arr[min_index]=arr[min_index],arr[i]
return arr
4.快速排序
时间复杂度O(nlogn)
不稳定
主要思想:选择一个基准元素(一般情况选第一个或最后一个),根据小于基准元素和大于基准元素将原序列分为两个子序列,再分别对子序列进行快排
python实现:
def quick_sort(arr):
#递归实现,先找到终止条件
if len(arr)<2:
return arr
#选择arr[0]为基准元素,将arr[1:]与其进行比较
left,right=[],[]
for i in range(1,len(arr)):
if arr[i] <=arr[0]:
left.append(arr[i])
else:
right.append(arr[i])
#每进行一次排序,基准元素的位置已经确定,分别将小于和大于基准元素的子序列进行快排
return quick_sort(left)+[arr[0]]+quick(right)
5.归并排序
时间复杂度O(nlogn)
稳定
主要思想:先两两合并成有序序列,再四四合并…
python实现:
递归方法:将大问题逐步分解成一个个小问题,先解决小问题(调用本身函数),再解决大问题
def recursion_merge_sort(arr):
#分割成不同子序列,各子序列分别再分割
mid=len(arr)//2
arr1=arr[:mid]
arr2=arr[mid:]
if len(arr1)>1:
arr1=recursion_merge_sort(arr1)
if len(arr2)>1:
arr2=recursion_merge_sort(arr2)
result=[]
#合并两个已经排好顺序的子序列
while arr1 and arr2:
if arr1[0]<arr2[0]:
result.append(arr1.pop(0))
else:
result.append(arr2.pop(0))
if arr1:
result=result+arr1
if arr2:
result=result+arr2
return result
非递归方法:先解决小问题,将得到的结果替换自己本身的值(迭代),将小问题一步步合并
def non_recursion_merge_sort(arr):
#分割未排序序列,步长i,逐渐增大步长
i=1
while i<len(arr):
low=0
while low<len(arr):
mid=low+i
high=min(mid+i,len(arr))
if mid<hight:
left,right=arr[low:mid],arr[mid:high]
#将每个子序列进行两两比较,合并成一个有序序列
result=[]
while left and right:
if left[0]<right[0]:
result.append(left.pop(0))
else:
result.append(right.pop(0))
if left:
result+=left
if right:
result+=right
#用新得到的结果替换自己的值
arr[low:high]=result
low+=2*i
i*=2
return arr
6.希尔排序
时间复杂度O(n1.3)
不稳定
主要思想:根据一定的增量(分组的跨度)分割序列,各子序列使用插入排序,再逐渐减小增量
python实现:
def shell_sort(arr):
grap=len(arr)//2
while grap>0:
#相当于跨度为grap的插入排序
for i in range(grap,len(arr)):
current=arr[i]
pre_index=i-grap
#两两比较,大的后移,找到插入点,插入当前元素
while pre_index>=0 and arr[pre_index]>current:
arr[pre_index+grap]=arr[pre_index]
pre_index-=grap
arr[pre_index+grap]=current
#逐渐减小跨度(跨度为1时为插入排序)
grap//=2
return arr
7.堆排序
时间复杂度O(nlogn)
不稳定
主要思想:若升序排列,根据大顶堆(根节点值大于等于所有左右子树的值的完全二叉树),每次排序将堆顶元素与最后一个值交换,调整大顶堆,交换…
python实现:
def heap_sort(arr):
#以start为根节点调整大顶堆(比较根节点与所有子树的值大小,最大值放在堆顶),end为堆的最后一个值对应的下标
def adjust_heap(start,end):
root=start
#若根节点下标为i,则左孩子下标为2*i+1,右孩子为2*i+2
while True:
child=2*root+1
#没有左子树
if child>end:
break
#找到各子树中的最大值
if child+1<end and arr[child+1]>arr[child]:
child+=1
if arr[root]<arr[child]:
arr[root],arr[child]=arr[child],arr[root]
root=child
#最大值已经在堆顶,结束调整
else:
break
#创建大顶堆
#完全二叉树中,最后一个非叶子节点的下标
last_no_leave=len(arr)//2-1
while last_no_leave>=0:
#调整每一个带有孩子的子树
adjust_heap(last_no_leave,len(arr)-1)
last_no_leave-=1
#将堆顶元素与最后一个元素交换,调整堆,再交换...
end=len(arr)-1
while end>0:
arr[0],arr[end]=arr[end],arr[0]
#除去最后一个元素,将剩余元素调整为大顶堆
adjust_heap(0,end-1)
end-=1
return arr