这次讨论的是归并排序。分为多次讲解,这次的是归并排序的基本思想以及归并排序一个核心问题的解决方法。
很多算法书籍开头讲的是链表,不过算法导论当中最先讲的却是几个排序算法,觉得很有意思,我也就按照这种顺序来絮叨一些。
排序是一件很普遍的事情,给你一堆数据,你把它从小到大排列也就是排序了。当然也可以从大到小。
归并排序是排序当中一个行之有效的高效算法,效率在最坏的情况下和快速排序最好的情况同等效率。不过,由于随机化快排,以及在虚拟内存中缓存的问题,现实中快速排序的应用更为广泛。(两种算法应该算是基于比较的排序算法当中最快的两个了)
归并排序的基本思想很简单:
(1) 给定n个数据
(2) 将数据分成两部分,对第一部分(1..n/2)和第二部分(n/2..n)分别使用归并排序
(3) 合并上一步骤得到的两个部分,得到排序结果
第(2)步直接说明了这个排序算法是递归的。
假设我们的算法的函数声明是这样的
void Merge_Sort(int A[],int left,int right);
那么第二步其实也就仅仅调用
Merge_Sort(A,left,mid);
Merge_Sort(A,mid+1,right);
所以,第二步是个很没有技术含量的事情
所以,算法的核心问题是合并动作。一般采用的合并算法是借助辅助数组来存储排序好的数据,合并完以后在放回原数组。
合并的过程类似有两堆扑克牌A和B。A堆和B堆都从小到大排序。
合并过程:从A,B中选出最小的牌出来,放在桌子上,然后从剩下的A堆,B堆中选取最小的牌,放置在桌子上。重复上述的工作,直到两堆的牌都到桌子上了,合并就完毕了。
假设A,B都有5张牌(1,3,4,8,10)和(1,1,2,6,9)
现在我们进行一次模拟合并。
第一次:从A中选出1
第二次:从B中选出1
第三次:从B中选出1
第三次:从B中选出2
第四次:从A中选出3
第五次:从A中选出4
第六次:从B中选出6
第七次:从A中选出8
第八次:从B中选出9
第九次:从A中选出10
排序过程如图,图中A1代表A组第一个元素。
回到抽象的世界。
在归并排序中,A数组在第二部被分为两个子数组B和C(并不需要分配空间)
也就是说
B=A[1….n/2], C=A[n/2+1…n]
在进行合并的时候,B和C都是已经排好序的(至于为什么是排好序的,问递归去吧,下次会更详细显示递归的过程)。
我们假设k=n/2
则B,C都有k个元素。为了排序,现在申请一个新的数组D,它共有2*k=n个位置空间,用来存储合并过程中踢出来的元素。
合并过程:从A和B中选出两个数组中最小的,插入在D最后,重复上述工作,直到数据全部放入。当两数组中有一个已经全部输出,只需要把另一个数组剩下的元素全部添加到D数组末尾后面就可以。
下面以伪代码为例
MERGE(B,C,k):
ptrToB<-1
ptrToC<-2
create array D[1..2*k]
while i<2*kAND(ptrToB<k)AND(ptrToC<k)
do if (B[ptrToB]<C[ptrToC)
thenD[i]<-B[ptrToB]
ptrToB<- ptrToB+1
elseD[i]<-C[ptrToC]
ptrToC<-ptrToC+1
i<-i+1
for j <- ptrToB to k
doD[i]=B[j]
i<-i+1
for j <- prtToC to k
doD[i]=C[j]
i<-i+1
伪代码采用算法导论中的标准,没记错是一个普遍使用的标记系统。
<- 表示赋值
循环采用
for
do statement
条件分之采用:
if
then
statement
else
statement
伪代码中,第一个循环的作用是从B和C中选择最小的元素的放入D(注意B,C已经排序)。
循环过程中很有可能出现的情况是B,C当中已经有一个全部遍历完,而另一个还有元素没有放入D中.后面两个循环所做的工作就是把没有放人的元素放入D中。
头像是我的公众号,扫码加我吧,哈哈哈,我定期发布文章