归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。
排序主要过程分为两步(以从小到大排序为例)。
分解序列
先对给定的原序列不断对半切割成左右子序列,再对各子序列分别对半切割……不断重复对半切割过程,直到最终所有子序列只有单个元素。单元素序列可以视为有序序列。合并序列:
对上一步骤最终分解得到的由同一父序列切割成的单元素序列两两进行对比合并。将值小者排前面,值大者排后面,合并完成后,产生了若干个双元素的有序序列。再对由同一父序列分解而成的双元素序列两两进行对比合并,首先由二者的首位进行对比,将值小者取出标记为位1,然后将值大者继续与另一序列(即取出了小值的那个序列)的第2位继续对比,将值小者取出标记为位2,之后剩余的值大者继续与刚才小值所在序列的下一位对比……
如果有一个序列的元素被全部取完,则将另一序列的元素一次性取出依序标记排位。不断将合并好的有序序列向上合并,直到所有切割成的序列都被合并完成,则排序完成。
图解算法
文字表述抽象,以图解说明更加直观易理解。
以对给定数组从小到大排序为例,图解如下:
从图解中可以看出,分解和归并操作还是比较容易理解的。无论是分解还是归并操作,其中蕴含的递归操作呼之欲出。
核心代码
/**
*
* @param a 要排序的序列
* @param left 每次序列的左端下标
* @param right 每次序列的右端下标
* @param temp 缓存数组。在方法内新建缓存数组亦可,但在递归中频繁开辟内存空间开销比较大。
*/
public static void mergeSort(int[] a, int left, int right, int[] temp){
if(left < right){
int mid = (left + right) / 2;//获取中间数,对半切割
mergeSort(a, left, mid, temp);//左序列递归切割
mergeSort(a, mid + 1, right, temp);//右序列递归切割
mergeArray(a, left, mid, right, temp);//左右序列合并
}
}
private static void mergeArray(int[] a, int left, int mid, int right, int[] temp){
int l = left;//l用来标记左序列每次拿出来对比的元素的下标,初始值为左序列首位。
int r = mid + 1;//r用来标记左序列每次拿出来对比的元素的下标,初始值为右序列首位,即mid+1位。
int tempIndex = left;//对比大小后,将小值放入缓存数组,tempIndex用来标记放入位置的下标。
while(l <= mid && r <= right){//表示左、右序列都还有元素时
if(a[l] < a[r]){//左、右序列取值进行对比。
//若a[l]小,则a[l]存入temp,左序列拿出下一位进行对比,故而l自增;temp存值后,其下标自增,用以下一次存值。
temp[tempIndex++] = a[l++];
} else {
temp[tempIndex++] = a[r++];
}
}
//可能出现一个序列元素全取完了,而另一个序列还有元素,则将所有元素存入temp。
while(l <= mid){
temp[tempIndex++] = a[l++];
}
while(r <= right){
temp[tempIndex++] = a[r++];
}
//将缓存数组的值存到原数组对应位置
int arrayIndex = left;
while(arrayIndex < tempIndex){//此处不用等号,因为上文while循环结束前,tempIndex自增了,用等号将下标越界。
a[arrayIndex] = temp[arrayIndex];
arrayIndex++;
}
}
调用方法如下:
int[] a = new int[]{3, 5, 8, 6, 9};
int len = a.length;
int[] temp = new int[len];
mergeSort(a, 0, len - 1, temp);
参考文章
http://blog.csdn.net/collonn/article/details/17581953
http://www.cnblogs.com/chengxiao/p/6194356.html
说明
本文为个人学习笔记,如有细节错误或描述歧义,请留言告知,谢谢!
本文首发于博客专栏:http://Windows9.Win/merge_sort_algorithm