您的位置:首页 > 其它

算法的领悟(上):分治思想

2010-05-20 09:36 309 查看
5分治思想与递归(上)
我们可以下一个不太严谨的论断,枚举是算法思想的最本源,任何算法问题不是都可以认为是寻找解吗?对枚举进行精化和特化的回溯和分支限界思想,在操作上表现为对解空间的搜索分治思想(Divide and Conquer)与后面要讨论的贪心、动态规划等等则可以认为是对解的构造。当然这其实也可以称之为启发式搜索,我们认为,分治策略的思想起源于对问题解的特性所做出的这样的观察和判断:原问题可以被划分成k个子问题,然后用一种方法将这些子问题的解合并,合并的结果就是原问题的解。既然我们知道了解可以以某种方式构造出来,就没有必要(使用枚举回溯)进行大批量的搜索了。枚举、回溯、分支限界利用了计算机工作的第一个特点:高速,不怕数据量大。分治和多种的各种算法思想利用了计算机工作的第二个特点:重复。

Divide_and_Conquer(P)
{
if(BasicCase(P)) return BasicDealing(P);
else{
divide P into smaller instances P1,P2,…,Pk;
for(int i=1;i<=k;i++) Yi= Divide_and_Conquer(Pi);
return merge(P1,P2,…,Pk);
}
}

分治思想可以做如下的形式化描述:对于一个给定的有n个输入的函数,分治思想建议将输入分为k个不同的子集,1<k≤n,从而产生k个子问题。解决这些子问题,然后用一种方法将这些子问题的解合并得到元问题的解。如果子问题仍然相对较大,就使用同样的方法再次分解成更小的子问题。这种思维的轨迹是如此之简明和直接,以至于完全可以写成很直观的代码设计模式,如右图。
由分治方案产生的子问题经常与原问题属同一类型,这种思想的本身决定了其与递归策略天生的关联性。于是递归技术就成了理解分治算法思想和技术不得不提的东西。然而,递归这般强大而有趣的东西,其丰富的内涵又岂止分治一项?
分治策略的一个极端是每次Divide and Conquer之后,子问题只剩下一个,这时候也就不存在合并(Merge,Combine)问题了,比如二分查找,这种情况下分治策略有一个更贴近的名字:Drease and Conquer,在实际的算法设计中,要能设计Drease and Conquer,效果更好。
分治思想源远流长。根据Wikipedia[1]
Binary search, a divide and conquer algorithm in which the original problem is successively broken down into single subproblems of roughly half the original size, has a long history. The idea of using a sorted list of items to fa cilitate searching dates back as far as Babylonia in 200BC,while a clear description of the algorithm on computers appeared in 1946 in an article by John Mauchly.Another divide and conquer algorithm with a single subproblem is the Euclidean algorithm to compute the greatest common divisor of two numbers (by reducing the numbers to smaller and smaller equivalent subproblems), which dates to several centuries BC.
原来,欧几里德算法也是分治的思想,不过欧几里德算法[2]中也没有所谓的“合并(Merge,Combine)”,这点和二分查找是一样的,所以上述模型也不是绝对的,模型是辅助的分析工具,但不是实践武器。
下面根据《算法导论》和王晓东《计算机算法设计与分析》对分治的论述,做一全面的总结:

Ø 分治法的基本思想

任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,……而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
如果原问题可分割成k个子问题,1<k≤n ,且这些子问题都可解,并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。

Ø 分治法的适用条件

分治法所能解决的问题一般具有以下几个特征:
l 该问题的规模缩小到一定的程度就可以容易地解决;
l 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
l 利用该问题分解出的子问题的解可以合并为该问题的解;
l 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
上述的第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;第二条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用;第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑贪心法或动态规划法。第四条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。

Ø 分治法的基本步骤

