漫谈算法(五)问题复杂度分析(Problem Complexity and Adversarial Lower Bound)

Keywords: Problem Complexity; Adversarial Strategy; Lower Bound.

[为什么写这类文章]   漫谈算法(零)序

[这系列文章里会用到的一下符号和公式]   漫谈算法(番外篇) 符号标记以及基本数学公式

通常我们说到某个算法,我们经常关心他的时间复杂度,当然,我们通常关心的是这个时间复杂度的upper bound,即上界。upper bound告诉我们在某些很糟糕的情况下,我们的算法的性能。比如我们的基于比较的排序算法,我们知道mergesort(归并排序)是O(nlgn),但是我们应该能提出这样的疑问,can we do it better?(我们能做的更好吗?) 所以,在这篇文章里,我们将简单介绍一下对于算法的下界,lower bound,它告诉我们对于某个问题,是否存在更好的算法。 lower bound告诉我们什么不能做到

"Lower bounds tell us what cannot be done, they are very important in guiding us in our search for what can be done."

(当然对于lower bound的问题,我自己是google了一下,中文方面的资料很少,据本人了解高校内算法课涉及lower bound的也几乎没有,但google本文上面的keywords,可以看到北美的各大高校基本全部都有相关知识章节,本科和研究生都有,莫非这就是教育的差异??!!这里就权当一个入门的tutorial了)

回归主题:

当然,我们知道,所谓lower bound,也就是找Omega(n)了。(不清楚的童鞋可以看一下文章开头给的链接)

还是决定举一个例子慢慢展开。

比如我们知道基于比较的排序算法最好也就是nlgn了,所以,这也就是说,我们应该能证明sorting = Omega(nlgn),当然,这里的sorting是指任意的排序算法。这时我们就会发现,问题出来了,我们要证明任意算法都满足一个条件,任意,任意....一些传统的举反例啊,针对某个算法的内部分析啊什么的都不能用了。

对于找解决某一问题的那一类算法的lower bound,我们通常有两种办法,

1)decision tree 我们通过分析决策树的方法来得到lower bound(这也是算法导论里面提到的方法)

2)adversary strategy 这个确实不好翻译,竞争者策略?! 当然这个方法要比上面的decision tree得到的lower bound更tight一些。(当然这个也不绝对,还要看你的strategy)

鉴于基本上lower bound的引入都是通过sorting这个人神共知的algorithm引入的,我这里也不非主流了。

下面先用sorting的lower bound的例子来分别解释一下上面提到的两个方法,然后在举一些例子帮助大家加深理解。恩。就这样。

Let's get started~

对于基于比较的排序,我们知道,这里排序的依据是比较!(这真是句大实话,同时也是句废话O(∩_∩)O~)

现在假设我们待比较的数据是  a1,a2,...,an,我们知道对于这n个数据(假设里面没有相等的元素),我们可以对这n个数据做一个全排列,里面有n!种。那么什么是decision tree呢?偷个懒,截了个算法导论的图。

专门截了那么一大段文字,因为讲的很详细,希望不太明白的童鞋都先仔细看了再说。每个非叶子节点的数字可以想象成待比较的元素ai的下标i,其中root表示比较a1和a2,如果是小于走左枝,大于走右枝。下面类似。对于叶子节点,就是一个最后的算有元素下标的全排列,当然这也就是一个可能的最后的排序的结果了。这个应该还是很好理解的,不好理解的我在啰嗦一句,比如 输入是a1,a2,a3,a4,  我最后有个叶子节点是 [ 2 4 1 3],这也就表示最后的排序结果是a2,a4,a1,a3。

这棵树看起来很简单,当然,他还是能给我很多有用的信息的,首先我们知道比较算法的复杂度和进行比较的次数紧密相关的。我们可以从树上看出,从root到leaf,一路上经历的比较次数,就是我们这个分支的高度,当然,很明显,我们是要考虑这棵树的最大的高度,也就是最长的那支。它表示了在最坏的情况下,我们需要的比较次数(这里好像和树的高度的offical的定义有冲突了,明白意思就好O(∩_∩)O~)

