關於遞歸算法學習的點滴感悟
本人的第一篇博客,純原創,部分內容參考下面兩位博主的文章,鳴謝!
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);
}
}
初步總結:對於編寫遞歸程序,首先是得到其通用的處理步驟(規律),然後再分析其特殊部分,而特殊部分往往是遞歸函數返回(閉環)的關鍵。
關於遞歸算法學習的點滴感悟