声振论坛

 找回密码
 我要加入

QQ登录

只需一步,快速开始

查看: 3625|回复: 7

[经典算法] [转帖]算法的复杂性

[复制链接]
发表于 2005-9-15 07:33 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?我要加入

x
<P>傅清祥 王晓东<br><CITE>算法与数据结构</CITE> , 电子工业出版社,1998</P>
<P><B>摘要</B></P>
<P>本文介绍了算法的复杂性的概念和衡量方法,并提供了一些计算算法的复杂性的渐近阶的方法。</P>
<P><B>目录</B></P>
<UL>
<LI>简介 <br>
<LI>比较两对算法的效率 <br>
<LI>复杂性的计量 <br>
<LI>复杂性的渐近性态及其阶 <br>
<LI>复杂性渐近阶的重要性 <br>
<LI>算法复杂性渐近阶的分析 <br>
<LI>递归方程组的渐近阶的求法 <br>
<LI>1.代入法 <br>
<LI>2.迭代法 <br>
<LI>3.套用公式法 <br>
<LI>4.差分方程法 <br>
<LI>5.母函数法 </LI></UL>
[此贴子已经被作者于2005-9-15 7:33:41编辑过]

回复
分享到:

使用道具 举报

 楼主| 发表于 2005-9-15 07:34 | 显示全部楼层

回复:(vibration)[转帖]算法的复杂性

<H2>简介</H2>
<P><DFN>算法的复杂性</DFN>是算法效率的度量,是评价算法优劣的重要依据。一个算法的复杂性的高低体现在运行该算法所需要的计算机资源的多少上面,所需的资源越多,我们就说该算法的复杂性越高;反之,所需的资源越低,则该算法的复杂性越低。 <br>
<P>计算机的资源,最重要的是时间和空间(即存储器)资源。因而,算法的复杂性有<DFN>时间复杂性</DFN>和<DFN>空间复杂性</DFN>之分。 <br>
<P>不言而喻,对于任意给定的问题,设计出复杂性尽可能地的算法是我们在设计算法是追求的一个重要目标;另一方面,当给定的问题已有多种算法时,选择其中复杂性最低者,是我们在选用算法适应遵循的一个重要准则。因此,算法的复杂性分析对算法的设计或选用有着重要的指导意义和实用价值。 <br>
<P>关于算法的复杂性,有两个问题要弄清楚: </P>
<OL>
<LI>用怎样的一个量来表达一个算法的复杂性; <br>
<LI>对于给定的一个算法,怎样具体计算它的复杂性。 </LI></OL>
<P>让我们从比较两对具体算法的效率开始。</P>
[此贴子已经被作者于2005-9-15 7:34:33编辑过]

 楼主| 发表于 2005-9-15 07:34 | 显示全部楼层

回复:(vibration)[转帖]算法的复杂性

<H2>比较两对算法的效率</H2>
<P>考虑问题1:已知不重复且已经按从小到大排好的m个整数的数组A[1..m](为简单起见。还设m=2<SUP> k</SUP>,k是一个确定的非负整数)。对于给定的整数c,要求寻找一个下标i,使得A=c;若找不到,则返回一个0。</P>
<P>问题1的一个简单的算法是:从头到尾扫描数组A。照此,或者扫到A的第i个分量,经检测满足A=c;或者扫到A的最后一个分量,经检测仍不满足A=c。我们用一个函数Search来表达这个算法:</P><PRE><CODE>Function Search (c:integer):integer;<br>Var J:integer; <br>Begin<br> J:=1; {初始化}<br> {在还没有到达A的最后一个分量且等于c的分量还没有找到时,<br> 查找下一个分量并且进行检测} <br> While (A&lt;c)and(j&lt;m) do <br>         j:=j+1; <br> If A[j]=c then search:=j {在数组A中找到等于c的分量,且此分量的下标为j} <br>           else Search:=0; {在数组中找不到等于c的分量}<br>End;</CODE></PRE>
<P>容易看出,在最坏的情况下,这个算法要检测A的所有m个分量才能判断在A中找不到等于c的分量。</P>
<P>解决问题1的另一个算法利用到已知条件中A已排好序的性质。它首先拿A的中间分量A[m/2]与c比较,如果A[m/2]=c则解已找到。如果A[m/2]&gt;c,则c只可能在A[1],A[2],..,A[m/2-1]之中,因而下一步只要在A[1], A[2], .. ,A[m/2-1]中继续查找;如果A[m/2]&lt;c,则c只可能在A[m/2+1],A[m/2+2],..,A[m]之中,因而下一步只要在A[m/2+1],A[m/2+2],..,A[m]中继续查找。不管哪一种情形,都把下一步需要继续查找的范围缩小了一半。再拿这一半的子数组的中间分量与c比较,重复上述步骤。照此重复下去,总有一个时候,或者找到一个i使得A=c,或者子数组为空(即子数组下界大于上界)。前一种情况找到了等于c的分量,后一种情况则找不到。</P>
<P>这个新算法因为有反复把供查找的数组分成两半,然后在其中一半继续查找的特征,我们称为二分查找算法。它可以用函数B_Search来表达: </P><PRE><CDOE>Function B_Search ( c: integer):integer;<br>Var<br> L,U,I : integer;  {U和L分别是要查找的数组的下标的上界和下界}<br> Found: boolean;<br>Begin<br> L:=1; U:=m;   {初始化数组下标的上下界}<br> Found:=false; {当前要查找的范围是A[L]..A[U]。}<br> {当等于c的分量还没有找到且U&gt;=L时,继续查找}<br> While (not Found) and (U&gt;=L) do<br>  Begin<br>   I:=(U+L) div 2;  {找数组的中间分量}   <br>   If c=A[I] then Found:=Ture<br>             else if c&gt;A[I] then L:=I+1 <br>                               else U:=I-1;   <br>  End;  <br> If Found then B_Search:=1<br>          else B_Search:=0;<br>End;</PRE>
<P>容易理解,在最坏的情况下最多只要测A中的k+1(k=logm,这里的log以2为底,下同)个分量,就判断c是否在A中。<img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img1.gif"></P>
<P>算法Search和B_Search解决的是同一个问题,但在最坏的情况下(所给定的c不在A中),两个算法所需要检测的分量个数却大不相同,前者要m=2<SUP> k</SUP>个,后者只要k+1个。可见算法B_Search比算法Search高效得多。</P>
<P>以上例子说明:解同一个问题,算法不同,则计算的工作量也不同,所需的计算时间随之不同,即复杂性不同。</P>
<P>上图是运行这两种算法的时间曲线。该图表明,当m适当大(m&gt;m<SUB>0</SUB>)时,算法B_Search比算法Search省时,而且当m更大时,节省的时间急剧增加。</P>
<P>不过,应该指出:用实例的运行时间来度量算法的时间复杂性并不合适,因为这个实例时间与运行该算法的实际计算机性能有关。换句话说,这个实例时间不单纯反映算法的效率而是反映包括运行该算法的计算机在内的综合效率。我们引入算法复杂性的概念是为了比较解决同一个问题的不同算法的效率,而不想去比较运行该算法的计算机的性能。因而,不应该取算法运行的实例时间作为算法复杂性的尺度。我们希望,尽量单纯地反映作为算法精髓的计算方法本身的效率,而且在不实际运行该算法的情况下就能分析出它所需要的时间和空间。</P>
[此贴子已经被作者于2005-9-15 7:34:49编辑过]

 楼主| 发表于 2005-9-15 07:35 | 显示全部楼层

回复:(vibration)[转帖]算法的复杂性

