归并排序

  归并排序,顾名思义,就是通过将两个有序的序列合并为一个大的有序的序列的方式来实现排序。合并排序是一种典型的分治算法:首先将序列分为两部分,然后对每一部分进行循环递归的排序,然后逐个将结果进行合并。

  归并排序的时间复杂度为O(nlgn),这个是我们之前的选择排序和插入排序所达不到的。它是一种稳定性排序,也就是相等的元素在序列中的相对位置在排序前后不会发生变化。他的唯一缺点是,需要利用额外的N的空间来进行排序。

原理

  合并排序依赖于合并操作,即将两个已经排序的序列合并成一个序列,具体的过程如下:

  1. 申请空间,使其大小为两个已经排序序列之和,然后将待排序数组复制到该数组中。
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较复制数组中两个指针所指向的元素,选择相对小的元素放入到原始待排序数组中,并移动指针到下一位置
  4. 重复步骤3直到某一指针达到序列尾
  5. 将另一序列剩下的所有元素直接复制到原始数组末尾

  该过程实现如下,注释比较清楚:

  1. private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {
  2. for (int k = lo; k <= hi; k++) {
  3. aux[k] = a[k];
  4. }
  5.  
  6. int i = lo, j = mid+1;
  7. for (int k = lo; k <= hi; k++) {
  8. if (i > mid) a[k] = aux[j++];
  9. else if (j > hi) a[k] = aux[i++];
  10. else if (less(aux[j], aux[i])) a[k] = aux[j++];
  11. else a[k] = aux[i++];
  12. }
  13. }

  下图是使用以上方法将EEGMR和ACERT这两个有序序列合并为一个大的序列的过程演示:

实现

  合并排序有两种实现,一种是至上而下(Top-Down)合并,一种是至下而上 (Bottom-Up)合并,两者算法思想差不多,这里仅介绍至上而下的合并排序。至上而下的合并是一种典型的分治算法(Divide-and-Conquer),如果两个序列已经排好序了,那么采用合并算法,将这两个序列合并为一个大的序列也就是对大的序列进行了排序。

  首先我们将待排序的元素均分为左右两个序列,然后分别对其进去排序,然后对这个排好序的序列进行合并,代码如下:

  1. public class Merge {
  2. private Merge() { }
  3. // stably merge a[lo .. mid] with a[mid+1 ..hi] using aux[lo .. hi]
  4. private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {
  5. for (int k = lo; k <= hi; k++) {
  6. aux[k] = a[k];
  7. }
  8. int i = lo, j = mid+1;
  9. for (int k = lo; k <= hi; k++) {
  10. if (i > mid) a[k] = aux[j++];
  11. else if (j > hi) a[k] = aux[i++];
  12. else if (less(aux[j], aux[i])) a[k] = aux[j++];
  13. else a[k] = aux[i++];
  14. }
  15. }
  16. private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {
  17. if (hi <= lo) return;
  18. int mid = lo + (hi - lo) / 2;
  19. sort(a, aux, lo, mid);
  20. sort(a, aux, mid + 1, hi);
  21. merge(a, aux, lo, mid, hi);
  22. }
  23. public static void sort(Comparable[] a) {
  24. Comparable[] aux = new Comparable[a.length];
  25. sort(a, aux, 0, a.length-1);
  26. assert isSorted(a);
  27. }
  28. private static boolean less(Comparable v, Comparable w) {
  29. return v.compareTo(w) < 0;
  30. }
  31. }

  以排序一个具有15个元素的数组为例,其调用堆栈为:

  我们单独将Merge步骤拿出来,可以看到合并的过程如下:

图示及动画

  如果以排序38,27,43,3,9,82,10为例,将合并排序画出来的话,可以看到如下图:

  下图是合并排序的可视化效果图:

  对6 5 3 1 8 7 24 进行合并排序的动画效果如下:

  下图演示了合并排序在不同的情况下的效率:

