前言
算法是迷人的,但通常也相当抽象。我在学习的时候,看了很多博客,教程,发现往往他们的代码都不太一样。这对我有点困扰。我总想写一个代表这个算法的标准实现。可是。。什么才是标准呢。。
归并算法简介
归并排序作为常见的一种排序,可以说关于他的介绍已经烂大街了。本文不做赘述。
值得一提的是
- 他的时间复杂度为O(nlogn)。
- 归并排序属于稳定排序,即排序之后,相等的两个元素的相对位置不会发生改变
- 归并排序常常用于待排序数组部分有序的情况。例如在map-reduce中,MapTask对于溢写文件的合并和ReduceTask对于fetch到的final out文件都是使用归并算法排序的
归并算法实现
1、二路归并原理
- 归并算法可以分成两个部分,一个是分组,一个是合并。分组的目的是得到两个有序的子数组。当然通常情况下,我们分完组之后,是不会得到有序数组的,这就需要我们利用递归方式去继续分组,直到我们可以轻松得到有序子数组。
- 二路归并就是把一个大组分成两个小组。利用递归思想,可以一直分下去,直到组数小于3。这时可以轻松实现这个小组的有序。这样我们就得到了一个部分有序的数组。这就完成了第一步分组。
- 第二步,我们要把这些有序数组合并起来。
给你个网图帮助你理解
这里可以看出,我们会使用一个新的数组来存放合并结果,然后将两个旧数组里的数据依次比较大小,小的放到新数组里,然后继续比较直到其中一个数组里的所有元素都放到新数组了,这也就说明另一个数组中的元素都是大于目前新数组里最大元素的了,这时直接将另一个数组中的所有剩余数据都插入新数组的末尾即可。
2、java代码实现
给出个人的代码实现
//分治法分组--二路归并
int[] dichotomyMerge(int []a){
//二路分组的尽头,也是递归结束的地方
if(a.length<3){//含有两个或者一个元素
if(a[0]>a[a.length-1]){//对2个或者1个元素进行排序
int tmp=a[0];
a[0]=a[a.length-1];
a[a.length-1]=tmp;
}
return a;
}
//说明分组还没结束,继续二路分组
int center =a.length/2;
int[] left =Arrays.copyOfRange(a,0,center);
int[] right =Arrays.copyOfRange(a,center,a.length);
left=dichotomyMerge(left);
right=dichotomyMerge(right);
//分完组之后,要进行二路归并(可以认为每个组都是有序的)
int i=0, l =0, r=0;//i:新数组的下标,l:left数组的下标,r:rigth数组的下标
while (l<left.length&&r<right.length) {//二路归并,直到其中一路完全归入新数组
// 为了节省空间,就用老数组a来当作新数组了
if(left[l]<right[r]){
a[i++]=left[l++];
}else if(left[l]==right[r]){
a[i++]=left[l++];
a[i++]=right[r++];
}else {
a[i++]=right[r++];
}
}
if(l==left.length){//将另外一路也归入新数组
for (int j = r; j < right.length; j++) {
a[i++]=right[j];
}
}else{//将另外一路也归入新数组
for (int j = l; j < left.length; j++) {
a[i++]=left[j];
}
}
return a;
};
3、测试结果
拿归并排序和希尔排序比较一下
可以看到在小数据量的情况下,归并排序比希尔排序快很多
原数组{3,44,38,5,47,15,36,26,27,2,46,4,19,50,48}
MergeSort排序时间为:64400
[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
ShellSort排序时间为:401500
[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]