1. 程式人生 > >關於遞歸算法學習的點滴感悟

關於遞歸算法學習的點滴感悟

初步 log 進行 有意 arc 最好的 單向鏈表 return []

本人的第一篇博客,純原創,部分內容參考下面兩位博主的文章,鳴謝!

https://www.cnblogs.com/GODYCA/archive/2013/01/15/2861545.html

https://www.cnblogs.com/LiCheng-/p/8206444.html (這篇文章寫的不是很好,因為博文提到“每次問題規模縮小程度必須為1”,而事實上我在查閱資料時,這是需要根據實際情況確定的,比如”二分查找法”就是最好的例子,每次問題規模縮小程度就是一半)

  關於遞歸,今天逛園子的時候偶然發現的,一篇關於利用數學歸納法指導編寫遞歸程序的博文,啟發了我。但博主的文章寫的不是那麽好理解,於是我又琢磨了一下午,參考了別的資料,寫了三個例子幫助自己理解。

關於數學歸納法,先看看是怎麽回事:

一般地,證明一個與自然數n有關的命題P(n),有如下步驟:

(1)證明當n取第一個值n0時命題成立。n0對於一般數列取值為0或1,但也有特殊情況;

(2)假設當n=k(k≥n0,k為自然數)時命題成立,證明當n=k+1時命題也成立。

綜合(1)(2),對一切自然數n(≥n0),命題P(n)都成立。

舉個例子:

已知N!=N*(N-1)*(N-2)*(N-3)*…*2*1,求證R(N)與R(N-1)之間的關系!

第一步當N=1時可知 N!=1

第二步設當R(N)=N!,R(N-1)=(N-1)!

第三步,求R(N)與R(N-1)之間的關系:

因為R(N-1)=(N-1)!= (N-1)*…*2*1=> R(N)=N*R(N-1)

即:R(N)=N*R(N-1)

寫成一個函數求N!的值:

 factorial (int N)
{

if(N==1) return 1;      /* 特殊部分 */

return N * factorial (N - 1) /* 遞歸部分 */

}

上面這個例子,利用數學歸納法得到R(N)=N*R(N-1) 的關系式恰好是遞歸寫法的核心(通用)部分,而遞歸返回(特殊)部分為 if(N==1) return 1 語句。

那麽編寫遞歸程序就有指導思想:首先分析得到問題解決的通用處理步驟(規律),再處理遞歸返回(特殊)部分

下面看看三個例子幫助消化:

第一個例子(自己碰到的面試題,當時寫不好,現在一下子就寫出了):

現有如下關系:1、1、2、3、5、8、13…請用遞歸求出第150位數字的值?

分析:對於8=5+3,對於5=3+2,對於2=1+1,即第n位=第n-1位+第n-2位,其中n>2

而對於第1位、第2位時,他們的值都是一樣,即為1.

那我們可以很容易就可以寫出來:

int IndexNum (int n)

{

if(n==1||n==2)

{

return 1;

}

return IndexNum(n - 1) + IndexNum(n - 2);

}

第二個例子,漢諾塔的實現,這個也很有意思(雖然書上很多):

  漢諾塔:三個立柱(命名為from、temp、to,from為圓盤初始所在立柱、to是目標立柱),N個直徑不相等的圓盤,將圓盤從from上一個一個移動在to上,要求,每次只能移動一個圓盤,而且只能在三個立柱之間移動。目標柱to不能出現大盤壓小盤的情況。

首先用數學歸納法分析:


當只有一個圓盤的時候,我們可以確定:

直接將圓盤從from移動到to上:       move (n, from, to);

現在假設有N個圓盤在from上,則需要進行如下操作:

首先需要將from上N-1個盤移動到temp上:   Hanoi (n-1, from, temp);

然後將from上的第N個盤移到to:      move (n, from, to);  

最後再將temp上N-1個盤移到to上:     Hanoi (n-1, temp, to);

設Hanoi (int n, int from, int temp, int to)函數就是我們要求的漢諾塔實現函數,意義是將按直徑遞增摞在一起的n個圓盤從from按要求移動到to上,temp為輔助柱。

寫出代碼即:

void Hanoi (int n, int from, int temp, int to)

{

  if (n == 1)

  {

    move (n, from, to);

  }

  else

  {

    Hanoi (n-1, from, to, temp);

    move (n, from, to);

    Hanoi (n-1, temp, from, to);

  }

}

上面的代碼,也同樣包含通用規律,特殊部分即是遞歸的返回部分。

再看第三個例子,關於建立單向鏈表關系:

假設有節點,定義如下:

class node

{

public int Num { get; set; } //序號

public node next { get; set; } //指向的下一個節點

public node(int num)

{

Num = num;

}

}

現在有數組nodes如下:

node[] nodes = new node[5];

nodes[0] = new node(2);

nodes[1] = new node(4);

nodes[2] = new node(1);

nodes[3] = new node(3);

nodes[4] = new node(9);

需將他們建立單向鏈關系:

首先分析:對於數組第n個節點,有nodes[n].next = nodes[n+1]的通用關系;

對於最後一個節點,有nodes[4].next = null

那我們可以這麽寫:

void CreatLink(node[]data, int n)

{

if (n >= data.Length-1)

{

data[n] = null;

return;

}else

{

data[n].next = data[n + 1];

CreatLink(data, n + 1);

}

}

初步總結:對於編寫遞歸程序,首先是得到其通用的處理步驟(規律),然後再分析其特殊部分,而特殊部分往往是遞歸函數返回(閉環)的關鍵。

關於遞歸算法學習的點滴感悟