分治法在每一层递归上都有三个步骤:
l 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
l 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
l 合并:将各个子问题的解合并为原问题的解。
其实用一种轻松的语言去描述这种思想和策略或许更好。爱因斯坦说,“简单,尽可能简单,但不要太简单”,对于分治思想而言,既道出了分治的思想根源,也说明了分治要注意的东西。哲学的结论告诉我们,世界是复杂的,所以你拿到的问题往往不会是很简单的事情,哲学也告诉我们,复杂的事情总是简单的东西交错在一起或相互牵制或混淆你视听的,所以而再复杂再难的问题都可以分解成一些有限的简单问题的和,这就是分治法的原理。所以,一个很感性化、但又不一定不合理的结论就是:当问题很复杂很复杂,以至于枚举和回溯这样的方法都解不出来,或者说,实在无法接受时,想想分治;假设问题缩小一半后,再放大一倍,你发现了什么的时候,想想分治。我们在第6、7章讨论完贪心和动态规划之后,会说这样一句话:从算法的实际操作上看,分治更多表现为对问题本身的(拆分)分解,如n元数组分解为n/k个子数组,侧重于状态分解,本章前面一个切蛋糕的插图即是此意;贪心和动态规划则是对求解过程的分解,侧重于过程分步。分治更多要发现问题本身等分后得出的子问题具有最优子结构性,它们通过合并的方式得出解;贪心和动态规划则是对构造解的步骤进行分解后,每一步得出的部分解可以通过某种方式继续构造出整体解。
下面我们就先揭秘这个所谓的状态分解(问题拆分)和合并吧。

XY = (A×10n/2+B)×(C×10n/2+D)
=AC×10n/2 + (AD+BC) × 10n/2+ BD ······(1)

状态分解很经典也非常适合的例子就是大数乘法[3]。设X和Y都是n位的整数,现在要计算它们的乘积XY。如果利用小学所学的方法,将每两个一位数都进行相乘[4],最后再相加,效率比较低下,乘法需要n2次。我们看看分治是否可行,只要看问题的条件能否满足分治必须的四个使用条件即可。分治法建议将X等分成两部分A和B,即X=A×10n/2+B,Y也同样处理,即Y=C×10n/2+D,那么:
进一步,AD和BC可以利用AC和BD来表示:

AD+BC=(A-B)×(D-C) + AC + BD ······ (2)

这样(1)的乘法次数由4次减少到3次,最后的运算效率会有所提高。如果我们听过Donald Knuth所引用的Hoare的名言,“不成熟的优化是万恶之源”,但是又难以分辨究竟何种场合下的优化才是不成熟的优化时,此处从(1)到(2)优化算是一个很好的例子。
实际上,本例的结论可以推广开,而这个结论又是极有价值的。分治法在求解两个n位大整数u和v的乘积时,将其分割为n/m的m段,可以使用2m-1次n/m位整数的乘法求出uv的值。
设a=2n/m,则可以将u、v及其乘积w表示为:
且有w=w0+w1x+w2x2+…+w2m-2x2m-2
对于任意相邻两组的 ui,vi,uj+1,vj+1,若uivi,uivj+1,uj+1vi,uj+1vj+1中知道非交叉乘积项,则两次n/m位的交叉乘积项可以由一个n/m位的交叉乘积项表示出来。这一点与(2)式是一样的,所以n/m位乘法的次数共计(m-2)×2+3=2m-1次,对照上面的图可以理解这个表达式的来由。时间复杂度为O(logm(2m-1))。
状态分解(问题等分)另一经典例子是棋盘覆盖。在一个2k×2k个方格组成的棋盘中,若恰有一个方格与其他方格不同,则称该方格为一个特殊方格,如下图右所示。在棋盘覆盖问题中,要用下图左所示几种形态的L型骨牌覆盖一个给定的特殊棋盘,在任何一个2k×2k的棋盘覆盖中用到的L型骨牌个数恰为(4k-1)/3。

