本文介绍比较快速的三种主流排序方法,平均时间复杂度均为。其中,堆排序的介绍中,默认读者已经对堆结构有了一定了解。代码同样实现原地排序,即只改变原数组,不用新数组替代。
快速排序
算法思路:快速排序也是一种分而治之的思路。首先先将数组的第一个元素视为目标元素,我们的第一个任务是将目标元素放到他在数组中“合适”的位置。“合适”的位置含义是这个位置左边的元素均比目标元素小,右边的元素均比目标元素大(但左右不一定有序)。当目标元素找到“合适”的位置后,我们以这个位置为分割点,分别将左右两个子数组重复上述操作。与归并排序类似,最小子数组为单个元素数组。
时间复杂度:
1. 最坏情况
2. 平均情况
3. 最好情况
空间复杂度:最坏 ,平均
稳定性:不稳定
代码复杂度:较复杂
def QuickSort(arr, l, r):
if l < r:
mid = Partition(arr,l,r) #返回值为切割点坐标,但此函数已将arr进行了调整
QuickSort(arr, l, mid-1)
QuickSort(arr, mid+1, r)
def Partition(arr, l, r):
temp = arr[l]
while l < r:
while l < r and arr[r] >= temp:
r-=1
arr[l] = arr[r]
while l < r and arr[l] <= temp:
l+=1
arr[r] = arr[l]
arr[l] = temp
return l
归并排序
算法思路:一种分而治之(divide-and-conque)的思路,先将数组分为左右两个子数组,将子数组进行排序(子问题),子数组成为有序列后,再将两个有序列合并成为一个有序列。最小子数组为数组里只有单个元素时,单个元素的数组可以视作有序列。
时间复杂度:
1. 最坏情况
2. 平均情况
3. 最好情况
空间复杂度:
稳定性:稳定
代码复杂度:较复杂
def MergeSort(arr, l, r):
if l < r:
mid = (l+r)//2
MergeSort(arr, l, mid)
MergeSort(arr, mid+1, r)
Merge(arr, l, mid, r)
def Merge(arr, l, mid, r):
L, R = [], []
for i in range(l, r+1):
if i < mid+1:
L.append(arr[i])
else:
R.append(arr[i])
L.append(float("inf"))
R.append(float("inf"))
i = j = 0
for k in range(l, r+1):
if L[i] < R[j]:
arr[k] = L[i]
i+=1
else:
arr[k] = R[j]
j+=1
堆排序
算法思路:首先先将列表化成大根堆,化成堆的过程也用到了分而治之的思想,即将每个子堆依次化成堆,再向上递归。化成堆后,可以保证堆顶的元素一定是整个堆的最大元素,因此我们只需要将堆顶元素与数组最后一个元素位置互换,再将去掉“尾巴”的剩余元素继续化成堆,重复此操作即可。
时间复杂度:
1. 最坏情况
2. 平均情况
3. 最好情况
空间复杂度:
稳定性:不稳定
代码复杂度:复杂
def HeapSort(arr):
n = len(arr)
for i in range((n-2)//2, -1, -1): # n-1是最后一个叶节点位置,他的父亲位置就是
#(n-1-1)//2,不管是左儿子还是右儿子,父节点都是
#(j-1)//2
sift(arr, i, n-1) # 令high均为n-1,因为high的作用就是不超边界
#建堆完成
for i in range(n-1, -1, -1):
arr[0], arr[i] = arr[i], arr[0] # 由于arr[0]一定是堆的最大数,因此我们每次将最大数放到最尾部
sift(arr, 0, i-1) # 重新建堆,但是不再考虑最后i个元素,因为后面i个元素已经有序
def sift(arr,low,high):
top = arr[low]
i = low
j = 2*i + 1
while j <= high:
if j+1 < high and arr[j+1] > arr[j]:
j = j+1
if arr[j] > top:
arr[i] = arr[j]
i = j
j = 2*i + 1
else:
break
arr[i] = top