什么是合并?
与很多有用的算法类似,合并排序基于这样一个技巧:将 2 个大小为 N/2 的已排序序列合并为一个 N 元素已排序序列仅需要 N 次操作。这个方法叫做合并。
我们用个简单的例子来看看这是什么意思:
通过此图你可以看到,在 2 个 4元素序列里你只需要迭代一次,就能构建最终的8元素已排序序列,因为两个4元素序列已经排好序了:
- 1) 在两个序列中,比较当前元素(当前=头一次出现的第一个)
- 2) 然后取出最小的元素放进8元素序列中
- 3) 找到(两个)序列的下一个元素,(比较后)取出最小的
- 重复1、2、3步骤,直到其中一个序列中的最后一个元素
- 然后取出另一个序列剩余的元素放入8元素序列中。
这个方法之所以有效,是因为两个4元素序列都已经排好序,你不需要再『回到』序列中查找比较。
- 拆分阶段,将序列分为更小的序列合并排序是把问题拆分为小问题,通过解决小问题来解决最初的问题(注:这种算法叫分治法,即『分而治之、各个击破』)
- 排序阶段,把小的序列合在一起(使用合并算法)来构成更大的序列
拆分阶段
在拆分阶段过程中,使用3个步骤将序列分为一元序列。步骤数量的值是 log(N) (因为 N=8, log(N)=3)。【译者注:底数为2,下文有说明】
我怎么知道这个的?
我是天才!一句话:数学。道理是每一步都把原序列的长度除以2,步骤数就是你能把原序列长度除以2的次数。这正好是对数的定义(在底数为2时)。
排序阶段
在排序阶段,你从一元序列开始。在每一个步骤中,你应用多次合并操作,成本一共是 N=8 次运算。
- 第一步,4 次合并,每次成本是 2 次运算。
- 第二步,2 次合并,每次成本是 4 次运算。
- 第三步,1 次合并,成本是 8 次运算。
因为有 log(N) 个步骤,整体成本是 N*log(N) 次运算。
合并排序的强大之处
为什么这个算法如此强大?
因为:
- 你可以更改算法,以便于节省内存空间,方法是不创建新的序列而是直接修改输入序列。
注:这种算法叫『原地算法』(in-place algorithm)
- 你可以更改算法,以便于同时使用磁盘空间和少量内存而避免巨量磁盘 I/O。方法是只向内存中加载当前处理的部分。在仅仅100MB的内存缓冲区内排序一个几个GB的表时,这是个很重要的技巧。
注:这种算法叫『外部排序』(external sorting)。
- 你可以更改算法,以便于在 多处理器/多线程/多服务器 上运行。
比如,分布式合并排序是Hadoop(那个著名的大数据框架)的关键组件之一。
- 这个算法可以点石成金(事实如此!)
这个排序算法在大多数(如果不是全部的话)数据库中使用,但是它并不是唯一算法。如果你想多了解一些,你可以看看 这篇论文,探讨的是数据库中常用排序算法的优势和劣势。
算法代码
我实现的归并算法的java 代码:
/**
* 归并算法
* @param list
* @param first
* @param last
*/
public static void mergeSort(int[] list, int first, int last){
if(null == list || list.length < 0 || first == last){
return;
}
//这个几行代码可以省略 如果省略 那么当只有2个元素的时候还需要调用此方法递归
//那么其实只有一个元素当作mergeSort入参 其实没什么意义
//加上这几行仅仅是减少几次函数调用 对结果无影响 强迫症者 可删除。
if(last - first == 1){
sort(list, first, (last + first)/2, last);
return;
}
mergeSort(list, first, (last + first)/2);
mergeSort(list, (last + first)/2 + 1, last);
sort(list, first, (last + first)/2, last);
}
//这个方法是合并两个有序数组 temp数组可以不需要,直接对list 相应位置的元素操作即可
//那么这么做合并元素就好像直接插入排序算法一样,前半部分数组不动,后半部分数组比较插入
public static void sort(int[] list, int first, int mid, int last ){
int[] temp = new int[last - first + 1];
int i = 0;
int j = 0;
int index = 0;
while(i < mid - first + 1 && j < last - mid){
if(list[first + i] >= list[mid + 1 + j]){
temp[index] = list[mid + 1 + j];
j++;
index++;
} else {
temp[index] = list[first + i];
i++;
index++;
}
}
while(i < mid - first + 1){
temp[index] = list[first + i];
index++;
i++;
}
while(j < last - mid){
temp[index] = list[mid + 1 + j];
index++;
j++;
}
for(int k = first, n = 0; k <= last; k++, n++){
list[k] = temp[n];
}
System.out.println(Arrays.toString(list));
}