算法——排序之归并排序

来源:互联网 发布:仓库软件多少钱 编辑:程序博客网 时间:2024/06/05 02:14

归并排序

首先将数组分成两个(或两个以上)部分,分别进行排序,然后将这些有序的子数组归并起来。

该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 将已有序的子数组合并,得到完全有序的数组;即先使每个子数组有序,再使子数组间有序。


归并排序最吸引人的是它能够保证任意长度为N的数组排序的时间和NlogN成正比。缺点是他需要的额外的空间也和N成正比。


树状图更加容易理解:


设n为树的层数。第k层的节点是一个大小为2^n-k的数组,在归并的时候,我们需要比较左右两个部分的数组,所以需要比较2^n-k次。又因为第k层有2^k个节点。所以总共需要比较2^n次。

而树的高度为lgN,也就是n,所以最终需要比较NlogN次。

代码如下:

public static void sort(Comparable[] a) {  arrayTemp = new Comparable[a.length];  sort1(a, 0, a.length - 1);}private static void sort(Comparable[] a, int begin, int end) {  if (begin >= end)    return;  int mid = (begin + end) / 2;  sort(a, begin, mid); //先排序左边  sort(a, mid + 1, end); // 排序右边  merge(a, begin, mid, end); // 归并}private static void merge(Comparable[] a, int low, int mid, int high) {  int j = low;  int k = mid + 1;  for (int i = low; i <= high; i++) {    arrayTemp[i] = a[i];  }  for (int i = low; i <= high; i++) {    if (j >= mid + 1) { // 如果左边没了      a[i] = arrayTemp[k++];    } else if (k >= high + 1) { // 右边没了      a[i] = arrayTemp[j++];    } else if (less(arrayTemp[j], arrayTemp[k])) { // 左边的比较小      a[i] = arrayTemp[j++];    } else { // 右边的比较小      a[i] = arrayTemp[k++];    }  }}


我们可以对归并排序进行一些小小的优化:

改进1:对小规模子数组使用插入排序。当排序的时候,如果发现数组的规模已经比较小了,我们可以使用别的排序方法进行排序。对于小规模的数组,插入排序或者选择排序可能会比归并排序更加快。
改进2:再进行归并操作之前,我们可以比较左边子数组的最后一位a[mid]和右边子数组的第一位a[mid+1],如果发现a[mid]<a[mid+1]。那么数组已经是有序的了。不需要再进行归并。

改进代码:

public static void sort(Comparable[] a) {  arrayTemp = new Comparable[a.length];  sort1(a, 0, a.length - 1);}private static void sort(Comparable[] a, int begin, int end) {    // 改进1,数组规模小的时候,使用插入排序    if (end - begin <= 7) {      InsertSort.sort(a, begin, end + 1);      return;    }    int mid = (begin + end) / 2;    sort(a, begin, mid);    sort(a, mid + 1, end);    // 改进2    if (less(a[mid], a[mid+1])) return;    merge(a, begin, mid, end);  }private static void merge(Comparable[] a, int low, int mid, int high) {  int j = low;  int k = mid + 1;  for (int i = low; i <= high; i++) {    arrayTemp[i] = a[i];  }  for (int i = low; i <= high; i++) {    if (j >= mid + 1) { // 如果左边没了      a[i] = arrayTemp[k++];    } else if (k >= high + 1) { // 右边没了      a[i] = arrayTemp[j++];    } else if (less(arrayTemp[j], arrayTemp[k])) { // 左边的比较小      a[i] = arrayTemp[j++];    } else { // 右边的比较小      a[i] = arrayTemp[k++];    }  }}

我们来比较一下运行结果,顺便比较一下和希尔排序的速度:

public static void main(String[] args) {  final int NUM = 1000000;  Integer[] a1 = new Integer[NUM];  Integer[] a2 = new Integer[NUM];  Integer[] a3 = new Integer[NUM];  Integer[] a4 = new Integer[NUM];  for (int i = 0; i < NUM; i++) {    a1[i] = (int) (Math.random() * NUM);    a2[i] = a1[i];    a3[i] = a1[i];    a4[i] = a1[i];  }  long startTime;  long endTime;  startTime = System.currentTimeMillis(); // 获取开始时间  ShellSort.sort(a2);  assert isSorted(a2);  endTime = System.currentTimeMillis();  System.out.println("shell排序cost: " + (endTime - startTime) + " ms");    startTime = System.currentTimeMillis(); // 获取开始时间  MergeSort.sort1(a3);  assert isSorted(a3);  endTime = System.currentTimeMillis();  System.out.println("Merge排序cost: " + (endTime - startTime) + " ms");    startTime = System.currentTimeMillis(); // 获取开始时间  MergeSort.sort2(a4);  assert isSorted(a4);  endTime = System.currentTimeMillis();  System.out.println("Merge排序改良cost: " + (endTime - startTime) + " ms");}
运行结果:

shell排序cost: 1281 msMerge排序cost: 423 msMerge排序改良cost: 397 ms
普遍来说,改良的Merge排序比之前的Merge排序来说要快。

而归并排序相比较于希尔排序,又有了一个很大的提升。数据量比较大的时候,提升就越明显了。

原创粉丝点击