<H2>复杂性的计量</H2>
<P>算法的复杂性是算法运行所需要的计算机资源的量,需要的时间资源的量称作时间复杂性,需要的空间(即存储器)资源的量称作空间复杂性。这个量应该集中反映算法中所采用的方法的效率,而从运行该算法的实际计算机中抽象出来。换句话说,这个量应该是只依赖于算法要解的问题的规模、算法的输入和算法本身的函数。如果分别用<I>N、I</I>和<I>A</I>来表示算法要解问题的规模、算法的输入和算法本身,用<I>C</I>表示算法的复杂性,那么应该有:</P>
<P><I>C </I>=<I>F</I>(<I>N,I,A</I>)</P>
<P>其中<I>F(N,I,A)</I>是<I>N,I,A</I>的一个确定的三元函数。如果把时间复杂性和空间复杂性分开,并分别用<I>T</I>和<I>S</I>来表示,那么应该有:</P>
<P><I>T </I>=<I>T</I>(<I>N,I,A</I>) (2.1)</P>
<P>和 <I>S </I>=<I>S</I>(<I>N,I,A</I>) (2.2)</P>
<P>通常,我们让A隐含在复杂性函数名当中,因而将(2.1)和(2.2)分别简写为</P>
<P><I>T </I>=<I>T</I>(<I>N,I</I>)</P>
<P>和 <I>S </I>=<I>S</I>(<I>N,I</I>)</P>
<P>由于时间复杂性和空间复杂性概念类同,计算方法相似,且空间复杂性分析相对地简单些,所以下文将主要地讨论时间复杂性。</P>
<P>下面以<I>T</I>(<I>N,I</I>)为例,将复杂性函数具体化。</P>
<P>根据<I>T</I>(<I>N,I</I>)的概念,它应该是算法在一台抽象的计算机上运行所需的时间。设此抽象的计算机所提供的元运算有<I>k</I>种,他们分别记为<I>O</I><SUB>1</SUB><I>,O</I><SUB>2 </SUB><I>,..,O</I><SUB>k</SUB>;再设这些元运算每执行一次所需要的时间分别为<I>t</I><SUB>1</SUB><I>,t</I><SUB>2</SUB><I>,</I>..<I>,t</I><SUB>k</SUB> 。对于给定的算法<I>A</I>,设经过统计,用到元运算<I>O</I><SUB>i</SUB>的次数为<I>e</I><SUB>i</SUB><I>,</I>i<I>=1,2,..,k</I> ,很明显,对于每一个i<I>,1&lt;=</I>i<I>&lt;=k,e</I><SUB>i</SUB>是<I>N</I>和<I>I</I>的函数,即<I>e</I><SUB>i</SUB>=<I>e</I><SUB>i</SUB>(<I>N,I</I>)。那么有:</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img2.gif"> (2.3)</P>
<P>其中<I>t</I><SUB>i</SUB>,i=1,2,..,k,是与<I>N,I</I>无关的常数。</P>
<P>显然,我们不可能对规模<I>N</I>的每一种合法的输入<I>I</I>都去统计<I>e</I><SUB>i</SUB>(<I>N,I</I>),i=1,2,…,k。因此<I>T</I>(<I>N,I</I>)的表达式还得进一步简化,或者说,我们只能在规模为<I>N</I>的某些或某类有代表性的合法输入中统计相应的<I>e</I><SUB>i<I> </I></SUB><I>, </I>i<I>=1,2,…,k</I>,评价时间复杂性。</P>
<P>下面只考虑三种情况的复杂性,即最坏情况、最好情况和平均情况下的时间复杂性,并分别记为<I>T</I><SUB>max</SUB><I>(N )、T</I><SUB>min</SUB>(<I>N</I>)和<I>T</I><SUB>avg</SUB>(<I>N </I>)。在数学上有:</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img5.gif"> (2.4)</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img7.gif"> (2.5)</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img9.gif"> (2.6)</P>
<P>其中,<I>D<SUB>N</SUB></I>是规模为<I>N</I>的合法输入的集合;<I>I</I><SUP> *</SUP>是<I>D<SUB>N</SUB></I>中一个使<I>T</I>(<I>N,I</I><SUP> *</SUP>)达到<I>T</I><SUB>max</SUB><I>(N)</I>的合法输入,<SUB><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img11.gif"></SUB>是<I>D<SUB>N</SUB></I>中一个使<I>T(N,</I><SUB><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img12.gif"></SUB>)到<I>T</I><SUB>min</SUB><I>(N)</I>的合法输入;而<I>P</I>(<I>I</I>)是在算法的应用中出现输入<I>I </I>的概率。</P>
<P>以上三种情况下的时间复杂性各从某一个角度来反映算法的效率,各有各的用处,也各有各的局限性。但实践表明可操作性最好的且最有实际价值的是最坏情况下的时间复杂性。下面我们将把对时间复杂性分析的主要兴趣放在这种情形上。</P>
<P>一般来说,最好情况和最坏情况的时间复杂性是很难计量的,原因是对于问题的任意确定的规模N达到了<I>T</I><SUB>max</SUB><I>(N)</I>的合法输入难以确定,而规模N的每一个输入的概率也难以预测或确定。我们有时也按平均情况计量时间复杂性,但那时在对<I>P(I)</I>做了一些人为的假设(比如等概率)之后才进行的。所做的假设是否符合实际总是缺乏根据。因此,在最好情况和平均情况下的时间复杂性分析还仅仅是停留在理论上。</P>
<P>现在以<a href="http://algorithm.diy.myrice.com/algorithm/complexity/chapter1.htm" target="_blank" >上一章</A>提到的<a href="http://algorithm.diy.myrice.com/algorithm/complexity/chapter1.htm#pro1" target="_blank" >问题1</A>的<a href="http://algorithm.diy.myrice.com/algorithm/complexity/chapter1.htm#search" target="_blank" >算法Search</A>为例来说明如何利用(2.4)-(2.6)对它的<I>T</I><SUB>max</SUB><I>、T</I><SUB>min</SUB>和<I>T</I><SUB>avg</SUB>进行计量。这里问题的规模以m计算,算法重用到的元运算有赋值、测试和加法等三种,它们每执行一次所需的时间常数分别为<I>a,t</I>,和<I>s </I>。对于这个例子,如假设c在A中,那么容易直接看出最坏情况的输入出现在c<I>=A</I>[m]的情形,这时:</P>
<P><I>T</I><SUB>max</SUB>(<I>m</I>)=<I>a</I>+2<I>mt</I>+(<I>m</I>-1)<I>s</I>+(<I>m</I>-1)<I>a</I>+<I>t</I>+<I>a</I>=(<I>m</I>+1)<I>a</I>+(2<I>m</I>+1)<I>t</I>+(<I>m</I>-1)<I>s</I> (2.7)</P>
<P>而最好情况的输入出现在c=A[1]的情形。这时:</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img15.gif"> (2.8)</P>
<P>至于<I>T</I><SUB>avg</SUB>(<I>m</I>),如前所述,必须对<I>D</I><SUB>m</SUB>上的概率分布做出假设才能计量。为简单起见,我们做最简单的假设:<I>D</I><SUB>m</SUB>上的概率分布是均等的,即<I>P</I>(<I>A</I>=c)=<I>1/m </I>。若记<I>T</I><SUB>i</SUB><I>=T</I>(<I>m,I</I><SUB>i</SUB>),其中<I>I</I><SUB>i</SUB>表示<I>A</I>=c的合法输入,那么:</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img18.gif"> (2.9)</P>
<P>而根据与(2.7)类似的推导,有:</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img21.gif"></P>
<P>代入(2.9) ,则:</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img23.gif"></P>
<P>这里碰巧有:</P>
<P><I>T</I><SUB>avg</SUB>(<I>m</I>)=(<I>T</I><SUB>max</SUB>(<I>m</I>)+<I>T<SUB>min</SUB></I>(<I>m</I>))/2</P>
<P>但必须指出,上式并不具有一般性。</P>
<P>类似地,对于算法B_Search照样可以按(2.4)-(2.6)计算相应的<I>T</I><SUB>max</SUB>(<I>m</I>)<I>、T</I><SUB>min</SUB>(<I>m</I>)和<I>T</I><SUB>avg</SUB>(<I>m</I>)<I><SUB> </SUB></I>。不过,我们这里只计算<I>T</I><SUB>max</SUB>(<I>m</I>) 。为了与Search比较,仍假设c在A中,即最坏情况的输入仍出现在c=A[m]时。这时,while循环的循环体恰好被执行了log<I>m </I>+1 即<I>k</I>+1 次。因为第一次执行时数据的规模为m,第二次执行时规模为m/2等等,最后一次执行时规模为1。另外,与Search少有不同的是这里除了用到赋值、测试和加法三种原运算外,还用到减法和除法两种元运算。补记后两种元运算每执行一次所需时间为b和d ,则可以推演出:</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img25.gif"> (2.10)</P>
<P>比较(2.7)和(2.10) ,我们看到m充分大时,在最坏情况下B_Search的时间复杂性远小于Search的时间复杂性。
 楼主| 发表于 2005-9-15 07:35 | 显示全部楼层