好,现在我们就开始找lower bound了~很显然,我们这棵树是二叉的,大于向左,小于向右,我们最后的高度是h,我们知道完全二叉树高为h的叶子最多有2^h个,同时我们也是到n个数据,最多有n!个排列,所以根据我们的decision tree,我们可以得到:

n! <= 2^h

当然简单变形一下,h>= lg(n!) 

所以 h = Omega(nlgn)。(不明真相的童鞋请看开头给的链接,里面有一些basic的数学公式)

这说明了什么,这说明了对于基于比较的排序算法,我们至少需要Omega(nlgn)次比较,不然最下面那支的叶子节点的结果我们就得不到。当然,最重要的的是,我们这里得出的结论是对于任意的排序算法,任意的哦!

下面再来看看如何利用Adversary Strategy来得到这个lower bound。

首先解释一下Adversary Strategy的思路:

我们可以想象存在一个Adversary,当然我们中文里应该就叫神~有这么一个神,当我们在进行比较的时候,我们不能立刻得到比较的结果,我们要去问神,只有神知道,(神的力量无法无天....跑了....回到主题),每当我们要进行比较,都要从神那里得到比较的结果。

当然对于神来说,它总是希望拖延我们,然后尽量多的询问它 关于比较的结果。 所以在我们和神直接就存在了一种adversarial的关系。

针对于sorting,我们知道一共有n!种可能,比如a1,a2,a3,我们的可能的结果有[1 2 3] [1 3 2] [2 1 3] [2 3 1] [ 3 1 2 ] [ 3 2 1 ],姑且叫它候选集合S,每当我们从神的那里得到两个数据的比较结果之后,我们就可以从候选集合中剔除若干元素,缩小候选集合的大小|S|,当|S|=1的时候,我们的任务就完成了。而神要做的是什么呢?神要做的就是他每次都要告诉我们一个结果,但是它总是想尽力的让我们的候选集合剩下的元素尽可能的多。不难看出,每次都让我们剩下的size大于等于前一次的一般,也就是我们每从神那里得到一个答案后,最多把候选集合里面的东西砍掉一般。所以为了让|S|从n!到1,我们至少需要问lg(n!)次,所以lower bound是Omega(nlgn)。当然如果我们没有问到nlgn次,我们的候选集合里面至少有两个元素,这时候算法要给出一个结果,而神总会说答案是另外的,这样我们的算法就fail了,这也就说明了小于nlgn的都是不行的。(有点类似反证)

上面证明了sorting的在worst case下的lower bound,下面我们还可以附带证明在average case的情况下,sorting的lower bound也是Omega(nlgn)。

其实通过上面的decision tree的方法,我们已经知道,找average case下的lower bound,其实也就是找那棵树的所有分支高度的平均值的lower bound。

如果我们的树是平衡二叉树,我们知道,每个分支都是nlgn,所以average case的lower bound就是Omega(nlgn),但是对于根据比较得到的decision tree不是平衡二叉树呢?其实我们只要证明 the one that minimizes their average depth is a completely balanced tree。这个其实也很显然,如果不是平衡二叉树,说明最深的节点减去最浅的节点的高度差大于等于二,这时我们可以把最深的那两个节点移接到最浅的那支上,这样我们减小了average heights,但是没有影响到别的,这也就说明了只有对于任何不平衡的树,我们都可以使他的average height更小一些,所以也就说明了average depth of leaves must be at least nlgn。证毕。

顺便可以证明一下对于所有的randomized sorting algorithm,比较次数的lower bound也是nlgn。http://en.wikipedia.org/wiki/Randomized_algorithm

决定把这个放到下一篇文章中讲。O(∩_∩)O~

后面举几个例子来简单具体阐述一下上面说的两个方法。

例 有两个长度为n的已经排好的list,a1<a2<...<an, b1<b2<...<bn,我们要把这两个list合并到一个list里面,保证这个list是increasing的,要证明在合并的过程中比较次数的lower bound是 2n-1.

