算法进化历程之“归并排序”
来源:互联网 发布:华泰证券期货软件 编辑:程序博客网 时间:2024/06/08 11:53
算法进化历程之“归并排序”
巧若拙(欢迎转载,但请注明出处:http://blog.csdn.net/qiaoruozhuo)
归并排序是分治算法的典型运用,其中先分治,再合并的思路真是美妙至极。
归并排序算法的基本操作是合并两个已排序的表,这只需要线性的时间,不足之处是需要分配一个临时数组来暂存数据。
归并排序算法可以用递归的形式实现,形式简洁易懂。如果N=1,则只有一个元素需要排序,我们可以什么都不做;否则,递归地将前半部分数据和后半部分数据各自归并排序,然后合并这两个部分。
归并排序算法也可以用非递归的形式实现,稍微难理解一点。它刚好是递归分治算法的逆向思维形式,在使用递归分治算法时,程序员只需考虑将一个大问题分成若干个形式相同的小问题,和解的边界条件,具体如何解决这些小问题是由计算机自动完成的;而非递归形式要求程序员从最基本的情况出发,即从解决小问题出发,一步步扩展到大问题。
一点说明:很多人在写递归形式的归并排序算法时,临时数组是在归并排序子程序 MSort ()中分配的,这使得 在任一时刻都可能有 logN 个临时数组处在活动期,如果数据较多,则开销很大,实用性很差 。我在版本1中把临时数组设置在合并函数 Merge ()中,避免了这个问题;在版本2中进一步优化,把临时数组设置在归并排序驱动程序MergeSort()中,只需执行一次分配空间的函数就行了。
版本一:普通的合并算法
网络上贴出的归并排序算法版本很多,多数采用的普通的合并函数 Merge (),即先将存储在A[]中的两个已排序序列按非递减序归并入tempA[],然后将A[]中剩余的元素复制到tempA[],最后将tempA[]中元素复制回A[]。算法简明易懂,代码如下:
void MergeSort_1(ElemType A[], int n)//归并排序驱动程序
{
MSort_1(A,0, n-1);
}
void MSort_1(ElemType A[], int left, intright)//归并排序子程序
{
intmid = (left + right) / 2;
if(left < right)
{
MSort_1(A,left, mid);
MSort_1(A,mid+1, right);
Merge_1(A,left, mid, right);
}
}
void Merge_1(ElemType A[], int left, intmid, int right)//普通合并程序
{
inti, j, k;
ElemType*tempA = malloc((right-left+1)*sizeof(ElemType));
if(!tempA)
{
printf("Outof space!");
exit(1);
}
k= 0;
for(i=left, j=mid+1; i<=mid && j<=right; )//将A[]中元素按非递减序归并入tempA[]
{
if(A[i] <= A[j])
tempA[k++]= A[i++];
else
tempA[k++]= A[j++];
}
//将A[]中剩余的元素复制到tempA[]
while(i <= mid)
tempA[k++]= A[i++];
while(j <= right)
tempA[k++]= A[j++];
//将tempA[]中元素复制回A[]
for (i=left,j=0; j<k; i++,j++)
A[i] = tempA[j];
free(tempA);
}
版本二:高效的合并算法
Mark Allen Weiss在其著作《数据结构与算法分析——C语言描述》中介绍了一个高效的归并排序算法,他把临时数组设置在归并排序驱动程序MergeSort()中,只需执行一次分配空间的函数就行了。
此外,还可以对普通的合并函数进行优化,只需把两个已排序序列“相向复制”到临时数组tempA[],然后归并入A[]即完成排序。所谓“相向复制”是指第一个序列顺序存储到tempA[],第二个序列逆序存储到tempA[];执行合并操作的时候从左向右扫描序列1,从右向左扫描序列2,正好“相向而行”。这样处理的好处是无需判断两个分序列是否越界,只要合并完所有元素,循环自动结束。
代码如下:
void MergeSort_2(ElemType A[], int n)//归并排序驱动程序
{
ElemType*tempA = malloc(n*sizeof(ElemType));
if(!tempA)
{
printf("Outof space!");
exit(1);
}
MSort_2(A,tempA, 0, n-1);
free(tempA);
}
void MSort_2(ElemType A[], ElemTypetempA[], int left, int right)//归并排序子程序
{
intmid = (left + right) / 2;
if(left < right)
{
MSort_2(A,tempA, left, mid);
MSort_2(A,tempA, mid+1, right);
Merge_2(A,tempA, left, mid, right);
}
}
void Merge_2(ElemType A[], ElemTypetempA[], int left, int mid, int right)//高效合并程序
{
inti, j, k;
for(i=left; i<=mid; i++)//将A[left..mid]中元素顺序复制到tempA[left..mid]
tempA[i]= A[i];
for(i=mid+1,j=right; i<=right; i++,j--)//将A[mid+1..right]中元素逆序序复制到tempA[mid+1..right]
tempA[j] = A[i];
for(k=i=left, j=right; k<=right; )//巧妙安排扫描方向,只需判断k是否越界
{
if(tempA[i] <= tempA[j])
A[k++]= tempA[i++];
else
A[k++]= tempA[j--];
}
}
版本三:非递归算法
归并排序的递归算法简明易懂,但毕竟要多次递归调用,需要额外的递归栈空间,效率不如非递归算法高。
网络上各种非递归算法也是五花八门,良莠不齐,我就不一一介绍了,只贴出我最喜欢的一款——也是原创的一款,呵呵。
简单介绍一下算法思路:分别用beforeLen和afterLen表示合并前后序列的长度,其中合并后序列的长度是合并前的两倍。
初始条件beforeLen=1,之后每执行一次合并操作,beforeLen增倍(beforeLen=afterLen)。
外部 for 循环的循环体每执行一次,都使 beforeLen 和 afterLen 加倍。内部的for循环执行序列的合并工作,它的循环体每执行一次,i 都向前移动 afterLen 个位置,合并相邻的两个等量序列,直到剩余元素的数量小于afterLen。若剩下的元素数量介于beforeLen和afterLen之间,说明这是两个数量不等的序列,对他们进行合并操作;若剩下的元素数量不多于beforeLen,说明这些元素属于同一个已排序的序列,无需合并。
合并函数采用了高效的Merge_2()。代码如下:
void MergeSort_3(ElemType A[], int n)//归并排序主程序(非递归)
{
inti, c=0;
intbeforeLen; // 合并前序列的长度
intafterLen = 1;// 合并后序列的长度
ElemType*tempA = malloc(n*sizeof(ElemType));
if(!tempA)
{
printf("Outof space!");
exit(1);
}
for (beforeLen=1; afterLen<n;beforeLen=afterLen)
{
afterLen = beforeLen << 1; //合并后序列的长度是合并前的两倍
for(i=0; i+afterLen<=n; i+=afterLen) //两两合并相邻的两个等量序列
Merge_2(A,tempA, i, i+beforeLen-1, i+afterLen-1);
if (i+beforeLen < n)//合并两个数量不等的序列
Merge_2(A,tempA, i, i+beforeLen-1, n-1);
}
free(tempA);
}
本篇文章中的Merge_2()和MergeSort_3()均属原创,虽然经过小规模数据的测试,但也可能存在纰漏,欢迎批评指正,谢谢!
- 算法进化历程之“归并排序”
- 算法进化历程之“快速排序”
- 算法进化历程之“水壶问题”
- 算法进化历程之相亲数
- 算法进化历程之“n皇后问题”
- 排序算法之归并
- 算法之归并排序
- 排序算法之归并
- 算法之归并排序
- 算法之归并排序
- 算法之归并排序
- 算法之归并排序
- 算法之归并排序
- 算法进化历程之“最大连续子序列之和”
- 排序算法之归并排序
- 排序算法之归并排序
- 排序算法之归并排序
- 排序算法之归并排序
- 汇编语言学习之第六章实验5.2第4问
- 如何设置socket函数的非阻塞调用
- 递归创建链表
- mysql性能优化-慢查询分析、优化索引和配置
- UDP协议与TCP协议的比较
- 算法进化历程之“归并排序”
- [MySQL] 日志文件概述
- 堆和栈的区别
- C语言中 %p的含义
- ACM算法列表
- POJ2762-Going from u to v or from v to u?(强连通缩点+DP)
- android intent uri汇总
- hdu 4417,poj 2104 划分树(模版)归并树(模版)
- 黑马程序员-----代理模式