回复:(vibration)[转帖]算法的复杂性

<H2>复杂性的渐近性态及其阶</H2>
<P>随着经济的发展、社会的进步、科学研究的深入,要求用计算机解决的问题越来越复杂,规模越来越大。但是,如果对这类问题的算法进行分析用的是第二段所提供的方法,把所有的元运算都考虑进去,精打细算,那么,由于问题的规模很大且结构复杂,算法分析的工作量之大、步骤之繁将令人难以承受。因此,人们提出了对于规模充分大、结构又十分复杂的问题的求解算法,其复杂性分析应如何简化的问题。</P>
<P>我们先要引入复杂性渐近性态的概念。设<I>T</I>(<I>N</I>)是在第二段中所定义的关于算法A的复杂性函数。一般说来,当<I>N</I>单调增加且趋于∞时,<I>T</I>(<I>N</I>)也将单调增加趋于∞。对于<I>T</I>(<I>N</I>),如果存在<I>T’</I>(<I>N</I>),使得当<I>N</I>→∞时有:</P>
<P>(<I>T</I>(<I>N </I>)-<I>T’</I>(<I>N</I> ))/<I>T</I>(<I>N</I> ) → 0</P>
<P>那么,我们就说<I>T’</I>(<I>N</I>)是<I>T</I>(<I>N</I>)当<I>N</I>→∞时的<DFN>渐近性态</DFN>,或叫<I>T’</I>(<I>N</I>)为算法A当<I>N</I>→∞的<DFN>渐近复杂性</DFN>而与<I>T</I>(<I>N</I>)相区别,因为在数学上,<I>T’</I>(<I>N</I>)是<I>T</I>(<I>N</I>)当<I>N</I>→∞时的渐近表达式。</P>
<P>直观上,<I>T’</I>(<I>N</I>)是<I>T</I>(<I>N</I>)中略去低阶项所留下的主项。所以它无疑比<I>T</I>(<I>N</I>)来得简单。比如当</P>
<P><I>T</I>(<I>N</I>)=3<I>N </I><SUP>2</SUP>+4<I>N</I>log<SUB>2</SUB><I>N</I> +7</P>
<P>时,<I>T’</I>(<I>N</I>)的一个答案是3<I>N </I><SUP>2</SUP>,因为这时有:</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img3.gif"></P>
<P>显然3<I>N</I><SUP> 2</SUP>比3<I>N</I><SUP> 2 </SUP>+4<I>N</I>log<SUB>2</SUB><I>N</I> +7简单得多。</P>
<P>由于当<I>N</I>→∞时<I>T</I>(<I>N</I>)渐近于<I>T’</I>(<I>N</I>),我们有理由用<I>T’</I>(<I>N</I>)来替代<I>T</I>(<I>N</I>)作为算法A在<I>N</I>→∞时的复杂性的度量。而且由于于<I>T’</I>(<I>N</I>)明显地比<I>T</I>(<I>N</I>)简单,这种替代明显地是对复杂性分析的一种简化。</P>
<P>进一步,考虑到分析算法的复杂性的目的在于比较求解同一间题的两个不同算法的效率,而当要比较的两个算法的渐近复杂性的阶不相同时,只要能确定出各自的阶,就可以判定哪一个算法的效率高。换句话说,这时的渐近复杂性分析只要关心<I>T’</I>(<I>N</I>)的阶就够了,不必关心包含在<I>T’</I>(<I>N</I>)中的常数因子。所以,我们常常又对<I>T’</I>(<I>N</I>)的分析进--步简化,即假设算法中用到的所有不同的元运算各执行一次,所需要的时间都是一个单位时间。</P>
<P>综上所述,我们已经给出了简化算法复杂性分析的方法和步骤,即只要考察当问题的规模充分大时,算法复杂性在渐近意义下的阶。与此简化的复杂性分析方法相配套,需要引入五个渐近意义下的记号:<I>Ο、Ω、θ、ο</I>和<I>ω</I>。</P>
<P>以下设<I>f</I>(<I>N</I>)和<I>g</I>(<I>N</I>)是定义在正数集上的正函数。</P>
<P>如果存在正的常数<I>C</I>和自然数<I>N</I><SUB>0</SUB>,使得当<I>N</I>≥<I>N</I><SUB>0</SUB>时有<I>f</I>(<I>N</I>)≤<I>Cg</I>(<I>N</I>)。则称函数<I>f</I>(<I>N</I>)当<I>N</I>充分大时上有界,且<I>g</I>(<I>N</I>)是它的一个<DFN>上界</DFN>,记为<I>f</I>(<I>N</I>)=<I>Ο</I>(<I>g</I>(<I>N</I>))。这时我们还说<I>f</I>(<I>N</I>)的阶不高于<I>g</I>(<I>N</I>)的阶。</P>
<P>举几个例子:</P>
<P>(1)因为对所有的<I>N</I>≥1有3<I>N</I>≤4<I>N</I>,我们有3<I>N </I>=<I>Ο</I>(<I>N</I>);</P>
<P>(2)因为当<I>N</I>≥1时有<I>N</I>+1024≤1025<I>N</I>,我们有<I>N </I>+1024=<I>Ο</I>(<I>N</I>);</P>
<P>(3)因为当<I>N</I>≥10时有2<I>N</I><SUP> 2</SUP>+11<I>N</I> -10≤3<I>N</I><SUP> 2</SUP>,我们有2<I>N </I><SUP>2</SUP>+11<I>N</I> -10=<I>Ο</I>(<I>N</I><SUP> 2</SUP>);</P>
<P>(4)因为对所有<I>N</I>≥1有<I>N</I><SUP> 2</SUP>≤<I>N </I><SUP>3</SUP>,我们有<I>N</I><SUP>2</SUP>=<I>Ο</I>(<I>N</I><SUP> 3</SUP>);</P>
<P>(5)作为一个反例<I>N</I><SUP> 3</SUP>≠<I>Ο</I>(<I>N </I><SUP>2</SUP>)。因为若不然,则存在正的常数<I>C</I>和自然数<I>N</I><SUB>0</SUB>,使得当<I>N</I>≥<I>N</I><SUB>0</SUB>时有<I>N</I><SUP>3</SUP>≤C<I> N</I><SUP> 2</SUP>,即<I>N</I>≤<I>C</I> 。显然当取<I>N</I> =max(<I>N</I><SUB>0</SUB>,[<I>C</I>]+l)时这个不等式不成立,所以<I>N</I><SUP>3</SUP>≠<I>Ο</I>(<I>N</I><SUP> 2</SUP>)。</P>
<P>按照大<I>Ο</I>的定义,容易证明它有如下运算规则:</P>
<OL>
<LI><I>Ο</I>(<I>f</I>)+<I>Ο</I>(<I>g</I>)=<I>Ο</I>(max(<I>f</I>,<I>g</I>));
<LI><I>Ο</I>(<I>f</I>)+<I> Ο</I>(<I>g</I>)=<I>Ο</I>(<I>f </I>+<I>g</I>);
<LI><I>Ο</I>(<I>f</I>)·<I>Ο</I>(<I>g</I>)=<I> Ο</I>(<I>f</I>·<I>g</I>);
<LI>如果<I>g</I>(<I>N</I>)=<I> Ο</I>(<I>f</I>(<I>N</I>)),则<I>Ο</I>(<I>f</I>)+<I> Ο</I>(<I>g</I>)=<I> Ο</I>(<I>f</I>);
<LI><I>Ο</I>(<I>Cf</I>(<I>N</I>))=<I> Ο</I>(<I>f</I>(<I>N</I>)),其中<I>C</I>是一个正的常数;
<LI><I>f</I> =<I>Ο</I>(<I>f</I>); </LI></OL>
<P>规则1的证明:</P>
<P>设F(N)=<I> Ο</I>(<I>f</I>) 。根据记号<I>Ο</I>的定义,存在正常数<I>C</I><SUB>1</SUB>和自然数<I>N</I><SUB>1</SUB>,使得对所有的<I>N</I>≥<I>N</I><SUB>1</SUB>,有<I>F</I>(<I>N</I>)≤<I>C</I><SUB>1</SUB><I> f</I>(<I>N</I>)。类似地,设<I>G</I>(<I>N</I>)=<I>Ο</I>(<I>g</I>),则存在正的常数<I>C</I><SUB>2</SUB>和自然数<I>N</I><SUB>2</SUB>使得对所有的<I>N</I>≥<I>N</I><SUB>2</SUB>有<I>G</I>(<I>N</I>)≤<I>C</I><SUB>2</SUB><I>g</I>(<I>N</I>),今令:</P>
<P><I>C</I><SUB>3</SUB>=max(<I>C</I><SUB>1</SUB>,<I> C</I><SUB>2</SUB>)</P>
<P><I>N</I><SUB>3</SUB>=max(<I>N</I><SUB>1</SUB>,<I> N</I><SUB>2</SUB>)</P>
<P>和对任意的非负整数<I>N</I>,</P>
<P><I>h</I>(<I>N</I>)=max(<I>f</I>,<I>g</I>),</P>
<P>则对所有的<I>N</I>≥<I>N</I><SUB>3</SUB>有:</P>
<P><I>F</I>(<I>N</I>)≤<I>C</I><SUB>1</SUB><I>f</I>(<I>N</I>)≤<I>C</I><SUB>1</SUB><I>h</I>(<I>N</I>)≤<I>C</I><SUB>3</SUB><I>h</I>(<I>N</I>)</P>
<P>类似地,有:</P>
<P><I>G</I>(<I>N</I>)≤<I>C</I><SUB>2</SUB><I>g</I>(<I>N</I>)≤<I>C</I><SUB>2</SUB><I>h</I>(<I>N</I>)≤<I>C</I><SUB>3</SUB><I>h</I>(<I>N</I>)</P>
<P>因而</P>
<P><I>Ο</I>(<I>f</I>)+<I>Ο</I>(<I>g</I>) =<I>F</I>(<I>N</I>)+<I>G</I>(<I>N</I>)≤<I>C</I><SUB>3</SUB><I>h</I>(<I>N</I>)+<I> C</I><SUB>3</SUB><I>h</I>(<I>N</I>)</P>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P> =2<I>C</I><SUB>3</SUB><I>h</I>(<I>N</I>)</P>
<P> =<I>Ο</I>(<I>h</I>)</P>
<P> =<I>Ο</I>(max(<I>f</I>,<I>g</I>))</P></BLOCKQUOTE></BLOCKQUOTE>
<P>其余规则的证明类似,请读者自行证明。</P>
<P>应用这些规则的一个例子:对于<a href="http://algorithm.diy.myrice.com/algorithm/complexity/chapter1.htm#search" target="_blank" >第一章中的算法search</A>,在<a href="http://algorithm.diy.myrice.com/algorithm/complexity/chapter2.htm" target="_blank" >第二章</A>给出了它的最坏情况下时间复杂性<I>T</I><SUB>max</SUB>(<I>m</I>)和平均情况下的时间复杂性<I>T</I><SUB>avg</SUB>(<I>m</I>)的表达式。如果利用上述规则,立即有:</P>
<P><I>T</I><SUB>max</SUB>(<I>m</I>)=<I>Ο</I>(<I>m</I>)</P>
<P>和 <I>T</I><SUB>avg</SUB>(<I>m</I>)=<I>Ο</I>(<I>m</I>)+<I>Ο</I>(<I>m</I>)+<I>Ο</I>(<I>m</I>)=<I>Ο</I>(<I>m</I>)</P>
<P>另一个例子:估计下面二重循环算法段在最坏情况下的时间复杂性<I>T</I>(<I>N</I>)的阶。</P><PRE><CODE>for i:=l to N do
  for j:=1 to i do
    begin
     S1;
     S2;
     S3;
     S4;
    end;</CODE></PRE>
