归并排序(Merge Sort)与快速排序思想类似:将待排序数据分成两部分,继续将两个子部分进行递归的归并排序;然后将已经有序的两个子部分进行合并,最终完成排序。其时间复杂度与快速排序均为O(nlogn),但是归并排序除了递归调用间接使用了辅助空间栈,还需要额外的O(n)空间进行临时存储。从此角度归并排序略逊于快速排序,但是归并排序是一种稳定的排序算法,快速排序则不然。

所谓稳定排序,表示对于具有相同值的多个元素,其间的先后顺序保持不变。对于基本数据类型而言,一个排序算法是否稳定,影响很小,但是对于结构体数组,稳定排序就十分重要。例如对于student结构体按照关键字score进行非降序排序:

  1. // A structure data definition
  2. typedef struct __Student
  3. {
  4. char name[16];
  5. int score;
  6. }Student;
  7. // Array of students
  8. name : A B C D
  9. score: 80 70 75 70
  10.  
  11. Stable sort in ascending order:
  12. name : B D C A
  13. score: 70 70 75 80
  14.  
  15. Unstable sort in ascending order:
  16. name : D B C A
  17. score: 70 70 75 80

其中稳定排序可以保证B始终在D之前;而非稳定排序,则无法保证。

1)数组的归并排序

归并排序的思想实际上是一种分治法,即将待排序数据分成两部分,分别对两部分排序,然后将这两部分合并。下面以非降序排序为例:

  1. // Split arr[] into two parts [begin,mid), [mid,end)
  2. // and using merge_core to merge this two parts
  3. // Total Time O(nlogn)
  4. void merge_sort(int arr[], int begin, int end)
  5. {
  6. if (end-begin < 2) return;
  7. int mid = (begin+end)>>1;
  8. merge_sort(arr,begin,mid);
  9. merge_sort(arr,mid,end);
  10. merge_core(arr,begin,mid,end);
  11. } // Time O(logn)

其中arr[]为待排序数组,对于一个长度为N的数组,直接调用merge_sort(arr,0,N);则可以排序。

归并排序总体分为两步,首先分成两部分,然后对每个部分进行排序,最后合并。当然也可以分成三部分或其他,然而通常是分成两部分,因此又称为二路归并。merge_core可以将两个有序数组合并成一个,具体操作如图所示:

  1. /* merge core: combine two parts which are sorted in ascending order
  2. * arr[]: ..., 1, 4, 8, 2, 3, 7, 9, ...
  3. * begin__| |__mid |__end
  4. * part1: 1, 4, 8 part2: 2, 3, 7, 9
  5. *
  6. * combination:
  7. * part1:[1] [4] [8]
  8. * part2: | [2] [3] | [7] | [9]
  9. * | | | | | | |
  10. * tmp :[1] [2] [3] [4] [7] [8] [9]
  11. *
  12. * at last, copyback tmp to arr[begin,end)
  13. * */

合并的前提是,两个数组已经是有序的。其代码为:

  1. void merge_core(int arr[], int begin, int mid, int end)
  2. {
  3. int i=begin, j=mid, k=0;
  4. int *tmp = (int*)malloc(sizeof(int)*(end-begin));
  5. for(; i<mid && j<end; tmp[k++]=(arr[i]<arr[j]?arr[i++]:arr[j++]));
  6. for(; i<mid; tmp[k++]=arr[i++]);
  7. for(; j<end; tmp[k++]=arr[j++]);
  8. for(i=begin, k=0; i<end; arr[i++]=tmp[k++]);
  9. free(tmp);
  10. } // Time O(n), Space O(n)

其中第6,7两行,将剩余的部分追加到tmp[]中,然后将tmp[]写回到arr[]。因此,对于数组使用归并排序,需要辅助空间O(n)。由于是尾部调用merge_core,当然可以将其写入到merge_sort尾部,这里为了思路清晰,将其分成两部分书写。

2)链表的归并排序

事实上,归并排序更适合对链表排序,因为在合并两个链表时,不需要额外的辅助空间存储,而且也不需要对数据拷贝,直接移动指针即可。唯一的不便是:需要每次寻找到链表的中间节点,然后以此将该链表分割成两部分。寻找中间节点,可以定义两个指针fast和Mid,fast每次移动两步,mid每次移动一步,当fast到链表尾部时,mid此时处于链表中间(不用考虑奇偶情况):

  1. // Merge sort for single list as ascending order
  2. // single list node define
  3. typedef struct __ListNode
  4. {
  5. int val;
  6. struct __ListNode *next;
  7. }ListNode;
  8.  
  9. // Merge sort for single list without head node
  10. ListNode *merge_sort(ListNode *head)
  11. {
  12. if (head==NULL || head->next==NULL) return head;
  13. ListNode *fast, *mid, H;
  14. // find mid node between head and end
  15. for (H.next=head, fast=mid=&H; fast && fast->next;){
  16. mid = mid->next;
  17. fast = fast->next->next;
  18. }
  19. fast = mid->next;
  20. mid->next = NULL; // cut down mid part from head list
  21. mid = fast;
  22.  
  23. head = merge_sort(head);
  24. mid = merge_sort(mid);
  25. return merge_core(head,mid);
  26. }

注意,找到链表的中间节点后,务必将其指向NULL,以保证确实将链表分成两部分。然后将两个链表head与mid进行合并。由于合并后可能会修改链表头结点,因此要返回新的链表头结点。下面是合并操作:

  1. // merge single list without head node (ascending order)
  2. ListNode *merge_core(ListNode *i, ListNode *j)
  3. {
  4. ListNode H, *p;
  5. for (p=&H; i && j; p=p->next){
  6. if (i->val < j->val){
  7. p->next = i;
  8. i = i->next;
  9. }
  10. else{
  11. p->next = j;
  12. j = j->next;
  13. }
  14. }
  15. p->next = (i ? i:j);
  16. return H.next;
  17. }