假设我们用adversarial strategy证明,假设我们只比较了2n-2次,也就是我们只想神询问了2n-2次,这时我们知道有一个元素ai,他一定是没有和bj或者是bj+1比较的,假设我们的ai = 2i-1,也就是所有的奇数,bi = 2i,所有的偶数,那么这个时候,我们的算法已经给出了output,但是对于无所不能的神来说,它知道你没有比较ai和bj或者是没有比较ai和bj+1。

如果你没有比较ai和bi,你输出的是aibi,但是神可以交换ai和bi的值,这个时候ai = 2i,bi = 2i-1,交换的同时,依然保证a1<a2<...<an, b1<b2<...<bn,所以这个时候,你的算法错了。

同理,如果你没有比较ai和bi+1,神依旧可以交换ai和bj+1的值,导致你的算法还是错的,所以你的2n-2的算法悲剧了,所以证明lower bound是2n-1。

例 给定一个长度为n的已经排好的list,a1<a2<...<an, 和一个数x,要找这个list里面有没有一个ai,ai==x。 给一个比较次数的lower bound,并证明。

这个我们可以用decision tree的方法找到这个lower bound,考虑下面的decision tree:

从这个树上我们可以得出:

1)对于每一个非叶子节点,都有两个分支。

2)这个树一共有n个叶子节点(对应了我们n个可能的答案)

假设树的高度是h,我们知道最多可能出现的叶子节点数是 2^0+2^1+2^2+。。。+2^k-1,这是等于2^k-1的,当然,这个是大于等于n的,所以,就有 2^k-1 >=n, 可以得到 k >= lg(n+1),这也就是,一路比较下去我们至少要用 lg(n+1)次比较。

例 给一组无序的n个数,找出这n个数的最大值和最小值。证明比较次数的upper bound和lower bound都是 3n/2-2.

对于某个问题的upper bound的证明,我们只需要给出一个算法,这个算法的upper bound是要证的就行了;而对于一个问题的lower bound的证明,我们就需要证明对于任意解决这个问题的算法,都需要这个bound,所以就需要用到decision tree或者是adversarial strategy。

对于upper bound,我们的算法如下,我们每次从n个数中取两个进行比较,这样就比较了n/2次,每次把比较中较大的数放到大集合Big中,把较小的数放到小集合Small中,这样,Big集合的size是n/2,Small集合的size也是n/2.这样我们分别在Big集合中比较n/2-1次得到最大的元素,在small集合中比较n/2-1次得到最小的元素,这样我们的比较次数就是 n/2+n/2-1+n/2-1 = 3n/2-2。

对于lower bound,我们用adversarial strategy来证明,假设一开始我们把所有的元素都给予标记“+/-”,所有头上为“+”的代表它可能是最大的元素,头上为“-”的代表它可能是最小的元素,所以现在每次比较都去掉一些元素标记,直到最后有一个“+”和一个“-”,其他的头上都没有标记为止,这样也就表明了我们找到了最大元素和最小元素。很显然,我们一共去掉了2n-2个标记,也就是我们要求神在若干次比较之后去掉2n-2个标记,但是可能有一些比较,可以一下子去掉两个标记,比如两个元素都是“+/-”的进行比较,我们去掉大元素的“-”,去掉小元素的“+”;有些比较只能去掉一个标记。我们知道最多有n/2次比较可以一下子去掉2个marks,其他的一次比较都只能去掉1个mark,所以我们一共要去掉2n-2个标记,所以就至少需要 2n-2-n/2 = 3n/2-2次比较。


========================================================================================

本作品采用知识共享署名 2.5 中国大陆许可协议进行许可。 本博客采用知识共享署名 2.5 中国大陆许可协议进行许可。本博客版权归作者所有,欢迎转载,但未经作者同意不得随机删除文章任何内容,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如您有任何疑问或者授权方面的协商,请给留言。(http://www.cnblogs.com/Gavin_Liu/)

posted @ 2011-04-16 05:47  Gavin.Liu  阅读(6411)  评论(1编辑  收藏  举报