分治策略对应着题目一个很简明的算法。但k>0,将一个2k×2k的棋盘分割为4个2k-1×2k-1子棋盘,如下图左所示。特殊方格必定位于4个较小的欺骗之一,其余3个均无特殊方格。为了将这三个棋盘转化为特殊期盼,以便继续“分治”下去,我们用一个L型骨牌覆盖3个较小棋盘的汇合处,如下图右所示,这三个子棋盘上被L型骨牌覆盖的方格就成为该棋盘上的特殊方格,从而原问题转化为4个较小棋盘覆盖问题,这正是“分治”说需要的。
确实,在利用了分治法的四个适用条件确定了分治法的可行性后,首要考虑因素就是是选择“分”“治”什么对象——包括分什么?怎么分?考虑分什么的时候又要考虑分了之后怎么治,但从以上这些例子可以得出一个比较不严谨的结论,适合分治的问题,最好具有比较明显的(或可转化得出的)数组、矩阵类的结构——线性结构,这样利于代码的编写,分治的进行也明朗得多这是针对“分什么”来说的。“怎么分”顺着“分什么”来,对于线性结构,怎么分不会很难发现,不过要注意的一个小问题就是:分几份。前面我们看见的都是二分,但是实际中不会都这样子,大数乘法的理论分析中就出现了分n份。另外还有一个很简单的例子,输出管道问题。
某石油公司计划建造一条由东向西的主输油管道。该管道要穿过一个有n口油井的油田。从每口油井都要有一条输油管道沿最短路径(或南或北)与主管道相连。如果给定n口油井的位置,即它们的x坐标和y坐标,应如何确定主管道的最优位置,即使各油井到主管道之间的输油管道长度总和最小的位置?证明可在线性时间内确定主管道的最优位置。
输出管道问题是很显而易见的中位数问题[5],不过如果要求你用分治呢?很显然,可以对所有点区间递归地三等分取中位数效果最好。
和任何通过子问题构造全局问题的算法一样,分治法在“分”“治”过程中也要注意重复子问题。这个暂时就不多说了,我们把它留到在本章后面的递归中再讨论。
此外分治的著名应用还有归并排序和快速排序,详细内容可以参考本书第二部分。
|||||||||||||||||||||||||||||||||||||||||||||||||
合并问题最好的例子莫过于求最接近点对,如Quoit Design
Have you ever played quoit in a playground? Quoit is a game in which flat rings are pitched at some toys, with all the toys encircled awarded.
In the field of Cyberground, the position of each toy is fixed, and the ring is carefully designed so it can only encircle one toy at a time. On the other hand, to make the game look more attractive, the ring is designed to have the largest radius. Given a configuration of the field, you are supposed to find the radius of such a ring.
Assume that all the toys are points on a plane. A point is encircled by the ring if the distance between the point and the center of the ring is strictly less than the radius of the ring. If two toys are placed at the same point, the radius of the ring is considered to be 0.
Input
The input consists of several test cases. For each case, the first line contains an integer N (2 <= N <= 100,000), the total number of toys in the field. Then N lines follow, each contains a pair of (x, y) which are the coordinates of a toy. The input is terminated by N = 0.
Output
For each test case, print in one line the radius of the ring required by the Cyberground manager, accurate up to 2 decimal places.
Sample Input
Sample Output
2
0 0
1 1
2
1 1
1 1
3
-1.5 0
0 0
0 1.5
0
0.71
0.00
0.75

