[算法]——归并排序(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-路归并排序 ...
随机推荐
- DDD CQRS架构和传统架构的优缺点比较
明天就是大年三十了,今天在家有空,想集中整理一下CQRS架构的特点以及相比传统架构的优缺点分析.先提前祝大家猴年新春快乐.万事如意.身体健康! 最近几年,在DDD的领域,我们经常会看到CQRS架构的概 ...
- 前端学HTTP之字符集
前面的话 HTTP报文中可以承载以任何语言表示的内容,就像它能承载图像.影片或任何类型的媒体那样.对HTTP来说,实体主体只是二进制信息的容器而已.为了支持国际性内容,服务器需要告知客户端每个文档的字 ...
- 流程开发Activiti 与SpringMVC整合实例
流程(Activiti) 流程是完成一系列有序动作的概述.每一个节点动作的结果将对后面的具体操作步骤产生影响.信息化系统中流程的功能完全等同于纸上办公的层级审批,尤其在oa系统中各类电子流提现较为明显 ...
- 基于改进人工蜂群算法的K均值聚类算法(附MATLAB版源代码)
其实一直以来也没有准备在园子里发这样的文章,相对来说,算法改进放在园子里还是会稍稍显得格格不入.但是最近邮箱收到的几封邮件让我觉得有必要通过我的博客把过去做过的东西分享出去更给更多需要的人.从论文刊登 ...
- WinForm设置控件焦点focus
winform窗口打开后文本框的默认焦点设置,进入窗口后默认聚焦到某个文本框,两种方法: ①设置tabindex 把该文本框属性里的tabIndex设为0,焦点就默认在这个文本框里了. ②Winfor ...
- java中易错点(一)
由于replaceAll方法的第一个参数是一个正则表达式,而"."在正则表达式中表示任何字符,所以会把前面字符串的所有字符都替换成"/".如果想替换的只是&qu ...
- Linux实战教学笔记08:Linux 文件的属性(上半部分)
第八节 Linux 文件的属性(上半部分) 标签(空格分隔):Linux实战教学笔记 第1章 Linux中的文件 1.1 文件属性概述(ls -lhi) linux里一切皆文件 Linux系统中的文件 ...
- 如何让spring mvc web应用启动时就执行特定处理
Asp.Net的应用中通过根目录下的Global.asax,在Application_Start方法中做一些初始化操作,比如:预先加载缓存项对网站热点数据进行预热,获取一些远程的配置信息等等. Spr ...
- 绿色版的Linux.NET——“Jws.Mono”(续)
在前一篇文章中,我们简略的介绍了jws.mono的安装使用,以及我们如何自己动手做一个jws.mono出来. 在文章发表之后的几天里,我一直觉得有点不妥之处,直到后来猛然的意识到:我们自己动手做的jw ...
- Visual Studio Code v0.9.1 发布
微软的跨平台编辑器 Visual Studio Code v0.9.1 已经发布,官方博客上发布文章Visual Studio Code – October Update (0.9.1):http:/ ...