链表合并时,不需要像数组那样,直接可以将链表尾部p->next指向剩余的i或j,即可完成合并。可以看出,归并排序更适合于对链表排序,而快速排序适合于数组排序。

注:本文涉及的源码:merge sort : https://git.oschina.net/eudiwffe/codingstudy/blob/master/src/sort/mergesort.c

[算法]——归并排序(Merge Sort)的更多相关文章

  1. 经典排序算法 - 归并排序Merge sort

    经典排序算法 - 归并排序Merge sort 原理,把原始数组分成若干子数组,对每个子数组进行排序, 继续把子数组与子数组合并,合并后仍然有序,直到所有合并完,形成有序的数组 举例 无序数组[6 2 ...

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

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

  3. 连续线性空间排序 起泡排序(bubble sort),归并排序(merge sort)

    连续线性空间排序 起泡排序(bubble sort),归并排序(merge sort) 1,起泡排序(bubble sort),大致有三种算法 基本版,全扫描. 提前终止版,如果发现前区里没有发生交换 ...

  4. 归并排序(merge sort)

    M erge sort is based on the divide-and-conquer paradigm. Its worst-case running time has a lower ord ...

  5. [算法导论]merge sort @ Python

    import sys class mergesort(): def merge_sort(self, A, p, r): if p < r: q = (p + r) / 2 self.merge ...

  6. 归并排序——Merge Sort

    基本思想:参考 归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法的一个非常典型的应用.首先考虑下如何将2个有序数列合并.这个非常简单,只要从比较2个数列的第一个数,谁小就先取谁,取了 ...

  7. 归并排序Merge Sort

    //C语言实现 void mergeSort(int array[],int first, int last) { if (first < last)//拆分数列中元素只剩下两个的时候,不再拆分 ...

  8. 归并排序Merge sort(转)

    原理,把原始数组分成若干子数组,对每一个子数组进行排序, 继续把子数组与子数组合并,合并后仍然有序,直到全部合并完,形成有序的数组 举例 无序数组[6 2 4 1 5 9] 先看一下每个步骤下的状态, ...

  9. 数据结构 - 归并排序(merging sort)

    归并排序(merging sort): 包含2-路归并排序, 把数组拆分成两段, 使用递归, 将两个有序表合成一个新的有序表. 归并排序(merge sort)的时间复杂度是O(nlogn), 实际效 ...

  10. 数据结构 - 归并排序(merging sort) 具体解释 及 代码

    归并排序(merging sort) 具体解释 及 代码 本文地址: http://blog.csdn.net/caroline_wendy 归并排序(merging sort): 包括2-路归并排序 ...

随机推荐

  1. Hadoop 中利用 mapreduce 读写 mysql 数据

    Hadoop 中利用 mapreduce 读写 mysql 数据   有时候我们在项目中会遇到输入结果集很大,但是输出结果很小,比如一些 pv.uv 数据,然后为了实时查询的需求,或者一些 OLAP ...

  2. 百度MIP页规范详解 —— canonical标签

    百度MIP的规范要求必须添加强制性标签canonical,不然MIP校验工具会报错: 强制性标签<link rel="/^(canonical)$/"> 缺失或错误 这 ...

  3. C#中那些[举手之劳]的性能优化

    隔了很久没写东西了,主要是最近比较忙,更主要的是最近比较懒...... 其实这篇很早就想写了 工作和生活中经常可以看到一些程序猿,写代码的时候只关注代码的逻辑性,而不考虑运行效率 其实这对大多数程序猿 ...

  4. ABP文档 - 异常处理

    文档目录 本节内容: 简介 启用错误处理 非AJAX请求 显示异常 UserFriendlyException Error 模型 AJAX 请求 异常事件 简介 这个文档针对Asp.net Mvc和W ...

  5. PHP赋值运算

    1. 赋值运算:= ,意思是右边表达式的值赋给左边的运算数. $int1=10; $int1=$int1-6; //$int1=4 echo $int1,"<br>"; ...

  6. JdbcTemplate+PageImpl实现多表分页查询

    一.基础实体 @MappedSuperclass public abstract class AbsIdEntity implements Serializable { private static ...

  7. EntityFramework Core 1.1是如何创建DbContext实例的呢?

    前言 上一篇我们简单讲述了在EF Core1.1中如何进行迁移,本文我们来讲讲EF Core1.1中那些不为人知的事,细抠细节,从我做起. 显式创建DbContext实例 通过带OnConfiguri ...

  8. SQL Server-聚焦NOT IN VS NOT EXISTS VS LEFT JOIN...IS NULL性能分析(十八)

    前言 本节我们来综合比较NOT IN VS NOT EXISTS VS LEFT JOIN...IS NULL的性能,简短的内容,深入的理解,Always to review the basics. ...

  9. Android N开发 你需要知道的一切

    title: Android N开发 你需要知道的一切 tags: Android N,Android7.0,Android --- 转载请注明出处:http://www.cnblogs.com/yi ...

  10. javascript匹配各种括号书写是否正确

    今天在codewars上做了一道题,如下 看上去就是验证三种括号各种嵌套是否正确书写,本来一头雾水,一种括号很容易判断, 但是三种怎么判断! 本人只是个前端菜鸟,,不会什么高深的正则之类的. 于是,在 ...