从2路归并到k路归并(三)

593 阅读3分钟

上一篇文章介绍了二路归并的实现,这篇文章在二路归并的基础上,介绍如何完成三路归并.然后我们结合二路和三路归并的实现,归纳出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路的实现代码