十大排序算法之归并排序

归并排序是一个O(n log n)排序算法,采取了分治的思想。将已有序的子序列合并,得到完全有序的序列,即使得每个子序列有序,再使得每个子序列段有序,最后整个序列有序。图示是自顶向下的归并排序过程,采取递归思想

归并排序合并过程是算法的关键过程,需要生成一个辅助数组,大小与需要合并的两部分子序列大小之和,临时装载需要排序的元素,用k指示排序后元素所在的位置,i是左序列标定点,j是右序列标定点;如果i指示元素小于j指示元素则将i指示元素填入k的位置上,k++,i++, j不动否则将j指示的元素填入k的位置上,k++, j++,i不动,还要考虑i超过中间标定点mid,此时应当将j指示及之后的元素依次赋给k及之后位置的元素,并且j已经超过序列右边界,此时应当将i指示及之后的元素依次赋给k及之后位置的元素。

以下是归并排序实现过程

//普通归并排序
public class MergeSort {
 
 //归并排序函数,调用私有递归归并函数
 public void mergeSort(int[] arr, int n) {
  //传入整个数组, 从arr[0...n-1]进行归并
  mergeSort(arr, 0, n - 1);
 }

 //归并排序函数
 private void mergeSort(int[] arr, int l, int r) {
  //如果左标定点大于等于右标定点,递归终止
  if (l >= r)
   return;
  //求取中间标定点
  int mid = (l + r) / 2;
  //左区间arr[l...mid]归并排序
  mergeSort(arr, l, mid);
  //右区间arr[l...mid]归并排序
  mergeSort(arr, mid + 1, r);
  //归并操作
  merge(arr, l, mid, r);
 }

 //归并函数
 private void merge(int[] arr, int l, int mid, int r) {
  //产生一个与需要归并数组一样大小的数组
  int[] aux = new int[r - l + 1];
  //将原数组原位置相应位置元素赋值给辅助数组
  for (int i = l; i <= r; i++)
   aux[i - l] = arr[i];
  
  //i, j 表示 k标定元素排序后所处位置
  int i = l, j = mid + 1;
  for (int k = l; k <= r; k++) {

   //当左标定i点超过中点mid时,让大于等于j标定点所对应的元素直接赋给arr对于k及k之后的元素
   if (i > mid) {
    arr[k] = aux[j - l];
    j++;
   }
   //当左标定j点超过右边界r时,让大于等于i标定点所对应的元素直接赋给arr对于k及k之后的元素
   else if (j > r) {
    arr[k] = aux[i - l];
    i++;
   }
   //当左标定点对应的元素小于右标定点元素,将i对应元素赋给arr k位置上元素,维护i,挪动一位置
   else if (aux[i - l] < aux[j - l]) {
    arr[k] = aux[i - l];
    i++;
   }
   //当右标定点对应的元素小于等于左标定点元素,将j对应元素赋给arr k位置上元素,维护j,往前挪动
   else {
    arr[k] = aux[j - l];
    j++;
   }
  }
 }
}

 针对100000数据量的随机数组、大量重复元素的数组、近乎有序的数组进行测试,以下是测试代码

import cn.zjut.util.SortTestUtil;

public class Main {

 public static void main(String[] args){
  int n = 100000;
  System.out.println("Test for Random Array, size = " + n + ", random range [0, " + n + ']');
  int[] arr1 = SortTestUtil.generateRandomArray(n, 0, n); 
  SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);

  System.out.println("--------------------------------");
  
  System.out.println("Test for Random Array, size = " + n + ", random range [0, " + 10 + ']');
  arr1 = SortTestUtil.generateRandomArray(n, 0, 10); 
  SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);

  System.out.println("--------------------------------");
  
  int swapTime = 100;
     System.out.println("Test for Random Nearly Ordered Array, size = " + n + ", swap time = " + swapTime);
     arr1 = SortTestUtil.generateNearlyOrderedArray(n, swapTime);
     SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
 }
}

以下是测试结果

 由结果可知针对10万数据量归并排序效果依然很好,对于有大量重复元素的数据和近乎有序的数据效果也比较好。其实对于上述排序实现还有有优化地方,当子序列比较小直接用插入排序实现,当arr[mid] < arr[j]表明两部分子序列合起来已经有序无需去比较合并,以下是优化后的归并排序实现过程

//归并排序改进类, 提升对近乎有序的数组排序的性能, 稍微降低对无序数组的性能
public class MergeSortImprove {
 public void mergeSort(int[] arr, int n) {
  mergeSort(arr, 0, n - 1);
 }

 private void mergeSort(int[] arr, int l, int r) {
  //对于小数组
  if (r - l <= 15) {
   new InsertSort().insertSort(arr, l, r);
   return;
  }
  int mid = (l + r) / 2;
  mergeSort(arr, l, mid);
  mergeSort(arr, mid + 1, r);
     // 对于arr[mid] <= arr[mid+1]的情况,不进行merge,因为这时两个区间合起来已经有序,无需归并
  if(arr[mid] > arr[mid + 1])
   merge(arr, l, mid, r);
 }

 private void merge(int[] arr, int l, int mid, int r) {
  int[] aux = new int[r - l + 1];
  for (int i = l; i <= r; i++)
   aux[i - l] = arr[i];
  int i = l, j = mid + 1;
  for (int k = l; k <= r; k++) {

   if (i > mid) {
    arr[k] = aux[j - l];
    j++;
   } else if (j > r) {
    arr[k] = aux[i - l];
    i++;
   } else if (aux[i - l] < aux[j - l]) {
    arr[k] = aux[i - l];
    i++;
   } else {
    arr[k] = aux[j - l];
    j++;
   }
  }
 }
}

