算法——排序之归并排序

归并排序

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

该算法是采用分治法(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 ms
Merge排序cost: 423 ms
Merge排序改良cost: 397 ms
普遍来说,改良的Merge排序比之前的Merge排序来说要快。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值