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

678 阅读3分钟

最近在看"算法"第四版,看到了归并排序时,把这节的练习做了一遍,感觉有些收获,拿出来给大家分享一下,也算抛砖引玉.

将归并排序之前,请大家先考虑一个问题,如何合并和两个有序数组?,比如,这里有两个数组a和b,它们都是有序的

a:2,3,4
b:1,3,5,7

如何合并这两个数组呢? 既然数组有序,我们就充分利用数组的有序性,设置两个指针i,j,分别指向数组的首地址,然后比较指针对应的值a[i]和b[j],每次我们总是取较小值,放入最终数组内.因为两个数组有序,每次都把较小值放入最终数组,那么比完以后最终数组自然有序.

注意,不管两个数组的元素个数和大小如何,在某一时刻,只可能有一个数组元素先用完,不会两个同时用完,为什么?因为根据上面的过程,肯定是数组最大值较小的先用完.即使两个数组元素大小和个数都一样,那么也只会有一个数组先用完.

我们绘制一下这个过程,最开始,i=0,j=0,他们都指向数组的首地址,此时两数较小值是1,所以把b[j]取出放到c中,然后j移到下一个位置.

a:2,3,4
  i
b:1,3,5,7
  j
c:
a:2,3,4
  i
b:1,3,5,7
    j
c:1

此时i=0,j=1,继续比较,2<3,2放入c,i移动到下一位值

a:2,3,4
    i
b:1,3,5,7
    j
c:1,2

此时i=1,j=1,因为此时 a[i]==a[j],所以 a[i]<b[j]不成立,取出b[j]放入c,j移动到下一位值

a:2,3,4
    i
b:1,3,5,7
      j
c:1,2,3

此时j=2,i=1,继续比较,把3放入c中,i移到下一位值

a:2,3,4
      i
b:1,3,5,7
      j
c:1,2,3,3

此时,j=2,i=2,继续比较,把4放入c中,i=4,超出a的索引范围,表示a元素已经用完

a:2,3,4
        i
b:1,3,5,7
      j
c:1,2,3,3,4

因为a元素用完,所以我们只需要依次取出b中剩余元素放入c中,最终得到的c如下:

a:2,3,4
        i
b:1,3,5,7
          j
c:1,2,3,3,4,5,7

将上面的过程转换成代码

  public static int[] merge(int[] a,int[] b){
    int len1=a.length;
    int len2=b.length;
    int len=len1+len2;
    int[] c=new int[len];
    int i=0,j=0;
    for(int k=0;k<len;k++){
      //a数组元素用完,依次把b剩余元素放入c中
      if(i==len1) c[k]=b[j++];
      //b数组元素用完,依次把a剩余元素放入c中
      else if(j==len2) c[k]=a[i++];
      //如果两个数组都没用完,取较小者放入c中
      else if(a[i]<b[j]) c[k]=a[i++];
      else c[k]=b[j++];
    }
    return c;
  }

上面的过程很简单,merge方法就是归并.只要把两个有序的数组传给merge,它就会返回一个有序的和数组.那问题来了,如何把一个无序的数组变成两个有序的子数组,以保证merge的正常运行呢?