<P>其中S<SUB>k </SUB>(k=1,2,3,4)是单一的赋值语句。对于内循环体,显然只需<I>Ο</I>(l)时间。因而内循环只需</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img6.gif"></P>
<P>时间。累加起来便是外循环的时间复杂性:</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img8.gif"></P>
<P>应该指出,根据记号<I>Ο</I>的定义,用它评估算法的复杂性,得到的只是当规模充分大时的一个上界。这个上界的阶越低则评估就越精确,结果就越有价值。</P>
<P>关于记号<I>Ω</I>,文献里有两种不同的定义。本文只采用其中的一种,定义如下:如果存在正的常数<I>C</I>和自然数<I>N</I><SUB>0</SUB>,使得当<I>N</I>≥<I>N</I><SUB>0</SUB>时有<I>f</I>(<I>N</I>)≥<I>Cg</I>(<I>N</I>),则称函数<I>f</I>(<I>N</I>)当<I>N</I>充分大时<DFN>下有界</DFN>,且<I>g</I>(<I>N</I>)是它的一个<DFN>下界</DFN>,记为<I>f</I>(<I>N</I>)=<I>Ω</I>(<I>g</I>(<I>N</I>))。这时我们还说<I>f</I>(<I>N</I>)的阶不低于<I>g</I>(<I>N</I>)的阶。</P>
<P><I>Ω</I>的这个定义的优点是与<I>Ο</I>的定义对称,缺点是当<I>f</I>(<I>N</I>)对自然数的不同无穷子集有不同的表达式,且有不同的阶时,未能很好地刻画出<I>f</I>(<I>N</I>)的下界。比如当:</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img10.gif"></P>
<P>时,如果按上述定义,只能得到<I>f</I>(<I>N</I>)=<I>Ω</I>(1),这是一个平凡的下界,对算法分析没有什么价值。</P>
<P>然而,考虑到<I>Ω</I>的上述定义有与<I>Ο</I>的定义的对称性,又考虑到常用的算法都没出现上例中那种情况,所以本文还是选用它。</P>
<P>我们同样也可以列举<I>Ω</I>的一些运算规则。但这里从略,只提供一个应用的例子。还是考虑<a href="http://algorithm.diy.myrice.com/algorithm/complexity/chapter1.htm#search" target="_blank" >算法Search</A>在最坏情况下的时间复杂性函数<I>T</I><SUB>max</SUB>(<I>m</I>)。由它的<a href="http://algorithm.diy.myrice.com/algorithm/complexity/chapter2.htm#equ2_7" target="_blank" >表达式(2.7)</A>及已知<I>a</I>,<I>s</I>,<I>t</I>均为大于0的常数,可推得,当<I>m</I>≥1时有:</P>
<P><I>T</I><SUB>max</SUB>(<I>m</I>)≥(<I>m</I>+1)<I>a</I>+(2<I>m</I>+1)<I>t</I>&gt;<I>ma</I>+2<I>mt</I>=(<I>a</I>+2<I>t</I>)<I>m</I> ,</P>
<P>于是<I> T</I><SUB>max</SUB>(<I>m</I>)=<I>Ω</I>(<I>m</I>)。</P>
<P>我们同样要指出,用<I>Ω</I>评估算法的复杂性,得到的只是该复杂性的一个下界。这个下界的阶越高,则评估就越精确,结果就越有价值。再则,这里的<I>Ω</I>只对问题的一个算法而言。如果它是对一个问题的所有算法或某类算法而言,即对于一个问题和任意给定的充分大的规模<I>N</I>,下界在该问题的所有算法或某类算法的复杂性中取,那么它将更有意义。这时得到的相应下界,我们称之为问题的下界或某类算法的下界。它常常与<I>Ο</I>配合以证明某问题的一个特定算法是该问题的最优算法或该问题在某算法类中的最优算法。</P>
<P>明白了记号<I>Ο</I>和<I>Ω</I>之后,记号<I>θ</I>将随之清楚,因为我们定义<I>f</I>(<I>N</I>)=<I>θ</I>(<I>g</I>(<I>N</I>))则<I>f</I>(<I>N</I>)=<I>Ο</I>(<I>g</I>(<I>N</I>)) 且<I>f</I>(<I>N</I>)=<I>Ω</I>(<I>g</I>(<I>N</I>))。这时,我们说<I>f</I>(<I>N</I>)与<I>g</I>(<I>N</I>)<DFN>同阶</DFN>。比如,对于算法Search在最坏情况下的时间复杂性<I>T</I><SUB>max</SUB>(<I>m</I>)。已有<I>T</I><SUB>max</SUB>(<I>m</I>)=<I>Ο</I>(<I>m</I>)和<I>T</I><SUB>max</SUB>(<I>m</I>)=<I>Ω</I>(<I>m</I>),所以有<I>T<SUB>max</SUB></I>(<I>m</I>)<I>=θ</I>(<I>m</I>),这是对<I>T</I><SUB>max</SUB>(<I>m</I>)的阶的精确估计。</P>
<P>最后,如果对于任意给定的<I>ε</I>≥0,都存在非负整数<I>N</I><SUB>0</SUB>,使得当<I>N</I>≥<I>N</I><SUB>0</SUB>时有<I>f</I>(<I>N</I>)≤<I>εg</I>(<I>N</I>),则称函数<I>f</I>(<I>N</I>)当<I>N</I>充分大时比<I>g</I>(<I>N</I>)<DFN>低阶</DFN>,记为<I>f</I>(<I>N</I>)= <I>o</I>(<I>g</I>(<I>N</I>)),例如:</P>
<P>4<I>N</I>log<I>N</I> +7=<I>o</I>(3<I>N</I><SUP> 2</SUP>+4<I>N</I>log<I>N</I>+7);而<I>f</I>(<I>N</I>)=<I>ω</I>(<I>g</I>(<I>N</I>))定义为<I>g</I>(<I>N</I>)=<I>o</I>(<I>f</I>(<I>N</I>))。</P>
<P>即当<I>N</I>充分大时<I>f</I>(<I>N</I>)的阶比<I>g</I>(<I>N</I>)高。我们看到<I>o</I>对于<I>Ο</I>有如<I>ω</I>对于<I>Ω</I>。</P>
 楼主| 发表于 2005-9-15 07:36 | 显示全部楼层

