数据结构和算法(四)归并排序

归并排序

归并排序: 将两个有序的数组归并成一个更大的有序数组。要将一个数组排序,可以先(递归的)将它分成两半分别排序,然后把结果归并成一个数组。归并排序最吸引人的性质是它能保证将任意长度为N的数组排序所需的时间和NlogN成正比。它的主要缺点是它所需的额外空间和N成正比。

原地归并的抽象方法

实现归并的一种直接了当的方法是将两个不同的有序数组原地归并到第三个数组中,两个数组中的元素都应该实现了Comparable接口。实现方法很简单,创建一个适当大小的数组然后将两个输入数组中的元素一个一个从小到大放入这个数组。但是用归并实现一个很大的数组排序时,需要进行很多次排序,并且每次归并都要创建一个新数组来存储排序的结果,这会带来很多问题。所以更希望有一种能够在原地归并的方法。

 1 static Comparable[] array = new Comparable[10000];
 2 
 3     public static void merge(Comparable[] a, int start, int mid, int end) {
 4         int i = start;
 5         int j = mid + 1;
 6         //将a中的值放入数组array中
 7         for (int k = start; k <= end; k++) {
 8             array[k] = a[k];
 9         }
10         for (int k = start; k <= end; k++) { //归并到a数组中
11             if (j > end) {
12                 a[k] = array[i++];
13             } else if (i > mid) {
14                 a[k] = array[j++];
15             } else if (array[j].compareTo(array[i]) < 0) {
16                 a[k] = array[j++];
17             } else {
18                 a[k] = array[i++];
19             }
20         }
21     }

 

上面的方法先将所有的元素复制到另一个数组中,然后再归并到原数组中。方法在归并时进行了四个判断:左半边用尽(取右半边元素),右半边用尽(取左半边元素),右半边的当前元素小于左半边的当前元素(取右半边元素),以及右半边的当前元素小于左半边的当前元素(取左半边元素)。代码中的if和其他else不能换,不然会出现数组越界。该方法将左边的数组与右边的数组进行比较,但并没有将左边与左边的进行比较,要比较需通过递归。

自顶向下的归并排序

自顶向下的归并排序是应用高效算法设计中分治思想的最典型的例子。如果能将两个子数组排序,它就能通过归并两个子数组来将整个数组排序。

 1  public static void sort(Comparable[] a) {
 2         array = new Comparable[a.length];//分配空间
 3         Merge.sort(a, 0, a.length - 1);
 4     }
 5 
 6     public static void sort(Comparable[] a, int start, int end) {
 7         if (start >= end) {
 8             return;
 9         }
10         int mid = (end + start) / 2;
11         Merge.sort(a, start, mid);//将左半边排序
12         Merge.sort(a, mid + 1, end);//将右半边排序
13         Merge.merge(a, start, mid, end);//归并结果
14     }

结论: 对于长度为N的任意数组,自顶向下的归并排序需要(NlgN)/2NlgN次比较。自顶向下的归并排序最多需要访问数组6NlgN次。

这告诉我们归并排序所需要的时间和NlgN成正比。

归并排序的主要缺点是辅助数组所使用的额外空间和N成正比

posted @ 2018-03-15 09:03  羽觞醉月  阅读(225)  评论(0编辑  收藏  举报