双调排序程序解析(可对随便长度的序列排序)

双调排序程序解析(可对任意长度的序列排序)

上一篇文章中给出了可对任意长度的序列进行排序的双调排序的程序实现,这篇文章将对程序进行解析,通过与归并排序的对比有助于对程序的理解。

首先回顾一下归并排序的程序实现:

public class MergeSorter
{
    private int[] a;
    private final static boolean ASCENDING=true;    // sorting direction

    public void sort(int[] a)
    {
        this.a=a;
        mergeSort(0, a.length, ASCENDING);
    }

    private void mergeSort(int lo, int n, boolean dir)
    {
        if (n>1)
        {
            int m=n/2;
            mergeSort(lo, m, dir);
            mergeSort(lo+m, n-m, dir);
            merge(lo, n, dir);
        }
    }

    private void merge(int lo, int n, boolean dir)
    {
        if (n>1)
        {
        	  int m=n/2;
        	  
        	  for (int i=lo; i<lo+m; i++)
        	  {
        	  	  if (dir==(a[i]>a[lo+m]))
        	  	  {
        	  	  	  exchange(i,lo+m);
        	  	  	  
        	  	  	  int k=lo+m;
        	  	  	  while (k<lo+n-1 && (dir==a[k]>a[k+1]))
        	  	  	      exchange(k, k+1);
        	  	  }
        	  }
        }
    }

    private void exchange(int i, int j)
    {
        int t=a[i];
        a[i]=a[j];
        a[j]=t;
    }
    
}
再来看双调排序的程序实现:

public class BitonicSorterForArbitraryN implements Sorter
{
    private int[] a;
    private final static boolean ASCENDING=true;    // sorting direction

    public void sort(int[] a)
    {
        this.a=a;
        bitonicSort(0, a.length, ASCENDING);
    }

    private void bitonicSort(int lo, int n, boolean dir)
    {
        if (n>1)
        {
            int m=n/2;
            bitonicSort(lo, m, !dir);
            bitonicSort(lo+m, n-m, dir);
            bitonicMerge(lo, n, dir);
        }
    }

    private void bitonicMerge(int lo, int n, boolean dir)
    {
        if (n>1)
        {
            int m=greatestPowerOfTwoLessThan(n);
            for (int i=lo; i<lo+n-m; i++)
                compare(i, i+m, dir);
            bitonicMerge(lo, m, dir);
            bitonicMerge(lo+m, n-m, dir);
        }
    }

    private void compare(int i, int j, boolean dir)
    {
        if (dir==(a[i]>a[j]))
            exchange(i, j);
    }

    private void exchange(int i, int j)
    {
        int t=a[i];
        a[i]=a[j];
        a[j]=t;
    }

    private int greatestPowerOfTwoLessThan(int n)
    {
        int k=1;
        while (k<n)
            k=k<<1;
        return k>>1;
    }

}
两个程序都是递归的。都是首先不断的二分,直到每组只剩下一个元素,然后开始归并。

不同之处在于,归并排序是按照原来的二分归并的,把原来二分后的两个子序分别排序后列合并成一个有序序列。而双调排序归并时并不是按照原来二分的界限来归并的,而是以不大于n的最大2的幂次方为界限,把2^k~n的元素分别与0~(n-2^k)的元素比较,然后再分别在0~2^k和2^k~n这两段上应用比较网络。

双调排序难以理解的地方就在于这个归并的过程,假设我们要把长度为n的序列a升序排列,由于二分时是把前n/2的序列降序排列后n/2的序列升序排列的(见代码17、18行),而n-2^k < n/2,所以前n-2^k 和后n-2^k个元素都大于中间的元素,当前n-2^k个元素和后n-2^k个元素比较时(代码27~29行),会把序列中最大的n-2^k个元素放到最后n-2^k个位置上,也就是说比较后,2^k~n的元素都比0~2^k的元素大,这样在分别对这两段用同样的方法归并(代码30、31行),最终得到完整的升序序列。

 以6个元素的序列为例,当前3个元素已经降序排好,后3个元素已经升序排好(递归到底时是从1个元素开始返回的,所以对6个元素归并时前后3个元素已经排好序,见代码17~19行),这个以4(不大于6的最大2的幂次方)为界限,分别让第1个和第5个、第2个和第6个元素比较,使得这6个元素中最大的两个处于第5、6个位置上,然后再分别对前4个和后2个元素按此方法归并。