死磕算法导论(二)合并排序

来源:互联网 发布:mac好用的流程图软件 编辑:程序博客网 时间:2024/06/14 05:06

距离我发布上一篇“死磕算法导论”已经过去了一年,按道理来说此处应该有一些感慨,然而实际上我什么感慨也没有。不管怎么样,我写文章只为了给自己看,也就不说那么多了。

那么这篇文章应当从哪里开始呢?说到合并排序,有一个概念是绝对逃不开的,那就是“递归”。递归,根据百度百科所说,是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法。这个概念倒是很简单,很好理解。但是通常来说,越是看起来简单的概念越包含无穷的奥妙。递归本身是个很大的概念,要想理解这个概念,就一定要从实例理解,一步步地感悟递归的哲学。

将“递归”这个概念具体一些,得到的是“分治法”这个思想。分治法的思想,我在这里摘抄《算法导论》的内容。
分治模式在每一层递归上都有三个步骤
* 分解:将原问题分解成一系列子问题;
* 解决:递归地解各子问题。若子问题足够小,则直接求解;
* 合并:将子问题的结果合并成原问题的解。

分治思想
在图中,用实现箭头代表分解问题,用虚线箭头来合并解。这种方法解决问题的意图是什么呢?很显然,原问题太过复杂,我们无法直接解决。但是仔细观察问题我们可以发现,原来的问题可以分解成若干个规模相似,解决方法类似的子问题,子问题又能够按照这种方法来继续细分。我们不断重复这个工作,直到我们无法再继续细分问题。接着,我们解决最底层的问题,然后一步步地合并,最终合并成原问题的规模,最终就可以完美解决原问题。

应用分治法的一个实例便是这篇文章所要讲的合并排序。在这里,我再次摘抄《算法导论》的内容。
合并排序算法完全依照了上述模式,直观地操作如下:
* 分解:将n个元素分成各含n/2个元素的子序列;
* 用合并排序法对两个子序列递归地排序;
* 合并两个已排序的子序列以得到排序结果。

根据《算法导论》的内容,书中直接给出了合并子序列的算法,直接一坨文字和算法甩在我的脸上。对于我来说,从微观到宏观本身不符合我的认知习惯;另外,由于我智商有限,一上来就是抽象的理论未免让我理解起来太过劳累。此时颇为烦恼的我翻到了合并排序的总算法,函数MERGE_SORT(),这里我用C++来表达:

void Merge_Sort(int A[], int p, int r){    int q;    if (p < r)    {        q = (p + r) / 2;        Merge_Sort(A, p, q);        Merge_Sort(A, q + 1, r);        Merge(A, p, q, r);    }}

根据这个算法,我生成了10个随机数,并以画图的方式展示了合并排序的过程:
合并排序过程
可以看出合并排序实际上就是分治的一个实例。一个包含十个数的数组,前五个为一组,后五个为一组,分别对这两组进行合并排序,然后将这两组有序地合并起来。前五个组成的那一组再次分为3个一组的数组和两个一组的数组。然后不停地分下去,最后分到每一组都只有一个数,对这个数进行合并排序。实际上只有一个数的时候已经不能再分了,默认就是一个有序的数组。接下来按照图中虚线箭头方向一层层合并,就能够对原来的数组进行合并。

接下来就到了合并排序最核心的算法了,也就是合并算法。其含义是把某个数组的子数组有序地合并,而分成的两个子数组也应当是有序的。那么具体的合并算法应当如何操作呢?我拿图中13、98、86、36、69来举例。

欲排序数组
这五个数,是A数组的后五个位置,对这个数组排序,其p为6,q为8,r为10。根据总排序算法,这个数组被分为了p~q和q+1~r的两个子数组,并且在进行合并之前都已经经历过合并排序。因此6到8是有序的,9到10是有序的。所以我们真正处理合并操作的时候,我们面对的数组是这样的:

待排序数组

一般来说,图画到这里,一般人大概也就能够看出一些门道了。所谓的合并过程,实际上就是备份整个数组到另外一个数组,然后按照大小依次重写相应位置的数,最终就能够完成合并排序。

可是问题也来了,如何才能“依次”重写呢?显然,备份至一个数组是一定不行的。但是这个数组是经过处理的,可以分为两组,每一组都是有序的。这个组的区分就是索引p~q以及索引q+1~r。那么就可以创立两个数组L和R,分别存储两部分索引的内容。原来的数组因为需要重写,这时可以看做是空的。

重新分解数组

我们的算法可以这么进行:13和36比,13小,13放在(6)号位;86和36比,36小,36放在(7)号位;86和69比,69小,69放在(8)号位。那么接下来呢?虽然很明显(9)号位是86,那么为什么呢?书中提到了两种办法:第一种是设计一种方法来判定R数组为空,如果这个判定为true,那么就可以将86和98直接放在后两个位置;第二种是扩展R,在69后面添加一个量,使得L中内容无论多大都不会有这个量大,那么86理所应当就会置于(9)号位。相应的,L后面也要添加这个量。书中给出的方式是第二种方式。的确,第二种方式是简单的。它仅仅是在每个子数组后面添加了一个量。而对于第一种方式来说,则要设计一个算法来判定比较已经到了结尾。我们要在每个数组之后添加的量,毫无疑问,是无限()。

在子数组后添加无限

至此,我们的排序策略已经非常清晰:
1. 将p~q索引的元素放入L数组,将q+1~r的元素放入R数组
2. L和R分别添加一个元素
3. 两个数组的元素两两比较,较小的放入A数组。
4. 如果L和R最后都只有没有放入A数组,代表整个过程已经完成。
(明日继续)

原创粉丝点击