1. 程式人生 > >遞歸(Recursion)簡述及一些註意事項

遞歸(Recursion)簡述及一些註意事項

語言 c++ 如何 我們 隱式轉換 這不 else recursion 其中

《Data Structure and Algorithm Analysis in C++》筆記

大多數的數學函數可以被描述成簡單表達式

例如:

  華氏度和攝氏度轉換的表達式為

    C = 5 *( F - 32 ) / 9

這種式子我們可以明確地一行行轉換成C++代碼。

但有時候,數學函數的表達式采用另一種形式——遞推式(iterative)

例如:

  f(0) = 0

  f(x) = 2 * f(x - 1) + x2

計算幾項結果:

  f(1) = 1,f(2) = 6, f(3) = 21 ...

遞推式通常給出初值和條件,後項由前項加以條件得到結果,層層進行以得到各項。

對於遞推式,C++語言可以通過兩種思路處理。

其中一種是遞歸(recursive)

C++允許函數被遞歸式定義,但這種允許是有條件的。

並非所有數學上的遞推式都可以高效地(或者正確地)使用C++模擬。

出於效率考慮,我們理想中的遞歸實現應當保證後項依賴的前項不涉及復雜計算。

上述遞推式的C++語言描述:

int f(int x) {
  if (x == 0) {
    return 0;
  }
  else {
    return 2 * f(x - 1) + x*x;
  }
}

其中第2到3行是遞推式f(0)的描述,它在遞推式中叫做初始條件,而在遞歸實現中稱為遞歸出口

僅僅聲明f(x) = 2f(x-1) +x2的關系是無意義的,遞歸實現必須先給出明確的遞歸出口。

對於遞歸,通常會存在普遍的疑問。

其中一個共同的問題就是:難道這不會陷入繞圈邏輯?如何避免呢?

答案是:

  只要我們在給定遞推關系時,保證遞推關系僅僅依賴於前後項而非自身即可。

換句話:

  遞推f(5)需要用到計算f(5)的結果,那將會陷入繞圈。

  但遞推f(5)僅需要用到計算f(4)的結果,不會繞圈。

另外一個重要問題,檢查遞歸出口有效性。(小心負數小心除法

在上述遞歸實現中,

想知道f(3),就得知道f(2)。想知道f(2),就得知道f(1)。想知道f(1),就得知道f(0)

。而f(0)=0是已知的。

這使得其看上去像是個美妙的遞歸實現。

小心負數:

但假如我們試圖計算符合數據類型的f(-1)呢?

想知道f(-1),就得知道f(-2)。想知道f(-2),就得知道f(-3)。……

以此類推,想得到前項需要用到無限的後項,計算機永遠也得不到結果,這種實現明顯是不合格的。

另外的導致遞推出口失效的隱秘可能,除法運算

int bad(int n) {
  if (n == 0) {
    return 0;
  }
  else {
    return bad(n / 3 + 1) + n - 1;
  }
}

它看上去定義了明確的遞推出口,但bad(1) 需要知道bad(n / 3 + 1),而C++的除法運算會隱式轉換,1/3 =0.

bad(1)需要知道bad(1),bad(2)需要知道bad(1),而bad(3)bad(5)都需要知道bad(2)

而這種混亂關系中,我們僅僅知道bad(0) = 0這個無法抵達的出口,因此整個遞歸實現都失效了。

在遞推關系涉及到負數和除法運算時,我們要格外小心

遞歸(Recursion)簡述及一些註意事項