面试题:快排和归并都是基于什么思想的

答:基于分治的思想

思想概念

归并排序,英文MERGE-SORT,利用归并的思想,采用经典的分治(divide-and-conquer)策略来实现排序的方法。这里面精髓就是分和治,很多人可能熟悉快排,冒泡等熟见的排序方式,不太会用归并排序。

谈归并排序_数组

可以看到这个算法很类似完全二叉树,通过将数据分成一些小堆,然后再将这些数据排序后组合起来,就是分而治之的策略。你也可以理解为先裂项,再合并同类项,最后就是我们想要的结果。

#include <iostream>
void Merge(int r[], int r1[], int s, int m, int t)
{
int i = s;
int j = m + 1;
int k = s;
while (i <= m && j <= t)
{
if (r[i] <= r[j])
r1[k++] = r[i++];
else
r1[k++] = r[j++];
}
if (i <= m)
while (i <= m)
r1[k++] = r[i++];
else
while (j <= t)
r1[k++] = r[j++];
for (int n = s; n <= t; n++)
r[n] = r1[n];
}
void MergeSort(int r[], int r1[], int s, int t)
{
if (s < t)
{
int m = (s + t) / 2;
MergeSort(r, r1, s, m);
MergeSort(r, r1, m + 1, t);
Merge(r, r1, s, m, t);
}
}
int main()
{
int r[8] = {10, 3, 5, 1, 9, 34, 54, 565}, r1[8];
MergeSort(r, r1, 0, 7);
for (int q = 0; q < 8; q++)
std::cout << r[q] << std::ends;
return 0;
}

算法复杂度

并排序时间复杂度为O(N*logN),额外的空间复杂度O(N)。

归并排序比较占用内存,但却是一种效率高且稳定的算法。

谈归并排序_归并排序_02

应用

基于快速排序,做少量调整即可完成归并排序。

小和问题

谈归并排序_时间复杂度_03

看到这个问题,我们比较容易想到的一种实现方法是:双层for循环遍历,找到比当前数小的数,累加。这种方法的时间复杂度是O(n^2)。

def merge_sort(arr):
# 递归的对数组进行分解,仅剩一个元素时,停止分解
if len(arr) == 1:
return arr, 0
mid = len(arr) // 2
left, sum_left = merge_sort(arr[:mid])
right, sum_right = merge_sort(arr[mid:])
# 对数组进行合并
arr, sum = merge(sum_left + sum_right, left, right)
return arr, sum


def merge(sum, left, right):
i = j = 0
arr = []
while (i < len(left) and j < len(right)):
if left[i] < right[j]:
arr.append(left[i])
sum += left[i] * (len(right) - j)
i += 1
else:
arr.append(right[j])
j += 1
while (i < len(left)):
arr.append(left[i])
i += 1

while (j < len(right)):
arr.append(right[j])
j += 1
return arr, sum


if __name__ == '__main__':
arr = [2, 3, 8, 6, 7, 4, 5]
print(merge_sort(arr)[1])

逆序对问题

谈归并排序_归并排序_04

我们需要对数组进行降序排列,每次合并时,找到左边比右边大的数,print。同理,由于数组每次是降序合并的,所以,当左边的某个数大于右边的某个数时,相当于是大于右边的这个数后面的所有数。

def merge_sort(arr):
# 递归的对数组进行分解,仅剩一个元素时,停止分解
if len(arr) == 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
# 对数组进行合并
arr = merge(left, right)
return arr


def merge(left, right):
i = j = 0
arr = []
while (i < len(left) and j < len(right)):
if left[i] > right[j]:
arr.append(left[i])
print('(' + str(left[i]) + ',' + str(right[j]) + ')')
tmp_j = j+1
while (tmp_j < len(right)):
print('(' + str(left[i]) + ',' + str(right[tmp_j]) + ')')
tmp_j += 1
i += 1
else:
arr.append(right[j])
j += 1
while (i < len(left)):
arr.append(left[i])
i += 1

while (j < len(right)):
arr.append(right[j])
j += 1
return arr


if __name__ == '__main__':
arr = [2, 3, 8, 6, 7, 4, 5]
print(merge_sort(arr))

优化

检测序列中的天然有序子段,如果检测到严格降序子段就翻转序列为升序子段,在最好的情况下无论升序还是降序都可以使时间复杂度将为o(n),具有很强的自适应性。这个方法成为Timsort。

总结

很多人可能在acm一些编程大赛中会去研究这个算法,在实际的工作项目中如果不是去研究底层的源码,是不会接触到这个归并排序的。在实际面试中只要你能说出分而治之的大概思想,我觉得面试官也就让你过了。