回复:(vibration)[转帖]算法的复杂性

<H2>复杂性渐近阶的重要性</H2>
<P>计算机的设计和制造技术在突飞猛进,一代又一代的计算机的计算速度和存储容量在直线增长。有的人因此认为不必要再去苦苦地追求高效率的算法,从而不必要再去无谓地进行复杂性的分析。他们以为低效的算法可以由高速的计算机来弥补,以为在可接受的一定时间内用低效的算法完不成的任务,只要移植到高速的计算机上就能完成。这是一种错觉。造成这种错觉的原因是他们没看到:随着经济的发展、社会的进步、科学研究的深入,要求计算机解决的问题越来越复杂、规模越来越大,也呈线性增长之势;而问题复杂程度和规模的线性增长导致的时耗的增长和空间需求的增长,对低效算法来说,都是超线性的,决非计算机速度和容量的线性增长带来的时耗减少和存储空间的扩大所能抵销。事实上,我们只要对效率上有代表性的几个档次的算法作些简单的分析对比就能明白这一点。</P>
<P>我们还是以时间效率为例。设A<SUB>1</SUB>,A<SUB>2</SUB>,…和A<SUB>6</SUB>。是求解同一间题的6个不同的算法,它们的渐近时间复杂性分别为<I>N</I>,<I>N</I>log<I>N</I>,<I>N</I><SUP> 2</SUP>,<I>N </I><SUP>3</SUP>,2<I><SUP>N</SUP></I>,<I>N!</I>。让这六种算法各在<I>C</I><SUB>1</SUB>和<I>C</I><SUB>2</SUB>两台计算机上运行,并设计算机<I>C</I><SUB>2</SUB>的计算速度是计算机<I>C</I><SUB>1</SUB>的10倍。在可接受的一段时间内,设在<I>C</I><SUB>1</SUB>上算法A<SUB>i</SUB>可能求解的问题的规模为<I>N</I><SUB>1i</SUB>,而在<I>C</I><SUB>2</SUB>上可能求解的问题的规模为<I>N</I><SUB>2i</SUB>,那么,我们就应该有<I>T</I><SUB>i</SUB>(<I>N</I><SUB>2i</SUB>)=10<I>T</I><SUB>i</SUB>(<I>N</I><SUB>1i</SUB>),其中<I>T</I><SUB>i</SUB>(<I>N</I>)是算法<I>A</I><SUB>i</SUB>渐近的时间复杂性,i=1,2,…,6。分别解出<I>N</I><SUB>2i</SUB>和<I>N</I><SUB>1i</SUB>的关系,可列成下表:</P>
<P align=center><B>表4-1算法与渐近时间复杂性的关系</B></P>
<TABLE cellSpacing=0 width="95%" align=center border=1>

