归并排序算法
来源:互联网 发布:linux 线程数过多 编辑:程序博客网 时间:2024/06/16 16:10
归并排序
归并排序以O(N logN)最坏情形时间运行而所使用的比较次数几乎是最优的。它是分治算法的一个很好的实例。
- 首先我们使用分治模式分析排序问题
- 分解:分解待排序的n个元素的表成各具n/2个元素的子表
- 解决:使用归并排序递归地排序两个子表
- 合并:合并两个已排序好的子表以产生已排序的答案
- 分解
因为当表长度n为1时无法再分,所以我们将n=1作为递归过 程的基准情形,当待排序的表长度为1时开始回升,这种情况下我们不需要进行操作,因为长度为1的表已经排好序
- 这些子表的表可以表示成平衡二叉树,根为原表,每个节点是当前表,每个左孩子是这个这个表的前半部分,每个右孩子是这个表的后半部分,为了方便,我们在n为奇数的情况下,将中间数归位左孩子
- 这样,当n为1时我们就访问到了这个平衡二叉树的叶
- 解决
合并成对的子表,因为两个表都是按照升序排列的,对他们进行归并后产生的表也是升序的
- 合并
当访问到叶并进行合并后,开始向上回升,不断地归并当前节点的两个子节点,最后完成整个表的排序,这里对应的是树的遍历中的后续遍历
伪代码如下
procedure mergesort(L=[a[0]..a[n]])/*L以非降序排列*/if n>1 then mid:=⌊n/2⌋ L1:=L[a[0]..a[mid]] L2:=L[a[mid]..a[n]] mergeSort(L1) mergeSort(L2) merge(L1,L2)
在归并(merge)例程中,我们需要借助一个队列来存储归并结果,否则原表会发生损坏。我们来分析一下在归并例程中不断申请队列的后果:
- 当我们访问到叶时开始回升,在叶处无需操作所以没有申请内存的操作
- 每当我们访问到节点时,我们申请在每一层我们都需要申请总长度为n的内存空间
- 在depth-1层,我们需要执行n/2次malloc操作,在depth-2层,我们需要执行n/4次malloc操作,依此类推,当我们访问到根时,仍需执行一次malloc操作,总的malloc操作次数为n-1次!
- 而为了防止内存冗余,我们又将执行n-1次free操作,这将产生很大的时间开销,这是我们所不愿意看见的
然而我们不难发现,对于任意一次归并操作,即使是到了根节点,我们所需要的队列大小仍未超过N,所欲我们只需要申请一次队列,以[1..n]为有效长度,即可满足我们的需求
mergeSort
//执行递归操作的函数主体void mergeSort(int*begin,int len,int*tmp){ if(len>1){ int mid=len>>1; mergeSort(begin, mid, tmp); mergeSort(begin+mid, len-mid, tmp); merge(begin, len,tmp); }}
归并排序的主题已经完成,接下来讨论merge例程
- 引理:对两个排好序的表进行归并,最多只需要n+m-1次比较
- 因为我们这两个表是由一个长度为2n的表产生,所以我们最多只需要2n次比较即可完成归并操作,而且我们可以仅将这个两个表的父表作为参数,通过下标进行分割,而不产生额外的操作
- 值得注意的是,因为这两个表都是非降序表,所以若左表的最大元素的数值小于右表的最小值,则表示表已有序,无需操作;若右表的最大值小于左表的最小值,则只需要置换两表的位置即可
merge
void merge(int*begin,int len,int*tmp){ int mid=len>>1; //若左表的最大元素的数值小于右表的最小值,则表示表已有序,不操作 if(begin[mid-1]<begin[mid])return ; int i,j; //若右表的最大值小于左表的最小值,则只需要置换两表的位置即可 if(begin[0]>begin[len-1]){ j=0; for(i=mid;i<len;i++) tmp[j++]=begin[i]; for(i=0;i<mid;i++) tmp[j++]=begin[i]; }else{ int k=0; i=0,j=mid; while(1){ //当两个子表任意一个访问完成,可以记直接将另一个表的所有元素倒入队列中 if(i==mid){ while(j!=len)tmp[k++]=begin[j++]; break; }else if(j==len){ while(i!=mid)tmp[k++]=begin[i++]; break; } //不断向队列中插入较小的元素 tmp[k++]=(begin[i]<begin[j])?begin[i++]:begin[j++]; } } //将排好序的队列中的元素放回原表 for(i=0;i<len;i++) begin[i]=tmp[i];}
当然,对于使用来说,在每次使用之前需要做准备工作,这对使用来说是不友好的,所以我们需要些一个辅助函数来封装起来这些准备工作
MergeSort
//我们排序所使用的接口bool MergeSort(int*arr,int len){ int*tmp=malloc(sizeof(int)*len); if(tmp==NULL){ /*因内存空间问题不是算法所关注的问题, 所以这里只提需要处理无法申请内存的情况 而不讨论内存的处理方法 */ Error("没有足够大的内存来申请数组"); return false; } mergeSort(arr, len, tmp);//开始调用排序的函数主体 free(tmp); return true;}
2 0
- 排序算法-归并排序
- 排序算法------归并排序
- 排序算法-归并排序
- 排序算法---归并排序
- 排序算法--归并排序
- 排序算法--归并排序
- 排序算法-归并排序
- 排序算法--归并排序
- 排序算法--归并排序
- 排序算法:归并排序
- 排序算法-归并排序
- 排序算法:归并排序
- 【排序算法】归并排序
- 排序算法--归并排序
- 排序算法-归并排序
- 排序算法--归并排序
- 排序算法:归并排序
- 【排序算法】归并排序
- 相机的坐标系转换(1)
- mac 升级后 intellij 不能debug
- 串口备份
- eclipse设置java虚拟机内存大小
- U8 SQL语句
- 归并排序算法
- 【Javaweb】笔面试题 ---(1)
- 第三章解答题
- SVM中KKT条件介绍
- 07_02 查询各学期的课程信息
- AI学习之路(8): 定义张量变量
- laravel 邮件服务
- 查看服务器型号、SN、Raid等信息
- java 使用ckfinder