[算法]——归并排序(Merge Sort)
归并排序(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)的更多相关文章
- 经典排序算法 - 归并排序Merge sort
经典排序算法 - 归并排序Merge sort 原理,把原始数组分成若干子数组,对每个子数组进行排序, 继续把子数组与子数组合并,合并后仍然有序,直到所有合并完,形成有序的数组 举例 无序数组[6 2 ...
- 排序算法二:归并排序(Merge sort)
归并排序(Merge sort)用到了分治思想,即分-治-合三步,算法平均时间复杂度是O(nlgn). (一)算法实现 private void merge_sort(int[] array, int ...
- 连续线性空间排序 起泡排序(bubble sort),归并排序(merge sort)
连续线性空间排序 起泡排序(bubble sort),归并排序(merge sort) 1,起泡排序(bubble sort),大致有三种算法 基本版,全扫描. 提前终止版,如果发现前区里没有发生交换 ...
- 归并排序(merge sort)
M erge sort is based on the divide-and-conquer paradigm. Its worst-case running time has a lower ord ...
- [算法导论]merge sort @ Python
import sys class mergesort(): def merge_sort(self, A, p, r): if p < r: q = (p + r) / 2 self.merge ...
- 归并排序——Merge Sort
基本思想:参考 归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法的一个非常典型的应用.首先考虑下如何将2个有序数列合并.这个非常简单,只要从比较2个数列的第一个数,谁小就先取谁,取了 ...
- 归并排序Merge Sort
//C语言实现 void mergeSort(int array[],int first, int last) { if (first < last)//拆分数列中元素只剩下两个的时候,不再拆分 ...
- 归并排序Merge sort(转)
原理,把原始数组分成若干子数组,对每一个子数组进行排序, 继续把子数组与子数组合并,合并后仍然有序,直到全部合并完,形成有序的数组 举例 无序数组[6 2 4 1 5 9] 先看一下每个步骤下的状态, ...
- 数据结构 - 归并排序(merging sort)
归并排序(merging sort): 包含2-路归并排序, 把数组拆分成两段, 使用递归, 将两个有序表合成一个新的有序表. 归并排序(merge sort)的时间复杂度是O(nlogn), 实际效 ...
- 数据结构 - 归并排序(merging sort) 具体解释 及 代码
归并排序(merging sort) 具体解释 及 代码 本文地址: http://blog.csdn.net/caroline_wendy 归并排序(merging sort): 包括2-路归并排序 ...
随机推荐
- node-webkit 环境搭建与基础demo
首先去github上面下载(地址),具体更具自己的系统,我的是windows,这里只给出windows的做法 下载windows x64版本 下载之后解压,得到以下东西 为了方便,我们直接在这个目录中 ...
- NuGet镜像上线试运行
为解决国内访问NuGet服务器速度不稳定的问题,我们用阿里云服务器搭建了一个NuGet镜像,目前已上线试运行. 使用NuGet镜像源的方法如下: 1)NuGet镜像源地址:https://nuget. ...
- Javascript - Promise学习笔记
最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下. 一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编 ...
- UWP学习目录整理
UWP学习目录整理 0x00 可以忽略的废话 10月6号靠着半听半猜和文字直播的补充看完了微软的秋季新品发布会,信仰充值成功,对UWP的开发十分感兴趣,打算后面找时间学习一下.谁想到学习的欲望越来越强 ...
- SQL Server 无法连接到服务器。SQL Server 复制需要有实际的服务器名称才能连接到服务器。请指定实际的服务器名称。
异常处理汇总-数据库系列 http://www.cnblogs.com/dunitian/p/4522990.html SQL性能优化汇总篇:http://www.cnblogs.com/dunit ...
- 02.LoT.UI 前后台通用框架分解系列之——灵活的菜单栏
LOT.UI分解系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下:https://github.com/du ...
- javascript之活灵活现的Array
前言 就如同标题一样,这篇文章将会灵活的运行Array对象的一些方法来实现看上去较复杂的应用. 大家都知道Array实例有这四个方法:push.pop.shift.unshift.大家也都知道 pus ...
- c#多线程
一.使用线程的理由 1.可以使用线程将代码同其他代码隔离,提高应用程序的可靠性. 2.可以使用线程来简化编码. 3.可以使用线程来实现并发执行. 二.基本知识 1.进程与线程:进程作为操作系统执行程序 ...
- 在开启DRS的集群中修复VMware虚拟主机启动问题
通过iSCSI方式连接到ESXi主机上的外挂存储意外失联了一段时间,导致部分虚拟主机在集群中呈现出孤立的状态,单独登陆到每台ESXi上可以看到这些虚拟主机都变成了unknow状态.因为有过上一次(VM ...
- StringUtils的isBlank与isEmply
1.public static boolean isEmpty(String str) 判断某字符串是否为空,为空的标准是 str==null 或 str.length()==0 StringUtil ...