以下是优化前归并排序和优化后归并排序的测试代码,测试数据量是1000000,有随机数组、大量重复元素数组、近乎有序的数组。

import cn.zjut.util.SortTestUtil;

public class Main {

 public static void main(String[] args){
  int n = 1000000;
  System.out.println("Test for Random Array, size = " + n + ", random range [0, " + n + ']');
  int[] arr1 = SortTestUtil.generateRandomArray(n, 0, n); 
  int[] arr2 = SortTestUtil.copyIntArray(arr1, n);
  SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
  SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);

  System.out.println("--------------------------------");
  
  System.out.println("Test for Random Array, size = " + n + ", random range [0, " + 10 + ']');
  arr1 = SortTestUtil.generateRandomArray(n, 0, 10);
  arr2 = SortTestUtil.copyIntArray(arr1, n);
  SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
  SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);

  System.out.println("--------------------------------");
  
  int swapTime = 10;
     System.out.println("Test for Random Nearly Ordered Array, size = " + n + ", swap time = " + swapTime);
     arr1 = SortTestUtil.generateNearlyOrderedArray(n, swapTime);
     arr2 = SortTestUtil.copyIntArray(arr1, n);
     SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
  SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);
 }
}

以下是两者的测试结果

从结果可知,优化效果对于近乎有序的数组比较明前,其他类型数据效果相差不大。归并排序还有另一种实现的过程自底向上实现合并过程,直接对一个个小序列实现合并,子序列逐步增大,最终将原序列实现排序,以下是实现代码

//自底向上归并排序类
public class MergeSortBottomUp {
 //归并排序函数
 public void mergeSort(int[] arr, int n) {
  
  InsertSort is = new InsertSort();
  //优化1
  for (int i = 0; i < n; i += 16)
   is.insertSort(arr, i, Math.min(i + 15, n - 1));

  for (int sz = 16; sz <= n; sz += sz)
   for (int i = 0; i < n - sz; i += sz + sz)
    //优化2
    if (arr[i + sz - 1] > arr[i + sz])
     merge(arr, i, i + sz - 1, Math.min(i + sz + sz - 1, n - 1));
 }

 private void merge(int[] arr, int l, int mid, int r) {
  int[] aux = new int[r - l + 1];
  for (int i = l; i <= r; i++)
   aux[i - l] = arr[i];
  int i = l, j = mid + 1;
  for (int k = l; k <= r; k++) {

   if (i > mid) {
    arr[k] = aux[j - l];
    j++;
   } else if (j > r) {
    arr[k] = aux[i - l];
    i++;
   } else if (aux[i - l] < aux[j - l]) {
    arr[k] = aux[i - l];
    i++;
   } else {
    arr[k] = aux[j - l];
    j++;
   }
  }
 }
}

以下是测试代码

import cn.zjut.util.SortTestUtil;

public class Main {

 public static void main(String[] args){
  int n = 1000000;
  System.out.println("Test for Random Array, size = " + n + ", random range [0, " + n + ']');
  int[] arr1 = SortTestUtil.generateRandomArray(n, 0, n); 
  int[] arr2 = SortTestUtil.copyIntArray(arr1, n);
  int[] arr3 = SortTestUtil.copyIntArray(arr1, n);
  SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
  SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);
  SortTestUtil.testSort("MergeSortBottomUp", "cn.zjut.sort.MergeSortBottomUp", "mergeSort", arr3, n);

  System.out.println("--------------------------------");
  
  System.out.println("Test for Random Array, size = " + n + ", random range [0, " + 10 + ']');
  arr1 = SortTestUtil.generateRandomArray(n, 0, 10);
  arr2 = SortTestUtil.copyIntArray(arr1, n);
  arr3 = SortTestUtil.copyIntArray(arr1, n);  
  SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
  SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);
  SortTestUtil.testSort("MergeSortBottomUp", "cn.zjut.sort.MergeSortBottomUp", "mergeSort", arr3, n);

  System.out.println("--------------------------------");
  
  int swapTime = 10;
     System.out.println("Test for Random Nearly Ordered Array, size = " + n + ", swap time = " + swapTime);
     arr1 = SortTestUtil.generateNearlyOrderedArray(n, swapTime);
     arr2 = SortTestUtil.copyIntArray(arr1, n);
     arr3 = SortTestUtil.copyIntArray(arr1, n);
     SortTestUtil.testSort("MergeSort", "cn.zjut.sort.MergeSort", "mergeSort", arr1, n);
  SortTestUtil.testSort("MergeSortImprove", "cn.zjut.sort.MergeSortImprove", "mergeSort", arr2, n);
  SortTestUtil.testSort("MergeSortBottomUp", "cn.zjut.sort.MergeSortBottomUp", "mergeSort", arr3, n);
 }
}

以下是测试结果

 自底向上的归并排序效果与自顶向下的归并排序效果差不多,在统计意义上自顶向下比自底向上归并排序效果好一些,但是自底向上的归并排序可以用于链表的排序过程,因为其排序过程没有使用索引来获取元素。

以上整个过程就是归并排序的所有实现过程。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值