改进

  对合并排序进行一些改进可以提高合并排序的效率。

  1. 当划分到较小的子序列时,通常可以使用插入排序替代合并排序

  对于较小的子序列(通常序列元素个数为7个左右),我们就可以采用插入排序直接进行排序而不用继续递归了),算法改造如下:

  1. private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {
  2. if (hi <= lo) return;
  3. int mid = lo + (hi - lo) / 2;
  4. if (hi-lo+1<阈值) selectionSort(a);
  5. sort(a, aux, lo, mid);
  6. sort(a, aux, mid + 1, hi);
  7. merge(a, aux, lo, mid, hi);
  8. }

  2. 如果已经排好序了就不用合并了

  当已排好序的左侧的序列的最大值<=右侧序列的最小值的时候,表示整个序列已经排好序了。

  算法改动如下:

  1. private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {
  2. if (hi <= lo) return;
  3. int mid = lo + (hi - lo) / 2;
  4. if (hi-o+1<阈值) selectionSort(a);
  5. sort(a, aux, lo, mid);
  6. sort(a, aux, mid + 1, hi);
  7. if(a[mid]<a[mid+1])return;
  8. merge(a, aux, lo, mid, hi);
  9. }

  3. 并行化

  分治算法通常比较容易进行并行化,在浅谈并发与并行这篇文章中已经展示了如何对快速排序进行并行化(快速排序在下一篇文章中讲解),合并排序一样,因为我们均分的左右两侧的序列是独立的,所以可以进行并行,值得注意的是,并行化也有一个阈值,当序列长度小于某个阈值的时候,停止并行化能够提高效率,这些详细的讨论在浅谈并发与并行这篇文章中有详细的介绍了,这里不再赘述。

自底向上的归并排序

代码实现

  1. public class MergeBU {
  2. private MergeBU() { }
  3. private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {
  4. for (int k = lo; k <= hi; k++) {
  5. aux[k] = a[k];
  6. }
  7. int i = lo, j = mid+1;
  8. for (int k = lo; k <= hi; k++) {
  9. if (i > mid) a[k] = aux[j++];
  10. else if (j > hi) a[k] = aux[i++];
  11. else if (less(aux[j], aux[i])) a[k] = aux[j++];
  12. else a[k] = aux[i++];
  13. }
  14.  
  15. }
  16. public static void sort(Comparable[] a) {
  17. int N = a.length;
  18. Comparable[] aux = new Comparable[N];
  19. for (int n = 1; n < N; n = n+n) {
  20. for (int i = 0; i < N-n; i += n+n) {
  21. int lo = i;
  22. int m = i+n-1;
  23. int hi = Math.min(i+n+n-1, N-1);
  24. merge(a, aux, lo, m, hi);
  25. }
  26. }
  27. }
  28. private static boolean less(Comparable v, Comparable w) {
  29. return v.compareTo(w) < 0;
  30. }
  31. }

用途

  合并排序和快速排序一样都是时间复杂度为nlgn的算法,但是和快速排序相比,合并排序是一种稳定性排序,也就是说排序关键字相等的两个元素在整个序列排序的前后,相对位置不会发生变化,这一特性使得合并排序是稳定性排序中效率最高的一个。在Java中对引用对象进行排序.

