上一篇文章介绍了二路归并的实现,这篇文章在二路归并的基础上,介绍如何完成三路归并.然后我们结合二路和三路归并的实现,归纳出k路归并
在介绍三路归并之前,我们先回顾一下二路归并的实现,为了满足不同数据类型的归并排序,我们把数组类型声明为Comparable,这样用户可以为自己的类型实现自定义的比较规则
//入口函数 初始化辅助数组
public static void sort(Comparable[] a){
Comparable[] aux = new Comparable[a.length];
sort(a,aux,0,a.length-1);
}
//把大的数组递归分解成小的数据,等有序后合并
private static void sort(Comparable[] a ,Comparable[] aux,int lo,int hi){
if(hi <= lo) return;
//分成2路
int mid = lo + (hi - lo) / 2;
//递归分解左数组
sort(a, aux,lo, mid);
//递归分解有数组
sort(a, aux,mid + 1, hi);
//归并有序的两个数组
merge(a, aux,lo, mid, hi);
}
/**
* 合并两个有序数组,通过三个索引来表示两个有序数组
* @param a 待排序数组
* @param aux 辅助数组
* @param lo 第一个数组的起始索引
* @param mid 第一数组的结束索引
* @param hi 第二个数组的结束索引
*/
private static void merge(Comparable[] a,Comparable[] aux, int lo,int mid, int hi) {
int i = lo,j = mid + 1;
for(int k = lo; k<=hi ;k++){
aux[k] = a[k];
}
for(int k = lo; k<= hi; k++){
if(i > mid) a[k] = aux[j++];
else if(j > hi) a[k] = aux[i++];
else if(less(aux[j],aux[i])) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
如果觉得三个索引还不够明显的话,可以用四个索引,这样更清楚,也方便我们归纳k路轨并
/**
* 归并两个有序数组
* @param a 待排序数组
* @param aux 辅助数组
* @param fst_lo 第一个有序数组的起始索引
* @param fst_hi 第一个有序数组的结束索引
* @param sec_lo 第二个有序数组的起始索引
* @param sec_hi 第二个有序数组的结束索引
*/
private static void merge(Comparable[] a,Comparable[] aux, int fst_lo,int fst_hi,int sec_lo, int sec_hi) {
//StdOut.printf("lo = %d, mid = %d, hi = %d\n",lo, mid, hi);
int i = fst_lo,j = sec_lo;
for(int k = fst_lo; k<=sec_hi ;k++){
aux[k] = a[k];
}
for(int k = fst_lo; k<= sec_hi; k++){
if(i > fst_hi) a[k] = aux[j++];
else if(j > sec_hi) a[k] = aux[i++];
else if(less(aux[j],aux[i])) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
当然在sort方法中的merge也要改成下面的调用方式merge(a,aux,lo,mid,mid+1,hi)
此时大家可以试着基于二路归并写一下三路归并,一方面检验你对二路归并的理解,同时也便于你看懂k路归并代码.
以下是三路归并代码
public static void sort(Comparable[] a){
int len=a.length;
if(len<=1) return;
int lo=0;
int hi=len-1;
Comparable[] aux = new Comparable[len];
sort(a,aux,lo,hi);
}
private static void sort(Comparable[] a,Comparable[] aux,int lo,int hi){
if(hi <=lo) return;
//分成三路(三个子数组), 并得到每路的长度
int step=(hi-lo)/3+1;
//第二路起始索引
int second=lo+step;
//第三路起始索引
int third=second+step;
//下面的判断用于防止子数组在递归时长度小于3,而导致索引越界异常
if(second>hi){
sort(a,aux,lo,hi);
merge(a,aux,lo,lo,lo,hi);
}
else if(third>hi){
sort(a,aux,lo,second-1);
sort(a,aux,second,hi);
merge(a,aux,lo,second-1,hi-1,hi);
}
else{
sort(a,aux,lo,second-1);
sort(a,aux,second,third-1);
sort(a,aux,third,hi);
merge(a,aux,lo,second-1,third-1,hi);
}
}
public static void merge(Comparable[] a, Comparable[] aux,int lo,int second,int third,int hi){
int i=lo,j=second+1,k=third+1;
for(int x=lo;x<=hi;x++)
aux[x]=a[x];
for(int x=lo;x<=hi;x++){
//判断某路元素是否用完
if(i>second){
if(j>third)
a[x]=aux[k++];
else if(k>hi)
a[x]=aux[j++];
else
a[x]= Sort.less(aux[j],aux[k])?aux[j++]:aux[k++];
}
else if(j>third){
if(i>second)
a[x]=aux[k++];
else if(k>hi)
a[x]=aux[i++];
else
a[x]=Sort.less(aux[i],aux[k])?aux[i++]:aux[k++];
}
else if(k>hi){
if(i>second)
a[x]=aux[j++];
else if(j>third)
a[x]=aux[i++];
else
a[x]=Sort.less(aux[i],aux[j])?aux[i++]:aux[j++];
}
else{
int min = min(aux,i,j,k);
if(min==i) a[x]=aux[i++];
else if(min==j) a[x]=aux[j++];
else a[x]=aux[k++];
}
}
}
private static int min(Comparable[] aux,int i,int j,int k){
int min = Sort.less(aux[i],aux[j])?i:j;
return Sort.less(aux[min],aux[k])?min:k;
}
看着代码挺多,起始很简单,跟二路归并结构一样:
- 入口函数
sort:用户真正调用的就是这个函数,它接受待排序数组,初始化辅助数组,然后调用递归sort来进行分解并合并有序子数组 - 递归
sort:将大数组递归分解成3路有序子数组,并利用merge得到三路合并后的有序子数组 - 归并
merge:将三个有序子数组合并成一个更大的子数组
上面的过程不负责,就是判断条件稍微繁琐一些,大家耐住性子看肯定能看懂,一些关键点的我已经加了注释,看不懂可以留言
最后附上上述过程用到的辅助类
public final class Sort {
public static boolean less(Comparable a,Comparable b){
return a.compareTo(b) < 0;
}
public static void exchange(Comparable[] a, int i,int j){
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
下一篇文章也是本系列的最后一篇,我将结合二路三路的实现展示k路的实现代码