1. 程式人生 > >遞迴+記憶化搜尋

遞迴+記憶化搜尋

邊界條件與遞迴方程是遞迴函式的兩個要素。

1)階乘函式

直接打板子:

Int fac(int n)

{

If (n==0) return 1;

Else return n*fac(n-1);

}

這裡,第一句的if是邊界條件,第二句是遞迴方程。0的階乘為1,n的階乘為(n-1)的階乘再乘n。

2)漢諾塔問題

本來覺得它是個不可解決的難題,實則不然,區區遞迴即可解決:

Void move(int n,int a,int b,int c)

{

If (n==1) cout<<a<<”->”<<c<<endl;

Else

{

Move(n-1,a,c,b);

Cout<<a<<”->”<<c<<endl;

Move(n-1,b,a,c);

}

這個問題,是個問題。首先,明確一下這裡形參的含義:n表示現在有n個盤需要移動,a表示盤子現在在哪裡,b表示中間的媒介,c表示盤子要去哪裡。然後,這裡的if語句還是邊界,就是當n=1的時候,也就是現在只有一個盤子的時候,直接移動並輸出路徑。底下else語句中的是主體:因為當前有n個盤子需要移動,而最終目的是讓第n號盤子從a移動到c,因此我們分為三步:①把上面的n-1個盤子看做一個整體,從a先移動到b;②把第n號盤子從a移動到c,輸出路徑;③把那n-1個盤子再從b移動到c。

遞迴函式的執行流程分為兩個階段:遞迴前進段、遞迴返回段。

這可以說一個很神奇的東西。總的來說就是:遞迴前進段就是你按它函式的順序執行下去,直到碰到邊界;遞迴返回段就是當你碰到了邊界,你把剛剛所有遞迴巢狀的函式值算出來,一步一步返回值,返回給最初的值。

這裡有一個我很容易搞錯的東西,也很難理解的東西:沒每遞迴呼叫一次函式,系統就會生成一個新的函式例項。這些函式例項有同名的引數和區域性變數,但各自獨立,互不干擾。流程執行到哪一層,那一層的變數就起作用,返回上一層,就釋放掉低層的同名變數。這個需要深刻理解一下。

3)斐波那契數列

板子很簡單:

Int f(int n)

{

If (n<=2) return 1;

Else return f(n-2)+f(n-1);

}

接下來學一個記憶化搜尋(是個難點)

來看一下斐波那契數列的板子:

Int f[max]={0};

Int f(int n)

{

If (f[n]!=0) return f[n];

If (n<=2) return f[n]=1;

Else return f[n]=f(n-2)+f(n-1);

}

可以這麼理解:你先打一個不用記憶化搜尋的板子,然後把它轉換為記憶化搜尋。記憶化搜尋的話,要多加一個數組,用來儲存已經求過的函式值。然後,你再在遞迴函式裡面先加一句話——if (這個陣列的值不為0) return 這個值;另外,後面的遞迴返回值裡在前面多加一個——“陣列=”遞迴返回值。也不知道看不看得懂,反正我大概知道是怎麼用的了。

還是斐波那契數列,板子還可以這樣打:

Int t1,t2,r;

T1=t2=r=1;

For (int i=3;i<=n;++i)

{

R=t1+t2;

T1=t2;

T2=r;

}

Cout<<r;

這種方法連陣列都不用。

遞迴構建有三個條件:1)引數;2)邊界;3)範圍。

據此來分析遞迴過程如何寫。

1) 引數:明確引數的意義以及當前的值;

2) 邊界:一個遞迴函式一定要有邊界,而且邊界一定要考慮全面,不能漏,否則它可能死迴圈;

3) 範圍:就是你在遞迴時的選擇往哪兒走,也就是說,你的遞迴呼叫的函式返回值。

然後我們現在再來看一下記憶化搜尋:

①定義好一個數組,用來儲存遞迴所求出來的值;②在主程式裡,memset一下,一般都是賦初值為-1,然後把這個陣列的邊界值設定好;③在遞迴函式裡,首先加一句:if (這個陣列的值>=0 return 這個值【如果賦初值為-1的話,一般都是>=0】;其次,在後面的遞迴呼叫中,先給這個陣列賦值,再return。

這樣就差不多了。