本题的建模应该还是比较简单的,一言以蔽之,给定平面上n个点,找其中的一对点,使得n个点组成的所有点对中,该点对间距离最小。
这个问题很容易理解[6],似乎也不难解决。我们只要将每一点与其他n-1个点的距离算出,找出达到最小距离的两个点即可。然而,这样做效率太低,需要O(n2)的计算时间。由问题的计算复杂性[7]中我们可以看到,该问题的计算时间下界为Ω(nlogn)。这个下界引导我们去找问题的一个Θ(nlogn)算法。
这个问题显然满足分治法的第一个和第二个适用条件,我们考虑将所给的平面上n个点的集合S分成2个子集S1和S2,每个子集中约有n/2个点,·然后在每个子集中递归地求其最接近的点对。在这里,一个关键的问题是如何实现分治法中的合并步骤,即由S1和S2的最接近点对,如何求得原集合S中的最接近点对,因为S1和S2的最接近点对未必就是S的最接近点对。如果组成S的最接近点对的2个点都在S1中或都在S2中,则问题很容易解决。但是,如果这2个点分别在S1和S2中,则对于S1中任一点p,S2中最多只有n/2个点与它构成最接近点对的候选者,仍需做n2/4次计算和比较才能确定S的最接近点对。因此,依此思路,合并步骤耗时为O(n2)。整个算法所需计算时间T(n)应满足:T(n)=2T(n/2)+O(n2),由主定理,它的解为T(n)=O(n2),即与合并步骤的耗时同阶,显示不出比用穷举的方法好。从解递归方程的套用公式法,我们看到问题出在合并步骤耗时太多。这启发我们把注意力放在合并步骤上。
为了使问题易于理解和分析,我们先来考虑一维的情形。此时S中的n个点退化为x轴上的n个实数x1,x2,..,xn。最接近点对即为这n个实数中相差最小的2个实数。我们显然可以先将x1,x2,..,xn排好序,然后,用一次线性扫描就可以找出最接近点对。这种方法主要计算时间花在排序上,因此如在排序算法中所证明的,耗时为O(nlogn)。然而这种方法无法直接推广到二维的情形。因此,对这种一维的简单情形,我们还是尝试用分治法来求解,并希望能推广到二维的情形。
假设我们用x轴上某个点m将S划分为2个子集S1和S2,使得S1={x∈S|x≤m};S2={x∈S|x>m}。这样一来,对于所有p∈S1和q∈S2有p<q。递归地在S1和S2上找出其最接近点对{p1,p2}和{q1,q2},并设δ=min{|p1-p2|,|q1-q2|},S中的最接近点对或者是{p1,p2},或者是{q1,q2},或者是某个{p3,q3},其中p3∈S1且q3∈S2。如右图所示。
我们注意到,如果S的最接近点对是{p3,q3},即|p3-q3|<δ,则p3和q3两者与m的距离不超过δ,即|p3-m|<δ,|q3-m|<δ,也就是说,p3∈(m-δ,m],q3∈(m,m+δ]。由于在S1中,每个长度为δ的半闭区间至多包含一个点(否则必有两点距离小于δ),并且m是S1和S2的分割点,因此(m-δ,m]中至多包含S中的一个点。同理,(m,m+δ]中也至多包含S中的一个点。由图可以看出,如果(m-δ,m]中有S中的点,则此点就是S1中最大点。同理,如果(m,m+δ]中有S中的点,则此点就是S2中最小点。因此,我们用线性时间就能找到区间(m-δ,m]和(m,m+δ]中所有点,即p3和q3。从而我们用线性时间就可以将S1的解和S2的解合并成为S的解。也就是说,按这种分治策略,合并步可在O(n)时间内完成。这样是否就可以得到一个有效的算法了呢?还有一个问题需要认真考虑,即分割点m的选取,及S1和S2的划分。选取分割点m的一个基本要求是由此导出集合S的一个线性分割,即S=S1∪S2 ,S1∩S2=Φ,且S1 {x|x≤m};S2 {x|x>m}。容易看出,如果选取m=[max(S)+min(S)]/2,可以满足线性分割的要求。选取分割点后,再用O(n)时间即可将S划分成S1={x∈S|x≤m}和S2={x∈S|x>m}。然而,这样选取分割点m,有可能造成划分出的子集S1和S2的不平衡。例如在最坏情况下,|S1|=1,|S2|=n-1,由此产生的分治法在最坏情况下所需的计算时间T(n)应满足递归方程T(n)=T(n-1)+O(n),它的解是T(n)=O(n2)。这种效率降低的现象可以通过分治法中“平衡子问题”的方法加以解决。也就是说,我们可以通过适当选择分割点m,使S1和S2中有大致相等个数的点。自然地,我们会想到用S的n个点的坐标的中位数来作分割点。在选择算法中介绍的选取中位数的线性时间算法使我们可以在O(n)时间内确定一个平衡的分割点m。由以上的分析可知,该算法的分割步骤和合并步骤总共耗时O(n)。因此,算法耗费的计算时间T(n)满足递归方程:

