排序算法之归并排序及其优化
归并排序介绍
归并排序是创建在归并操作上的一种有效的排序算法,效率为O(n log n)。1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。
自顶向下
不说废话了,开工。
待排数组如下:

第一步
我们首先将这 8 个元素,一层一层的分割,如:
- 第一层两组:[8 , 6, 2 , 3],[ 1, 5, 7, 4]
- 第二层四组:[8, 6], [2, 3], [1, 5], [7, 4]
- 第三层八组:[8], [6], [2], [3], [1], [5], [7], [4]
如下图:

第二步 归并操作(Merge)
从底层开始两两归并(当然也可以三三进行归并),归并之后是有序的。
第三层八组,进行两两归并:

第二层四组,进行两两归并:

第一层两组,进行两两归并:

此时,数组已经排序完成!
第三步 分析程序
很明显,上面的是自顶向下,采用的是分治法,我们采用递归的方法,来实现这个算法:
1 | void mergeSort(int arr[],int n) { |
开始分割:
1 | void __mergeSort(int arr[],int l,int r) { |
注意:这里取中间的 mid 有个问题,大多数人都会写成这样:
1 | int mid = (l + r)/2; |
这样写不严谨,当l和r 都很大时,(l + r) 会溢出。
先不断的分割[l,mid],然后[mid+1,r]
最后归并:
1 | void _merge(int arr[],int l, int mid,int r) { |
这里创建了一个临时的数组长度为 (r - l) + 1 .
注意:如果数据量很大,可能会造成栈溢出,这是归并排序的一个缺点。
咱们来分析一下归并排序的最后一步:

先排除边界:
- 当
i > mid时,说明[l,mid]区间已经结束,所以 [mid+1,r] 直接赋值就行了; - 当
j > r时,说明[mid+1,r]区间已经结束,所以 [l,mid] 直接赋值就行了;
开始比较:
第一次比较
i -> 2,j -> 1; 2 > 1,将 1 放进去:

第二次比较
此时数组的状态及各个变量的指向:

i -> 2,j -> 4; 2 < 4,将 2 放进去:

第三次比较
此时数组的状态及各个变量的指向:

i -> 3,j -> 4; 3 < 4,将 3 放进去:

第四次比较
此时数组的状态及各个变量的指向:

i -> 6,j -> 4; 6 > 4,将 4 放进去:

后面的就不一一解释了,按照这个思路,请继续往下画…
我相信前面的程序,现在是非常清晰了。
自低向上
其实自低向上很好理解,就是第一步被分割后,开始向上归并,只不过不是使用递归的解法,而是迭代法。
我这里把程序贴出来,大家跟着程序把图画一下,就理解了。
1 | void mergeSortBackUp(int arr[],int n) { |
注意:这里的_merge 函数 用的是上一步的!
测试
这里用了 1千万一个数据测试,测试结果如下:
1 | **************有序************* |
最后附上Demo