<TR>
<TH align=middle width="6%">算法</TH>
<TH align=middle width="23%">渐进时间复杂性<I>T</I>(<I>N</I>)</TH>
<TH align=middle width="24%">在<I>C</I><SUB>1</SUB>上可解的规模<I>N</I><SUB>1</SUB></TH>
<TH align=middle width="23%">在<I>C</I><SUB>2</SUB>上可解的规模<I>N</I><SUB>2</SUB></TH>
<TH align=middle width="24%"><I>N</I><SUB>1</SUB>和<I>N</I><SUB>2</SUB>的关系</TH></TR>
<TR>
<TD align=middle width="6%">A<SUB>1</SUB></TD>
<TD align=middle width="23%">N</TD>
<TD align=middle width="24%"><I>N</I><SUB>11</SUB></TD>
<TD align=middle width="23%"><I>N</I><SUB>21</SUB></TD>
<TD align=middle width="24%"><I>N</I><SUB>21</SUB>=10<I>N</I><SUB>11</SUB></TD></TR>
<TR>
<TD align=middle width="6%">A<SUB>2</SUB></TD>
<TD align=middle width="23%"><I>N</I>log<I>N</I></TD>
<TD align=middle width="24%"><I>N</I><SUB>12</SUB></TD>
<TD align=middle width="23%"><I>N</I><SUB>22</SUB></TD>
<TD align=middle width="24%"><I>N</I><SUB>22</SUB>≈10<I>N</I><SUB>12</SUB></TD></TR>
<TR>
<TD align=middle width="6%">A<SUB>3</SUB></TD>
<TD align=middle width="23%"><I>N</I><SUP>2</SUP></TD>
<TD align=middle width="24%"><I>N</I><SUB>13</SUB></TD>
<TD align=middle width="23%"><I>N</I><SUB>23</SUB></TD>
<TD align=middle width="24%"><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img96.gif"></TD></TR>
<TR>
<TD align=middle width="6%">A<SUB>4</SUB></TD>
<TD align=middle width="23%"><I>N</I><SUP>3</SUP></TD>
<TD align=middle width="24%"><I>N</I><SUB>14</SUB></TD>
<TD align=middle width="23%"><I>N</I><SUB>24</SUB></TD>
<TD align=middle width="24%"><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img98.gif"></TD></TR>
<TR>
<TD align=middle width="6%">A<SUB>5</SUB></TD>
<TD align=middle width="23%">2<I><SUP>N</SUP></I></TD>
<TD align=middle width="24%"><I>N</I><SUB>15</SUB></TD>
<TD align=middle width="23%"><I>N</I><SUB>25</SUB></TD>
<TD align=middle width="24%"><I>N</I><SUB>25</SUB> =<I>N</I><SUB>15</SUB>+log10</TD></TR>
<TR>
<TD align=middle width="6%">A<SUB>6</SUB></TD>
<TD align=middle width="23%">N!</TD>
<TD align=middle width="24%"><I>N</I><SUB>16</SUB></TD>
<TD align=middle width="23%"><I>N</I><SUB>26</SUB></TD>
<TD align=middle width="24%"><I>N</I><SUB>26</SUB> =<I>N</I><SUB>16</SUB>+小的常数</TD></TR></TABLE>
<P>从表4-1的最后一列可以清楚地看到,对于高效的算法A<SUB>1</SUB>,计算机的计算速度增长10倍,可求解的规模同步增长10倍;对于A<SUB>2</SUB>,可求解的问题的规模的增长与计算机的计算速度的增长接近同步;但对于低效的算法A<SUB>3</SUB>,情况就大不相同,计算机的计算速度增长10倍只换取可求解的问题的规模增加log10。当问题的规模充分大时,这个增加的数字是微不足道的。换句话说,对于低效的算法,计算机的计算速度成倍乃至数10倍地增长基本上不带来求解规模的增益。因此,对于低效算法要扩大解题规模,不能寄希望于移植算法到高速的计算机上,而应该把着眼点放在算法的改进上。</P>
<P>从表4-l的最后一列我们还看到,限制求解问题规模的关键因素是算法渐近复杂性的阶,对于表中的前四种算法,其渐近的时间复杂性与规模N的一个确定的幂同阶,相应地,计算机的计算速度的乘法增长带来的是求解问题的规模的乘法增长,只是随着幂次的提高,规模增长的倍数在降低。我们把渐近复杂性与规模N的幂同阶的这类算法称为多项式算法。对于表中的后两种算法,其渐近的时间复杂性与规模<I>N</I>的一个指数函数同阶,相应地计算机的计算速度的乘法增长只带来求解问题规模的加法增长。我们把渐近复杂性与规模<I>N</I>的指数同阶的这类算法称为指数型算法。多项式算法和指数型算法是在效率上有质的区别的两类算法。这两类算法的区别的内在原因是算法渐近复杂性的阶的区别。可见,算法的渐近复杂性的阶对于算法的效率有着决定性的意义。所以在讨论算法的复杂性时基本上都只关心它的渐近阶。</P>
<P>多项式算法是有效的算法。绝大多数的问题都有多项式算法。但也有一些问题还未找到多项式算法,只找到指数型算法。</P>
<P>我们在讨论算法复杂性的渐近阶的重要性的同时,有两条要记住:</P>
<OL>
<LI>“复杂性的渐近阶比较低的算法比复杂性的渐近阶比较高的算法有效”这个结论,只是在问题的求解规模充分大时才成立。比如算法A<SUB>4</SUB>比A<SUB>5</SUB>有效只是在<I>N</I><SUP> 3</SUP>&lt;2<I><SUP>N</SUP></I>,即<I>N</I>≥<I>c </I>时才成立。其中<I>c</I>是方程<I>N</I><SUP> 3</SUP>=2<I><SUP>N</SUP></I>的解。当<I>N </I>&lt;<I>c</I>时,A<SUB>5</SUB>反而比A<SUB>4</SUB>有效。所以对于规模小的问题,不要盲目地选用复杂性阶比较低的算法。其原因一方面是如上所说,复杂性阶比较低的算法在规模小时不一定比复杂性阶比较高的算法更有效;另方面,在规模小时,决定工作效率的可能不是算法的效率而是算法的简单性,哪一种算法简单,实现起来快,就选用那一种算法。
<LI>当要比较的两个算法的渐近复杂性的阶相同时,必须进一步考察渐近复杂性表达式中常数因子才能判别它们谁好谁差。显然常数因子小的优于常数因子大的算法。比如渐近复杂性为<I>N</I>1og<I>N</I>/l00的算法显然比渐近复杂性为l00<I>N</I>log<I>N</I>的算法来得有效。 </LI></OL>
 楼主| 发表于 2005-9-15 07:36 | 显示全部楼层

回复:(vibration)[转帖]算法的复杂性

<H2>算法复杂性渐近阶的分析</H2>
<P>前两段讲的是算法复杂性渐近阶的概念和对它进行分析的重要性。本段要讲如何具体地分析一个算法的复杂性的渐近阶,给出一套可操作的规则。算法最终要落实到用某种程序设计语言(如Pascal)编写成的程序。因此算法复杂性渐近阶的分析可代之以对表达该算法的程序的复杂性渐近阶的分析。</P>
<P>如前所提出,对于算法的复杂性,我们只考虑最坏、最好和平均三种情况,而通常又着重于最坏情况。为了明确起见,本段限于针对最坏情况。</P>
<P>仍然以时间复杂性为例。这里给出分析时间复杂性渐近阶的八条规则。这八条规则已覆盖了用Pascal语言程序所能表达的各种算法在最坏情况下的时间复杂性渐近阶的分析。</P>
<P>在逐条地列出并解释这入条规则之前,应该指出,当我们分析程序的某一局部(如一个语句,一个分程序,一个程序段,一个过程或函数)时,可以用具体程序的输入的规模N作为复杂性函数的自变量,也可以用局部的规模参数作为自变量。但是,作为最终结果的整体程序的复杂性函数只能以整体程序的输入规模为自变量。</P>
<P>对于串行的算法,相应的Pascal程序是一个串行的Pascal语句序列,因此,很明显,该算法的时间复杂性(即所需要的时间)等于相应的Pascal程序的每一个语句的时间复杂性(即所需要的时间)之和。所以,如果执行Pascal语句中的每一种语句所需要的时间都有计量的规则,那么,执行一个程序,即执行一个算法所需要的时间的计量便只是一个代数问题。接着,应用本节第三段所提供的Ο、Ω和θ等运算规则就可以分析出算法时间复杂性的渐近阶。</P>
<P>因此,我们的时间计量规则只需要针对Pascal有限的几种基本运算和几种基本语句。下面是这些规则的罗列和必要的说明。</P>
<DL>
<DT><B>规则(1)</B> </DT></DL>
<P>赋值、比较、算术运算、逻辑运算、读写单个常量或单个变量等,只需要1个单位时间。</P>
<DL>
<DT><B>规则(2)</B> </DT></DL>
<P>条件语句"if C then S<SUB>1</SUB> else S<SUB>2</SUB>"只需要T<SUB>c</SUB>+max(T<SUB>s1</SUB>,T<SUB>s2</SUB>)的时间,其中T<SUB>c</SUB>是计算条件表达式C需要的时间,而T<SUB>s1</SUB>和T<SUB>s2</SUB>分别是执行语句S<SUB>1</SUB>和S<SUB>2</SUB>需要的时间。</P>
<DL>
<DT><B>规则(3)</B> </DT></DL>
<P>选择语句"Case  A  of  a1:S1; a2:S2; … ;am:Sm;  end",需要max(Ts1, Ts2,…,Tsm)的时间,其中Tsii是执行语句Si所需要的时间,i=l,2,…,m。</P>
<DL>
<DT><B>规则(4)</B> </DT></DL>
<P>访问一个数组的单个分量或一个记录的单个域,只需要1个单位时间。</P>
<DL>
<DT><B>规则(5)</B> </DT></DL>
<P>执行一个for循环语句需要的时间等于执行该循环体所需要的时间乘上循环的次数。</P>
<DL>
<DT><B>规则(6)</B> </DT></DL>
<P>执行一个while循环语句"while C do S"或一个repeat循环语句" repeat S until C",需要的时间等于计算条件表达式C需要的时间与执行循环S体需要的时间之和乘以循环的次数。与规则5不同,这里的循环次数是隐含的。</P>
<P>例如,b_search函数中的while循环语句。按规则(1)-(4),计算条件表达式" (not found)and(U≥=L)"与执行循环体</P><PRE><CODE>I:=(U+L)div 2;
if c=A[I] then found:=true
          else  if  c&gt;A[I] then L:=I+1
                           else U:=I-1;</CODE></PRE>
