归并排序(Merge Sort)
- 假设现在的列表分两段有序,将其合成为一个有序列表,这种操作称为一次归并
- 一次归并复杂度是O(n)
代码:
def merge(li, low, mid, high): #一次归并,已经有两个有序区了
#low列表开始位置,mid前一个有序区最后一个值的位置,high列表结束位置
i = low #i指向前一个有序区
j = mid + 1 #j指向后一个有序区
ltemp = [] #临时列表
while i <= mid and j <= high: #只要左右两边都有数
if li[i] < li[j]:
ltemp.append(li[i])
i += 1
else:
ltemp.append(li[j])
j += 1
#while执行完后,有一个区没数了
while i <= mid: #前一个区还有数
ltemp.append(li[i])
i += 1
while j <= high: #后一个区还有数
ltemp.append(li[j])
j += 1
li[low:high+1] = ltemp
使用归并
- 分解:将列表越分越小,直至分成一个元素
- 终止条件:一个元素是有序的
- 合并:将两个有序列表归并,列表越来越大
代码:
def merge_sort(li, low, high):
if low < high: #至少有两个元素
mid = (low + high) // 2
merge_sort(li, low, mid)
merge_sort(li, mid+1, high)
merge(li, low, mid, high)
归并排序复杂度
- 共logn层,所以最后的时间复杂度是O(nlogn)
- 非原地排序,空间复杂度为O(n)
快速排序、堆排序、归并排序比较
- 时间复杂度都是O(nlogn)
- 一般情况下,就运行时间而言
- 快速排序<归并排序<堆排序
- 三种排序算法的缺点
- 快速排序:极端情况下排序效率低
- 归并排序:需要额外的内存开销
- 堆排序:在快的排序算法中相对较慢
六种排序方法小结
排序方法 | 时间复杂度 | 空间复杂度 | 稳定性 | 代码复杂度 | ||
---|---|---|---|---|---|---|
最坏情况 | 平均情况 | 最好情况 | ||||
冒泡 | O(n^2) | O(n^2) | O(n) 改进过的冒泡排序 | O(1) | 稳定 | 简单 |
直接选择 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 | 简单 |
直接插入 | O(n^2) | O(n^2) | O(n^2) | O(1) | 稳定 | 简单 |
快速 | O(n^2) 递归n层 | O(nlogn) | O(nlogn) | 平均情况O(logn) 最坏情况O(n) 递归需要用到空间 1层是O(1) | 不稳定 | 较复杂 |
堆 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 | 复杂 |
归并 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 | 较复杂 |
- 稳定性保证相同的两个数的相对位置不变,例如32124,排完序后第一个2仍在第二个2前面
- 判断稳定性:挨个换的是稳定的,飞着换的是不稳定的