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

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

// A structure data definition
typedef struct __Student
{
char name[16];
int score;
}Student;
// Array of students
name : A B C D
score: 80 70 75 70 Stable sort in ascending order:
name : B D C A
score: 70 70 75 80 Unstable sort in ascending order:
name : D B C A
score: 70 70 75 80

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

1)数组的归并排序

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

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

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

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

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

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

void merge_core(int arr[], int begin, int mid, int end)
{
int i=begin, j=mid, k=0;
int *tmp = (int*)malloc(sizeof(int)*(end-begin));
for(; i<mid && j<end; tmp[k++]=(arr[i]<arr[j]?arr[i++]:arr[j++]));
for(; i<mid; tmp[k++]=arr[i++]);
for(; j<end; tmp[k++]=arr[j++]);
for(i=begin, k=0; i<end; arr[i++]=tmp[k++]);
free(tmp);
} // Time O(n), Space O(n)

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

2)链表的归并排序

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

// Merge sort for single list as ascending order
// single list node define
typedef struct __ListNode
{
int val;
struct __ListNode *next;
}ListNode; // Merge sort for single list without head node
ListNode *merge_sort(ListNode *head)
{
if (head==NULL || head->next==NULL) return head;
ListNode *fast, *mid, H;
// find mid node between head and end
for (H.next=head, fast=mid=&H; fast && fast->next;){
mid = mid->next;
fast = fast->next->next;
}
fast = mid->next;
mid->next = NULL; // cut down mid part from head list
mid = fast; head = merge_sort(head);
mid = merge_sort(mid);
return merge_core(head,mid);
}

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

// merge single list without head node (ascending order)
ListNode *merge_core(ListNode *i, ListNode *j)
{
ListNode H, *p;
for (p=&H; i && j; p=p->next){
if (i->val < j->val){
p->next = i;
i = i->next;
}
else{
p->next = j;
j = j->next;
}
}
p->next = (i ? i:j);
return H.next;
}

链表合并时,不需要像数组那样,直接可以将链表尾部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. node-webkit 环境搭建与基础demo

    首先去github上面下载(地址),具体更具自己的系统,我的是windows,这里只给出windows的做法 下载windows x64版本 下载之后解压,得到以下东西 为了方便,我们直接在这个目录中 ...

  2. NuGet镜像上线试运行

    为解决国内访问NuGet服务器速度不稳定的问题,我们用阿里云服务器搭建了一个NuGet镜像,目前已上线试运行. 使用NuGet镜像源的方法如下: 1)NuGet镜像源地址:https://nuget. ...

  3. Javascript - Promise学习笔记

    最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下.   一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编 ...

  4. UWP学习目录整理

    UWP学习目录整理 0x00 可以忽略的废话 10月6号靠着半听半猜和文字直播的补充看完了微软的秋季新品发布会,信仰充值成功,对UWP的开发十分感兴趣,打算后面找时间学习一下.谁想到学习的欲望越来越强 ...

  5. SQL Server 无法连接到服务器。SQL Server 复制需要有实际的服务器名称才能连接到服务器。请指定实际的服务器名称。

    异常处理汇总-数据库系列  http://www.cnblogs.com/dunitian/p/4522990.html SQL性能优化汇总篇:http://www.cnblogs.com/dunit ...

  6. 02.LoT.UI 前后台通用框架分解系列之——灵活的菜单栏

    LOT.UI分解系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下:https://github.com/du ...

  7. javascript之活灵活现的Array

    前言 就如同标题一样,这篇文章将会灵活的运行Array对象的一些方法来实现看上去较复杂的应用. 大家都知道Array实例有这四个方法:push.pop.shift.unshift.大家也都知道 pus ...

  8. c#多线程

    一.使用线程的理由 1.可以使用线程将代码同其他代码隔离,提高应用程序的可靠性. 2.可以使用线程来简化编码. 3.可以使用线程来实现并发执行. 二.基本知识 1.进程与线程:进程作为操作系统执行程序 ...

  9. 在开启DRS的集群中修复VMware虚拟主机启动问题

    通过iSCSI方式连接到ESXi主机上的外挂存储意外失联了一段时间,导致部分虚拟主机在集群中呈现出孤立的状态,单独登陆到每台ESXi上可以看到这些虚拟主机都变成了unknow状态.因为有过上一次(VM ...

  10. StringUtils的isBlank与isEmply

    1.public static boolean isEmpty(String str) 判断某字符串是否为空,为空的标准是 str==null 或 str.length()==0 StringUtil ...