1. 程式人生 > >深入理解遞迴

深入理解遞迴

遞迴最重要的一點就是假設子問題已經解決了,現在要基於已經解決的子問題來解決當前問題。

遞迴之所以把問題變簡單了,是因為相當於多給你了一個前提條件(儘管是假設的)。

大家都知道,遞迴的本質和棧資料的存取很相似了,都是先進去,但是往往最後處理!再者對於遞迴函式的區域性變數的儲存是按照棧的方式去存的,對於每一層的遞迴函式在棧中都儲存了本層函式的區域性變數,一邊該層遞迴函式結束時能夠儲存原來該層的資料!如圖:


  如上圖遞迴式依次往下進行的,並且在該層遞迴函式還沒結束即將進入下一層遞迴呼叫時,將會把該層函式中的區域性變數儲存起來,以供下次使用!
        好了,以上是遞迴函式的資料儲存方式,可是有時候我們又得抓頭了,遞迴的話,有時候又很難理解,貌似總也想不通!

      於是我又把每一層遞迴函式化分為三部分了,第一部分:是遞迴呼叫前的一些資料處理,判斷以及遞迴結束判斷(當然了結束條件肯定在遞迴呼叫前,要不每次遞迴就不會結束了),第二部分:就是遞迴函式本身了。而第三部分:當然就是遞迴函式的後續處理程式碼了!在這裡我想我們得想明白一件事情了,每一層的函式都是在上一層遞迴函式結束時才返回的然後接著處理該層遞迴函式剩下的部分!例如如下程式碼:

#include<iostream>
#include<string>
using namespace std;

int i=0,j;
void reverse(string &s);
int main()
{
	string s;

	cin>>s;
	j=i=s.size();
	reverse(s);
	cout<<s<<endl;

	return 0;
}

void reverse(string &s)
{
	char ch;                    //..........第一部分 ..........
	i--;
	ch=s[i];
	cout<<ch<<endl;             //這裡i是全域性變數,而ch是區域性變數會儲存在棧中
	if(-1==i)
		return;                  
	reverse(s);                 //本身的遞迴看做第二部分
	                            //後續部分看做第三部分
	s[--j]=ch;               //這句當且僅當該遞迴函式中的reverse返回時 才執行
	cout<<ch<<endl;
}


在以上程式碼中,每一層只有當reverse()結束了才會接著處理下面的s[--j]=ch;程式碼,因為每一次遞迴進去的時候reverse()上面的程式碼都已經處理了,所以當遞迴返回時處理的自然就是reverse()下面的程式碼了,如此迴圈直到結束!不過我覺著最重要的還有一樣就是有時候不必刻意去關注的那麼細,也要有全域性觀,例如我們只需要知道函式reverse()是繼續處理同樣的功能,沒必要再去想這個函式裡面又是怎麼樣怎麼樣的,我感覺肯定會抓狂的!希望跟我一樣糾結的朋友不在糾結遞迴了.........

另外,

遞迴的使用條件:

  存在一個遞迴呼叫的終止條件;

  每次遞迴的呼叫必須越來越靠近這個條件;只有這樣遞迴才會終止,否則是不能使用遞迴的!

總之,在你使用遞迴來處理問題之前必須首先考慮使用遞迴帶來的好處是否能補償

  他所帶來的代價!否則,使用迭代演算法會比遞迴演算法要高效。

遞迴的基本原理:

  1 每一次函式呼叫都會有一次返回.當程式流執行到某一級遞迴的結尾處時,它會轉移到前一級遞迴繼續執行.

  2 遞迴函式中,位於遞迴呼叫前的語句和各級被調函式具有相同的順序.如列印語句 #1 位於遞迴呼叫語句前,它按照遞迴呼叫的順序被執行了 4 次.

  3 每一級的函式呼叫都有自己的區域性變數.

  4 遞迴函式中,位於遞迴呼叫語句後的語句的執行順序和各個被呼叫函式的順序相反.

           即位於遞迴函式入口前的語句,右外往裡執行;位於遞迴函式入口後面的語句,由裡往外執行。

  5 雖然每一級遞迴有自己的變數,但是函式程式碼並不會得到複製.

  6 遞迴函式中必須包含可以終止遞迴呼叫的語句.

一旦你理解了遞迴(理解遞迴,關鍵是腦中有一幅程式碼的圖片,函式執行到遞迴函式入口時,就擴充一段完全一樣的程式碼,執行完擴充的程式碼並return,繼續執行前一次遞迴函式中遞迴函式入口後面的程式碼),閱讀遞迴函式最容易的方法不是糾纏於它的執行過程,而是相信遞迴函式會順利完成它的任務。如果你的每個步驟正確無誤,你的限制條件設定正確,並且每次呼叫之後更接近限制條件,遞迴函式總是能正確的完成任務。

不算遞迴呼叫語句本身,到目前為止所執行的語句只是除法運算以及對quotient的值進行測試。由於遞迴呼叫這些語句重複執行,所以它的效果類似迴圈:當quotient的值非零時,把它的值作為初始值重新開始迴圈。但是,遞迴呼叫將會儲存一些資訊(這點與迴圈不同),也就好是儲存在堆疊中的變數值。這些資訊很快就會變得非常重要。

斐波那契數是典型的遞迴案例:

  Fib(0) = 0 [基本情況] Fib(1) = 1 [基本情況]

  對所有n > 1的整數:Fib(n) = (Fib(n-1) + Fib(n-2)) [遞迴定義]

 遞迴演算法一般用於解決三類問題:

  (1)資料的定義是按遞迴定義的。(Fibonacci函式)

  (2)問題解法按遞迴演算法實現。(回溯)

  (3)資料的結構形式是按遞迴定義的。(樹的遍歷,圖的搜尋)

 如:

  procedure a;

  begin

  a;

  end;

  這種方式是直接呼叫.

又如:

  procedure b;

  begin

  c;

  end;

  procedure c;

  begin

  b;

  end;

  這種方式是間接呼叫.

如何設計遞迴演算法

  1.確定遞迴公式

  2.確定邊界(終了)條件


Ref:

http://blog.csdn.net/zp032420/article/details/7422158

http://www.cnblogs.com/jnje/archive/2011/04/09/2010637.html