<P>只需要<I>θ</I>(1)时间,而循环次数为log<I>m</I>,所以,执行此while语句只需要<I>θ</I>(log<I>m</I>)时间。</P>
<P>在许多情况下,运用规则(5)和(6)常常须要借助具体算法的内涵来确定循环的次数,才不致使时间的估计过于保守。这里举一个例子。</P>
<P>考察程序段:</P>
<DIV align=center>
<TABLE height=275 cellSpacing=0 cellPadding=0 width="90%" bgColor=#e0e0e0 border=0>

<TR>
<CENTER>
<TD vAlign=top width=515 height=9> </TD></CENTER>
<TD vAlign=top align=middle width=72 height=9> </TD></TR>
<TR>
<TD vAlign=top width=515 height=9>
<P>Size:=m;</P></TD>
<TD vAlign=top align=middle width=72 height=9>1</TD></TR>
<TR>
<CENTER>
<TD vAlign=top width=515 height=18>
<P>i:=1;</P></TD></CENTER>
<TD vAlign=top align=middle width=72 height=18>1</TD></TR>
<CENTER>
<TR>
<TD vAlign=top width=515 height=18>
<P>while i&lt;n do</P></TD>
<TD vAlign=top align=middle width=72 height=18> </TD></TR>
<TR>
<TD vAlign=top width=515 height=18>
<P>    begin</P></TD>
<TD vAlign=top align=middle width=72 height=18> </TD></TR>
<TR>
<TD vAlign=top width=515 height=18>
<P>      i:=i+1;</P></TD>
<TD vAlign=top align=middle width=72 height=18> </TD></TR></CENTER>
<TR>
<CENTER>
<TD vAlign=top width=515 height=21>
<P>      S<SUB>1</SUB>;</P></TD></CENTER>
<TD vAlign=top align=middle width=72 height=21><I>θ</I>(<I>n</I>)</TD></TR>
<TR>
<CENTER>
<TD vAlign=top width=515 height=18>
<P>      if  Size&gt;0  then</P></TD></CENTER>
<TD vAlign=top align=middle width=72 height=18>1</TD></TR>
<CENTER>
<TR>
<TD vAlign=top width=515 height=18>
<P>         begin</P></TD>
<TD vAlign=top align=middle width=72 height=18> </TD></TR></CENTER>
<TR>
<CENTER>
<TD vAlign=top width=515 height=18>
<P>         在1到Size的范围内任选一个数赋值给t;</P></TD></CENTER>
<TD vAlign=top align=middle width=72 height=18><I>θ</I>(1)</TD></TR>
<TR>
<CENTER>
<TD vAlign=top width=515 height=18>
<P>             Size:=Size-t;</P></TD></CENTER>
<TD vAlign=top align=middle width=72 height=18>2</TD></TR>
<CENTER>
<TR>
<TD vAlign=top width=515 height=18>
<P>             for j:=l  to  t  do</P></TD>
<TD vAlign=top align=middle width=72 height=18> </TD></TR></CENTER>
<TR>
<CENTER>
<TD vAlign=top width=515 height=21>
<P>                 S<SUB>2</SUB></P></TD></CENTER>
<TD vAlign=top align=middle width=72 height=21><I>θ</I>(<I>n</I>)</TD></TR>
<CENTER>
<TR>
<TD vAlign=top width=515 height=18>
<P>         end;</P></TD>
<TD vAlign=top align=middle width=72 height=18> </TD></TR>
<TR>
<TD vAlign=top width=515 height=18>
<P>    end;</P></TD>
<TD vAlign=top align=middle width=72 height=18> </TD></TR></CENTER>
<TR>
<TD vAlign=top width=515 height=17> </TD>
<TD vAlign=top align=middle width=72 height=17> </TD></TR></TABLE></DIV>
<P>程序在各行右端顶格处标注着执行相应各行所需要的时间。如果不对算法的内涵作较深入的考察,只看到1≤<I>t</I>≤Size≤<I>m</I>,就草率地估计while的内循环for的循环次数为<I>Ο</I>(<I>m</I>),那么,程序在最坏情况下的时间复杂性将被估计为<I>Ο</I>(<I>n</I><SUP> 2</SUP>+<I>m</I>·<I>n</I><SUP> 2</SUP>)。反之,如果对算法的内涵认真地分析,结果将两样。事实上,在while的循环体内<I>t</I>是动态的,size也是动态的,它们都取决while的循环参数i,即<I>t</I>=<I>t</I>(i)记为<I>t</I><SUB>i</SUB>;size=size(i)记为size<SUB>i </SUB>,i=l,2,…,<I>n</I>-1。对于各个i,1≤i≤<I>n</I>-1,<I>t</I><SUB>i</SUB>与<I>m</I>的关系是隐含的,这给准确地计算for循环的循环体S<SUB>2</SUB>被执行的次数带来困难。上面的估计比较保守的原因在于我们把S<SUB>2</SUB>的执行次数的统计过于局部化。如果不局限于for循环,而是在整个程序段上统计S<SUB>2</SUB>被执行的总次数,那么,这个总次数等于<SUB><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img13.gif"></SUB>,又根据算法中t<SUB>i</SUB>的取法及size<SUB>i+1</SUB>=size<SUB>i</SUB>-t<SUB>i</SUB>,i=1,2,…,n-1 有size<SUB>n</SUB>=size<SUB>1</SUB>-<SUB><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img4.gif"></SUB>。最后利用size<SUB>1</SUB>=m和size<SUB>n</SUB>=0得到<SUB><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img14.gif"></SUB>=m 。于是在整个程序段上,S<SUB>2</SUB>被执行的总次数为m,所需要的时间为<I>θ</I>(<I>mn</I>)。执行其他语句所需要的时间直接运用规则(l)-(6)容易计算。累加起来,整个程序段在最坏情况下时间复杂性渐近阶为<I>θ</I>(<I>n</I><SUP> 2</SUP>+<I>mn</I>)。这个结果显然比前面粗糙的估计准确。</P>
<DL>
<DT><B>规则(7)</B> </DT></DL>
<P>对于goto语句。在Pascal中为了便于表达从循环体的中途跳转到循环体的结束或跳转到循环语句的后面语句,引入goto语句。如果我们的程序按照这一初衷使用goto语句,那么,在时间复杂性分析时可以假设它不需要任何额外的时间。因为这样做既不会低估也不会高估程序在最坏情况下的运行时间的阶。如果有的程序滥用了goto语句,即控制转移到前面的语句,那么情况将变得复杂起来。当这种转移造成某种循环时,只要与别的循环不交叉,保持循环的内外嵌套,则可以比照规则(1)-(6)进行分析。当由于使用goto语句而使程序结构混乱时,建议改写程序然后再做分析。</P>
<DL>
<DT><B>规则(8)</B> </DT></DL>
<P>对于过程调用和函数调用语句,它们需要的时间包括两部分,一部分用于实现控制转移,另一部分用于执行过程(或函数)本身,这时可以根据过程(或函数)调用的层次,由里向外运用规则(l)-(7)进行分析,一层一层地剥,直到计算出最外层的运行时间便是所求。如果过程(或函数)出现直接或间接的递归调用,则上述由里向外逐层剥的分析行不通。这时我们可以对其中的各个递归过程(或函数),所需要的时间假设为一个相应规模的待定函数。然后一一根据过程(或函数)的内涵建立起这些待定函数之间的递归关系得到递归方程。最后用求递归方程解的渐进阶的方法确定最坏情况下的复杂性的渐进阶。</P>
<P>递归方程的种类很多,求它们的解的渐近阶的方法也很多,我们将在下一段比较系统地给予介绍。本段只举一个简单递归过程(或函数)的例子来说明如何建立相应的递归方程,同时不加推导地给出它们在最坏情况下的时间复杂性的渐近阶。</P>
<P>例:再次考察函数b_search,这里将它改写成一个递归函数。为了简明,我们已经运用前面的规则(l)-(6),统计出执行各行语句所需要的时间,并标注在相应行的右端:</P>
<DIV align=center>
<CENTER>
<TABLE cellSpacing=0 cellPadding=0 width="95%" bgColor=#e0e0e0 border=0>

