1、归并排序思想
归并排序的核心思想:如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。
归并排序的执行效率与要排序的原始数组的有序程度无关,所以其时间复杂度是非常稳定的,不管是最好情况、最坏情况,还是平均情况,时间复杂度都是 O(nlogn)。空间复杂度是 O(n)。
2、代码详解(合并函数merge())
'''归并排序算法,a是int型列表'''
def merge_sort(a):
_merge_sort_between(a, 0, len(a) - 1)
'''递归调用函数'''
def _merge_sort_between(a, low, high):
if low < high: # 必要,特殊情况排查
mid = low + (high - low)//2
_merge_sort_between(a, low, mid)
_merge_sort_between(a, mid+1, high)
if a[mid] > a[mid+1]: # 时间优化:当前部分<=后部分时,则不需用进行merge操作
_merge(a, low, mid, high)
def _merge(a, low, mid, high):
# a[low...mid], a[mid+1...high] are sorted.
# 游标i , j 分别指向 a[low...mid], a[mid+1...high] 的第一个元素
i, j = low, mid + 1
tmp = [] # 临时数组
while i <= mid and j <= high:
if a[i] <= a[j]:
tmp.append(a[i])
i += 1
else:
tmp.append(a[j])
j += 1
# 循环结束时,会有一个子数组有剩余的数据
# if i <= mid ,说明前半个子数组有剩余的数据,start = i ,否则start = j
start = i if i <= mid else j
end = mid if i <= mid else high
# 将剩余的数据添加到 tmp 中
tmp.extend(a[start:end + 1])
# 将 tmp 中的数组拷贝回 a[low...high]
a[low:high + 1] = tmp
if __name__ == "__main__":
a1 = [3, 5, 6, 7, 8]
a2 = [2, 2, 2, 2]
a3 = [4, 3, 2, 1]
a4 = [5, -1, 9, 3, 7, 8, 3, -2, 9]
merge_sort(a1)
print(a1)
merge_sort(a2)
print(a2)
merge_sort(a3)
print(a3)
merge_sort(a4)
print(a4)
3、归并与快排的区别
归并排序和快速排序是两种稍微复杂的排序算法,它们用的都是分治的思想,代码都通过递归来实现,过程非常相似。理解归并排序的重点是理解递推公式和 merge() 合并函数。同理,理解快排的重点也是理解递推公式,还有 partition() 分区函数。
归并排序算法是一种在任何情况下时间复杂度都比较稳定的排序算法,这也使它存在致命的缺点,即归并排序不是原地排序算法,空间复杂度比较高,是 O(n)。正因为此,它也没有快排应用广泛。
快速排序算法虽然最坏情况下的时间复杂度是 O(n^2),但是平均情况下时间复杂度都是 O(nlogn)。不仅如此,快速排序算法时间复杂度退化到 O(n^2) 的概率非常小,我们可以通过合理地选择 pivot 来避免这种情。
上图可见,归并排序的处理过程是由下到上的,先处理子问题,然后再合并。
而快排正好相反,它的处理过程是由上到下的,先分区,然后再处理子问题。
归并排序虽然是稳定的、时间复杂度为 O(nlogn) 的排序算法,但是它是非原地排序算法。我们前面讲过,归并之所以是非原地排序算法,主要原因是合并函数无法在原地执行。快速排序通过设计巧妙的原地分区函数,可以实现原地排序,解决了归并排序占用太多内存的问题。