解此递归方程可得T(n)=O(nlogn)。这个算法看上去比用排序加扫描的算法复杂,然而这个算法可以向二维推广。下面我们来考虑二维的情形。此时S中的点为平面上的点,它们都有2个坐标值x和y。为了将平面上点集S线性分割为大小大致相等的2个子集S1和S2,我们选取一垂直线l:x=m来作为分割直线。其中m为S中各点x坐标的中位数。由此将S分割为S1={p∈S|px≤m}和S2={p∈S|px>m}。从而使S1和S2分别位于直线l的左侧和右侧,且S=S1∪S2 。由于m是S中各点x坐标值的中位数,因此S1和S2中的点数大致相等。
递归地在S1和S2上解最接近点对问题,我们分别得到S1和S2中的最小距离δ1和δ2。现设δ=min(δ1,δ1)。若S的最接近点对(p,q)之间的距离d(p,q)<δ则p和q必分属于S1和S2。不妨设p∈S1,q∈S2。那么p和q距直线l的距离均小于δ。因此,我们若用P1和P2分别表示直线l的左边和右边的宽为δ的2个垂直长条,则p∈S1,q∈S2,如右图所示。

在一维的情形,距分割点距离为δ的2个区间(m-δ,m]和(m,m+δ]中最多各有S中一个点。因而这2点成为唯一的末检查过的最接近点对候选者。二维的情形则要复杂些,此时,P1中所有点与P2中所有点构成的点对均为最接近点对的候选者。在最坏情况下有n2/4对这样的候选者。但是P1和P2中的点具有以下的稀疏性质,它使我们不必检查所有这n2/4对候选者。考虑P1中任意一点p,它若与P2中的点q构成最接近点对的候选者,则必有d(p,q)<δ。满足这个条件的P2中的点有多少个呢?容易看出这样的点一定落在一个δ×2δ的矩形R中,如下图所示。

由δ的意义可知P2中任何2个S中的点的距离都不小于δ。由此可以推出矩形R中最多只有6个S中的点。事实上,我们可以将矩形R的长为2δ的边3等分,将它的长为δ的边2等分,由此导出6个(δ/2)×(2δ/3)的矩形。如下图左所示。

若矩形R中有多于6个S中的点,则由鸽舍原理易知至少有一个δ×2δ的小矩形中有2个以上S中的点。设u,v是这样2个点,它们位于同一小矩形中,则

因此d(u,v)≤5δ/6<δ 。这与δ的意义相矛盾。也就是说矩形R中最多只有6个S中的点。上图右是矩形R中含有S中的6个点的极端情形。由于这种稀疏性质,对于P1中任一点p,P2中最多只有6个点与它构成最接近点对的候选者。因此,在分治法的合并步骤中,我们最多只需要检查6×n/2=3n对候选者,而不是n2/4对候选者。这是否就意味着我们可以在O(n)时间内完成分治法的合并步骤呢?现在还不能作出这个结论,因为我们只知道对于P1中每个S1中的点p最多只需要检查P2中的6个点,但是我们并不确切地知道要检查哪6个点。为了解决这个问题,我们可以将p和P2中所有S2的点投影到垂直线l上。由于能与p点一起构成最接近点对候选者的S2中点一定在矩形R中,所以它们在直线l上的投影点距p在l上投影点的距离小于δ。由上面的分析可知,这种投影点最多只有6个。因此,若将P1和P2中所有S的点按其y坐标排好序,则对P1中所有点p,对排好序的点列作一次扫描,就可以找出所有最接近点对的候选者,对P1中每一点最多只要检查P2中排好序的相继6个点。显而易见T(n)=O(nlogn),预排序所需的计算时间为O(n1ogn)。因此,整个算法所需的计算时间为O(nlogn)。在渐近的意义下,此算法已是最优的了。

