2-3.归并排序详解
2015-05-28 20:37
302 查看
归并排序
基本思想:设两个有序的子序列(相当于输入序列)放在同一序列中相邻的位置上:array[low..m],array[m + 1..high],先将它们合并到一个局部的暂存序列 temp (相当于输出序列)中,待合并完成后将 temp 复制回 array[low..high]中,从而完成排序。
在具体的合并过程中,设置 i,j 和 p 三个指针,其初值分别指向这三个记录区的起始位置。合并时依次比较 array[i] 和 array[j] 的关键字,取关键字较小(或较大)的记录复制到 temp[p] 中,然后将被复制记录的指针 i 或 j 加 1,以及指向复制位置的指针 p 加 1。重复这一过程直至两个输入的子序列有一个已全部复制完毕(不妨称其为空),此时将另一非空的子序列中剩余记录依次复制到 array 中即可。
合并已排序的序列的具体实现代码:
归并排序有两种实现方法:自底向上和自顶向下。
自底向上方法,也就是常说的二路归并排序,其基本思想是:第 1 趟排序将长度为 n 的待排序记录看作 n 个长度为 1 的有序子序列,然后将这些子序列两两合并。完成第 1 趟排序之后,将得到 lgn 个长度为 2 的有序子序列(如果 n 为奇数,则最后还有一个长度为 1 的子序列)。第 2 趟排序是在第 1 趟的排序的基础上,将这 lgn 个长度为 2 的子序列两两合并。如此反复,直到最后得到一个长度为n的有序文件为止。从这个排序过程来看,二路归并排序是从将长度为 1 的子序列排序变化到长度为 n 的有序序列,因而是自底向上的。自底向上的二路归并排序算法虽然效率较高,但可读性较差(循环实现比递归实现一般效率都要高些)。
自低向上的具体实现代码:
下面来看看自上而下的递归实现,其可读性要好得多。自上而下的方法是采用分治法思想,具体排序过程分成三个过程:
(1)分解:将当前区间一分为二,即求分裂点 mid = (low + high)/2;
(2)求解:递归地对两个子区间 array[low..mid] 和 array[mid + 1..high] 进行归并排序;递归的终结条件:子区间长度为 1(一个记录自然有序)。
(3)合并:将已排序的两个子区间R[low..mid]和R[mid + 1..high]归并为一个有序的区间 array[low..high]。
自上而下的具体实现代码:
归并排序的完整实现代码如下:
时间复杂度分析:对长度为 n 的序列进行 lgn 趟二路归并,而每一趟归并的时间复杂度为 O(n),因此归并排序的平均时间复杂度为 nlgn。
空间复杂度分析:需要与待排记录等大的空间来存储中间变量,因为其空间复杂度为 O(n)。因此,归并排序肯定就不是就地排序了。
补充:归并排序是稳定排序。若归并排序采用链表存储结构的话,实现起来更加高效。
基本思想:设两个有序的子序列(相当于输入序列)放在同一序列中相邻的位置上:array[low..m],array[m + 1..high],先将它们合并到一个局部的暂存序列 temp (相当于输出序列)中,待合并完成后将 temp 复制回 array[low..high]中,从而完成排序。
在具体的合并过程中,设置 i,j 和 p 三个指针,其初值分别指向这三个记录区的起始位置。合并时依次比较 array[i] 和 array[j] 的关键字,取关键字较小(或较大)的记录复制到 temp[p] 中,然后将被复制记录的指针 i 或 j 加 1,以及指向复制位置的指针 p 加 1。重复这一过程直至两个输入的子序列有一个已全部复制完毕(不妨称其为空),此时将另一非空的子序列中剩余记录依次复制到 array 中即可。
合并已排序的序列的具体实现代码:
void mergearray(int a[],int first,int mid,int last,int temp[]) { int i=first,j=mid+1; int m=mid,n=last; int k=0; while(i<=m&&j<=n) { if (a[i]<=a[j]) { temp[k++]=a[i++]; } else { temp[k++]=a[j++]; } } while(i<=m) { temp[k++]=a[i++]; } while(j<=n) { temp[k++]=a[j++]; } for (i=0;i<k;i++) { a[first+i]=temp[i]; } }
归并排序有两种实现方法:自底向上和自顶向下。
自底向上方法,也就是常说的二路归并排序,其基本思想是:第 1 趟排序将长度为 n 的待排序记录看作 n 个长度为 1 的有序子序列,然后将这些子序列两两合并。完成第 1 趟排序之后,将得到 lgn 个长度为 2 的有序子序列(如果 n 为奇数,则最后还有一个长度为 1 的子序列)。第 2 趟排序是在第 1 趟的排序的基础上,将这 lgn 个长度为 2 的子序列两两合并。如此反复,直到最后得到一个长度为n的有序文件为止。从这个排序过程来看,二路归并排序是从将长度为 1 的子序列排序变化到长度为 n 的有序序列,因而是自底向上的。自底向上的二路归并排序算法虽然效率较高,但可读性较差(循环实现比递归实现一般效率都要高些)。
自低向上的具体实现代码:
void mergesortdowntoup(int a[],int n,int temp[]) { int length=1; while(length<n) { int i=0; for (;i+2*length<n;i+=2*length) { mergearray(a,i,i+length-1,i+2*length-1,temp); } if (i+length<=n-1) { mergearray(a,i,i+length-1,n-1,temp);//尚有两个子文件,其中后一个长度小于length,归并最后两个子文件 ////注意:若i≤n-1且i+length-1≥n-1时,则剩余一个子文件轮空,无须归并 } length*=2; } }
下面来看看自上而下的递归实现,其可读性要好得多。自上而下的方法是采用分治法思想,具体排序过程分成三个过程:
(1)分解:将当前区间一分为二,即求分裂点 mid = (low + high)/2;
(2)求解:递归地对两个子区间 array[low..mid] 和 array[mid + 1..high] 进行归并排序;递归的终结条件:子区间长度为 1(一个记录自然有序)。
(3)合并:将已排序的两个子区间R[low..mid]和R[mid + 1..high]归并为一个有序的区间 array[low..high]。
自上而下的具体实现代码:
void mergesortuptodown(int a[],int first,int last,int temp[]) { if (first<last) { int mid=(first+last)/2; mergesortuptodown(a,first,mid,temp); mergesortuptodown(a,mid+1,last,temp); mergearray(a,first,mid,last,temp); } }
归并排序的完整实现代码如下:
// 归并排序.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
void mergearray(int a[],int first,int mid,int last,int temp[]) { int i=first,j=mid+1; int m=mid,n=last; int k=0; while(i<=m&&j<=n) { if (a[i]<=a[j]) { temp[k++]=a[i++]; } else { temp[k++]=a[j++]; } } while(i<=m) { temp[k++]=a[i++]; } while(j<=n) { temp[k++]=a[j++]; } for (i=0;i<k;i++) { a[first+i]=temp[i]; } }
//自上向下归并
void mergesortuptodown(int a[],int first,int last,int temp[]) { if (first<last) { int mid=(first+last)/2; mergesortuptodown(a,first,mid,temp); mergesortuptodown(a,mid+1,last,temp); mergearray(a,first,mid,last,temp); } }
//自下向上归并
void mergesortdowntoup(int a[],int n,int temp[]) { int length=1; while(length<n) { int i=0; for (;i+2*length<n;i+=2*length) { mergearray(a,i,i+length-1,i+2*length-1,temp); } if (i+length<=n-1) { mergearray(a,i,i+length-1,n-1,temp);//尚有两个子文件,其中后一个长度小于length,归并最后两个子文件 ////注意:若i≤n-1且i+length-1≥n-1时,则剩余一个子文件轮空,无须归并 } length*=2; } }
int _tmain(int argc, _TCHAR* argv[])
{
int num[]={5,2,1,6,7,9,0,4,3,8};
int num1[]={5,2,1,6,7,9,0,4,3,8};
int temp[10];
int temp1[10];
mergesortuptodown(num,0,9,temp);
printf("自上向下的归并排序:");
for (int i=0;i<10;i++)
{
printf("%d ",num[i]);
}
printf("\n");
mergesortdowntoup(num1,10,temp1);
printf("自下向上的归并排序:");
for (int i=0;i<10;i++)
{
printf("%d ",num[i]);
}
printf("\n");
return 0;
}
时间复杂度分析:对长度为 n 的序列进行 lgn 趟二路归并,而每一趟归并的时间复杂度为 O(n),因此归并排序的平均时间复杂度为 nlgn。
空间复杂度分析:需要与待排记录等大的空间来存储中间变量,因为其空间复杂度为 O(n)。因此,归并排序肯定就不是就地排序了。
补充:归并排序是稳定排序。若归并排序采用链表存储结构的话,实现起来更加高效。
相关文章推荐
- ARM 64位处理器架构ARMv8技术浅析
- UML简单介绍(八)——时序图的基本概念与解析
- C++线程基础
- 第四章 冒泡排序法 潘璠
- 03-Xcode 6 插件失效的临时解决方案
- angular 实现modal windows效果(即模态窗口,半透明的遮罩层),以及bootstrap(css,components,js)的初步学习
- Scrapy中的item是什么
- 多项式求和(SDUTOJ_2504)
- 秒杀核心设计(减库存部分)-防超卖与高并发
- 杭电oj 1328
- linux 琐碎知识点
- Android 怎么使用Bitmap+Canvas 自适应屏幕
- openstack Juno版部署记录
- C语言做的键盘操作的五子棋
- 并查集的简单应用——HDU
- sip的安装
- Spark目录
- 圆形头像
- noip2014Day2T3解方程
- MapReduce的流程