基本排序算法<二>的更多相关文章

  1. 排序算法二:归并排序(Merge sort)

    归并排序(Merge sort)用到了分治思想,即分-治-合三步,算法平均时间复杂度是O(nlgn). (一)算法实现 private void merge_sort(int[] array, int ...

  2. Java排序算法(二):简单选择排序

    [基本思想] 在要排序的一组数中.选出最小的一个数与第一个位置的数交换:然后在剩下的数中再找出最小的与第二个位置的数交换,如此循环至倒数第二个数和最后一个数比較为止. 算法关键:找到最小的那个数.并用 ...

  3. 排序算法二(时间复杂度为O(N*logN))

    快速排序: 1 package test; public class QuickSort { // 快速排序 public void quickSort(int s[], int l, int r) ...

  4. python 实现排序算法(二)-合并排序(递归法)

    #!/usr/bin/env python2 # -*- coding: utf-8 -*- """ Created on Tue Nov 21 22:28:09 201 ...

  5. python实现排序算法二:归并排序

    ##归并排序 ##基本思想:对于两个排好序的数组A和B,逐一比较A和B的元素,将较小值放入数组C中,当A或者B数组元素查询完后,将A或者B剩余的元素直接添加到C数组中,此时C数组即为有序数组,这就是归 ...

  6. java讲讲几种常见的排序算法(二)

    java讲讲几种常见的排序算法(二) 目录 java讲讲几种常见的排序算法(一) java讲讲几种常见的排序算法(二) 堆排序 思路:构建一个小顶堆,小顶堆就是棵二叉树,他的左右孩子均大于他的根节点( ...

  7. java讲讲几种常见的排序算法

    java讲讲几种常见的排序算法(一) 目录 java讲讲几种常见的排序算法(一) java讲讲几种常见的排序算法(二) 以数组array={6,3,20,8,15,1}为例 冒泡排序 思路:从第0个到 ...

  8. 排序算法三:Shell插入排序

    排序算法三:Shell插入排序 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 引言 在我的博文<"主宰世界"的10种算法短评> ...

  9. 必须知道的八大种排序算法【java实现】(二) 选择排序,插入排序,希尔算法【详解】

    一.选择排序 1.基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换:然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止. 2.实例 3.算法 ...

随机推荐

  1. linux—粘滞位的设置

    粘滞位(Stickybit),或粘着位,是Unix文件系统权限的一个旗标.最常见的用法在目录上设置粘滞位,如此以来,只有目录内文件的所有者或者root才可以删除或移动该文件.如果不为目录设置粘滞位,任 ...

  2. Win10 Bash/WSL调试Linux环境下的.NET Core应用程序

    一.简介 使用过Mac OS的程序员都知道,在Mac Book Pro上写程序是一件比较爽的事儿,作为dotneter,我们都比较羡慕Mac系统的环境,比如命令行,当然设备也是挺漂亮的. 在新的Win ...

  3. 模式识别与机器学习—bagging与boosting

    声明:本文用到的代码均来自于PRTools(http://www.prtools.org)模式识别工具箱,并以matlab软件进行实验. (1)在介绍Bagging和Boosting算法之前,首先要简 ...

  4. 跨站的艺术-XSS Fuzzing 的技巧

    作者 | 张祖优(Fooying)  腾讯云 云鼎实验室 对于XSS的漏洞挖掘过程,其实就是一个使用Payload不断测试和调整再测试的过程,这个过程我们把它叫做Fuzzing:同样是Fuzzing, ...

  5. oracle索引(转)

    引,索引的建立.修改.删除 2007-10-05 13:29 来源: 作者: 网友评论 0 条 浏览次数 2986 索引索引是关系数据库中用于存放每一条记录的一种对象,主要目的是加快数据的读取速度和完 ...

  6. 小练习,判断X的奇偶性

    package lianxi1; public class text { public static void main(String[] args) { ; ==) { System.out.pri ...

  7. 关于CO中的processRequest和processFormRequest的区别

    在OAF开发中会有许多的CO,而一般情况下CO中的有两个基本的方法那就是processRequest和processFormRequest,processRequest是页面执行初始化的时候执行的方法 ...

  8. *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ViewController > setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key

    *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ViewController > ...

  9. Java进阶之网络编程

    网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编 ...

  10. 老李分享:开发python的unittest结果输出样式

    老李分享:开发python的unittest结果输出样式   Python的unittest结果命令行输出,格式比较乱.为了提高格式输出的可读性,实现可以不同的颜色标识.所以准备扩展Python的un ...