<TR>
<TD vAlign=top>
<P> </P></TD>
<TD vAlign=top align=middle width=120>
<P> </P></TD></TR>
<TR>
<TD vAlign=top>
<P>Function b_search(C,L,U:integer):integer;</P></TD>
<TD vAlign=top align=middle width=120>
<P>单位时间数</P></TD></TR>
<TR>
<TD vAlign=top>
<P>var index,element:integer;</P></TD>
<TD vAlign=top align=middle width=120> </TD></TR>
<TR>
<TD vAlign=top>
<P> begin</P></TD>
<TD vAlign=top align=middle width=120> </TD></TR>
<TR>
<TD vAlign=top>
<P>   if (U&lt;L) then</P></TD>
<TD vAlign=top align=middle width=120>
<P>     1</P></TD></TR>
<TR>
<TD vAlign=top>
<P>            b_search:=0;</P></TD>
<TD vAlign=top align=middle width=120>
<P>     1</P></TD></TR>
<TR>
<TD vAlign=top>
<P>   else</P></TD>
<TD vAlign=top align=middle width=120> </TD></TR>
<TR>
<TD vAlign=top>
<P>    begin</P></TD>
<TD vAlign=top align=middle width=120> </TD></TR>
<TR>
<TD vAlign=top>
<P>      index:=(L+U) div 2;</P></TD>
<TD vAlign=top align=middle width=120>
<P>     3</P></TD></TR>
<TR>
<TD vAlign=top>
<P>      element:=A[index];</P></TD>
<TD vAlign=top align=middle width=120>
<P>     2</P></TD></TR>
<TR>
<TD vAlign=top>
<P>      if element=C then</P></TD>
<TD vAlign=top align=middle width=120>
<P>     1</P></TD></TR>
<TR>
<TD vAlign=top>
<P>        b_search:=index</P></TD>
<TD vAlign=top align=middle width=120>
<P>     1</P></TD></TR>
<TR>
<TD vAlign=top>
<P>   else if element&gt;C then</P></TD>
<TD vAlign=top align=middle width=120> </TD></TR>
<TR>
<TD vAlign=top>
<P>        b_search:=b_search(C,L,index-1)</P></TD>
<TD vAlign=top align=middle width=120>
<P>   3+<I>T</I>(<I>m</I>/2)</P></TD></TR>
<TR>
<TD vAlign=top>
<P>      else</P></TD>
<TD vAlign=top align=middle width=120> </TD></TR>
<TR>
<TD vAlign=top>
<P>        b_search:=b_search(C,index+1,U);</P></TD>
<TD vAlign=top align=middle width=120>
<P>   3+<I>T</I>(<I>m</I>/2)</P></TD></TR>
<TR>
<TD vAlign=top>
<P>    end;</P></TD>
<TD vAlign=top align=middle width=120> </TD></TR>
<TR>
<TD vAlign=top>
<P> end;</P></TD>
<TD vAlign=top align=middle width=120> </TD></TR>
<TR>
<TD vAlign=top> </TD>
<TD vAlign=top align=middle width=120> </TD></TR></TABLE></CENTER></DIV>
<P>其中<I>T</I>(<I>m</I>)是当问题的规模<I>U</I>-<I>L</I>+1=<I>m</I>时b_search在最坏情况下(这时,数组A[<I>L</I>..<I>U</I>]中没有给定的<I>C</I>)的时间复杂性。根据规则(l)-(8),我们有:</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img16.gif"></P>
<P>或化简为</P>
<P><img src="http://algorithm.diy.myrice.com/algorithm/complexity/images/img19.gif"></P>
<P>这是一个关于T(m)的递归方程。用下一段将介绍的迭代法,容易解得:</P>
<P><I>T</I>(<I>m</I>)=11log<I>m </I>+l3=<I>θ</I>(log<I>m</I>)</P>
<P>在结束这一段之前,我们要提一下关于算法在最坏情况下的空间复杂性分析。我们照样可以给出与分析时间复杂性类似的规则。这里不赘述。然而应该指出,在出现过程(或函数)递归调用时要考虑到其中隐含的存储空间的额外开销。因为现有的实现过程(或函数)递归调用的编程技术需要一个隐含的、额外(即不出现在程序的说明中)的栈来支持。过程(或函数)的递归调用每深人一层就把本层的现场局部信息及调用的返回地址存放在栈顶备用,直到调用的最里层。因此递归调用一个过程(或函数)所需要的额外存储空间的大小即栈的规模与递归调用的深度成正比,其比例因子等于每深入一层需要保存的数据量。比如本段前面所举的递归函数b_search,在最坏情况下,递归调用的深度为log<I>m</I>,因而在最坏情况下调用它所需要的额外存储空间为<I>θ</I>(log<I>m</I>)。</P>
 楼主| 发表于 2005-9-15 07:37 | 显示全部楼层

回复:(vibration)[转帖]算法的复杂性

<H2>递归方程解的渐近阶的求法</H2>
<P>上一章所介绍的递归算法在最坏情况下的时间复杂性渐近阶的分析,都转化为求相应的一个递归方程的解的渐近阶。因此,求递归方程的解的渐近阶是对递归算法进行分析的关键步骤。
<P>递归方程的形式多种多样,求其解的渐近阶的方法也多种多样。这里只介绍比较实用的五种方法。
<OL>
<LI><B>代入法 </B>这个方法的基本步骤是先推测递归方程的显式解,然后用数学归纳法证明这一推测的正确性。那么,显式解的渐近阶即为所求。
<LI><B>迭代法</B>  这个方法的基本步骤是通过反复迭代,将递归方程的右端变换成一个级数,然后求级数的和,再估计和的渐近阶;或者,不求级数的和而直接估计级数的渐近阶,从而达到对递归方程解的渐近阶的估计。
<LI><B>套用公式法 </B>这个方法针对形如:<I>T </I>(<I>n</I>)<I>=aT </I>(<I>n </I>/ <I>b</I>)+<I>f </I>(<I>n</I>) 的递归方程,给出三种情况下方程解的渐近阶的三个相应估计公式供套用。
<LI><B>差分方程法</B>  有些递归方程可以看成一个差分方程,因而可以用解差分方程(初值问题)的方法来解递归方程。然后对得到的解作渐近阶的估计。
<LI><B>母函数法 </B>这是一个有广泛适用性的方法。它不仅可以用来求解线性常系数高阶齐次和非齐次的递归方程,而且可以用来求解线性变系数高阶齐次和非齐次的递归方程,甚至可以用来求解非线性递归方程。方法的基本思想是设定递归方程解的母函数,努力建立一个关于母函数的可解方程,将其解出,然后返回递归方程的解。 </LI></OL>
您需要登录后才可以回帖 登录 | 我要加入

本版积分规则

QQ|小黑屋|Archiver|手机版|联系我们|声振论坛

GMT+8, 2024-4-28 00:20 , Processed in 0.093675 second(s), 17 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表