下面是一个没有仔细优化的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <cmath>
#include <iostream>
using namespace std;
struct Node
{
double x,y;
}pt[100005];

int Y_sort[100005];
double MinN(double a,double b)
{
if(a<b) return a;
else return b;
}
inline double Distance(Node &a,Node &b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int CMP_X(const void *a,const void *b)
{
if(((Node*)a)->x<((Node*)b)->x) return -1;
else return 1;
}
int CMP_Y(const void *a,const void *b)
{
if(pt[(*(int*)a)].y<pt[(*(int*)b)].y) return -1;
else return 1;
}
double Nearest_Point_Distance(int first,int end)//first end 指点在pt数组中的下标,并假设pt已经按x排序
{
if(end-first==1) return Distance(pt[first],pt[first+1]);
else if(end-first==2) return MinN(MinN(Distance(pt[first],pt[first+1]),Distance(pt[first+1],pt[first+2])),Distance(pt[first],pt[first+2]));
int mid=(first+end)/2;
double min_dist=MinN(Nearest_Point_Distance(first,mid),Nearest_Point_Distance(mid+1,end));
int Y_end=0;
for(int i=mid;i>=first&&(pt[mid].x-pt[i].x)<min_dist;i--)
{
Y_sort[Y_end++]=i;
}
for(int i=mid+1;i<=end&&(pt[i].x-pt[mid+1].x)<min_dist;i++)
{
Y_sort[Y_end++]=i;
}
qsort(Y_sort,Y_end,sizeof(Y_sort[0]),CMP_Y);
/*for(int i=0;i<Y_end;i++)
{
for(int j=1;j<8&&i+j<Y_end;j++)
{
min_dist=MinN(min_dist,Distance(pt[Y_sort[i]],pt[Y_sort[i+j]]));
}
}*/
for(int i=0;i<Y_end;i++)//上面那种也可以,但貌似速度慢点
{
for(int j=i+1;j<Y_end&&pt[Y_sort[j]].y-pt[Y_sort[i]].y<min_dist;j++)
{
min_dist=MinN(min_dist,Distance(pt[Y_sort[i]],pt[Y_sort[j]]));
}
}
return min_dist;
}
int main()
{
int n;
while (scanf("%d",&n)&&n)
{
for (int i=0;i<n;i++)
scanf("%lf%lf", &pt[i].x, &pt[i].y);
qsort(pt,n,sizeof(pt[0]),CMP_X);
printf("%.2f/n", Nearest_Point_Distance(0,n-1)/2);
}
return 0;
}
本例中说表现出来的合并问题是相当有代表性的,即在“分”步骤中所取的交界处出现临界问题,这几乎是所有分治算法的实现中都要考虑的问题,在归并排序中这一问题也体现了很明显。
|||||||||||||||||||||||||||||||||||||||||||||||||
以上我们分析了分治法的解题思想,它的具体操作策略以及在具体实现中要注意的问题,我们会在章节末尾再做一总结。在实际运用中,分治法往往都具有很好的效率,O(nlogn) 是它典型的时间开销。尤其,分治法中“该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题”的特性非常适合于并行计算,这样又大大提高了运行效率;此外,Divide-and-conquer algorithms naturally tend to make efficient use of memory caches,原因在于当子问题小到一定程度时,原则上计算机可以在高速缓存中解决它们,而且子问题越小被再次引用的概率就越高,根据计算机运行的局部性原理(locality),Cache Hit概率也会很高。

[1] http://en.wikipedia.org/wiki/Divide_and_conquer_algorithm

[2] 详细内容见本书第二部分。

[3] 本例来自王晓东《计算机算法的设计与分析》。

[4] 我们是否可以做一个草率的定义,把一切直观的,没有任何“机心”的思路都称之为“暴力算法”。

[5] 中位数问题可见本书第三部分。

[6] 以下分析内容来自王晓东《计算机算法的设计与分析》,尽管重复,但是本题既经典,该书也分析得相当好!

[7] 下界理论将在本书第9章详细介绍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: