0 遞歸的定義
如果你沒明白遞歸的定義,參見本文"0.遞歸的定義"
1 從斐波那契數列開始
斐波那契的遞推公式
斐波那契數列遞歸算法和遞推公式類似
int fibo(int x) { if (x<3) return 1; return fibo(x-1)+fibo(x-2); }
就這么簡單?沒錯,通過這個例子可以看出,遞歸函數只需要寫兩部分,一個是遞歸終止條件(if(x<3) return 1;),一個是遞歸的“交接”(return fibo(x-1)+fibo(x-2);)
然而這個遞歸有一點問題,時間復雜度是O(fibo(n))的,因為實際上這個算法每次調用到遞歸終止條件,都只會給答案加1,如果要計算fiibo(n)則必須一個一個地加,也就是說要加fibo(b)次
問題出在哪里呢?
這是fibo(6)的遞歸樹可以發現fibo(1)被調用了3次,fibo(2)被調用了5次,fibo(3)被調用了3次...
看起來沒毛病,實際上不難發現對於同一個數x,只要計算過一遍,就沒必要再算第二遍,這就是這個算法浪費時間的地方
2.優化
int ans[202]; int fibo(int x) { if (ans[x]!=-1) return ans[x];//如果已經算過一遍,則無需再次計算 if (x<3) return 1;//遞歸初始條件 ans[x]=fibo(x-1)+fibo(x-2);//算出來以后,儲存運算結果 return ans[x]; } int main() { memset(ans,-1,sizeof(ans));//用-1初始化數組,表示沒有算過 return 0; }
花一點內存作為代價,換來的是O(n)的時間復雜度
加方框表示已經算過一次的,可以直接出結果,無需繼續遞歸
把已經算過的數據“記住”,下一次再次使用的時候可以直接讀取“記憶”,這就是所謂的記憶化
3.數字三角形
傳送門:
https://www.luogu.com.cn/problem/P1216
這是動態規划入門題目
設dp[x][y]為從第x行第y列走到底部的最優解
val[x][y]表示第x行第y個數
則
如果x不等於n
dp[x][y]=
val[x][y]+max(dp[x+1][y],dp[x+1][y+1])//遞歸“交接”,在動態規划里叫做狀態轉移
如果x=n
dp[x][y]=val[x][y]//遞歸終止條件
不難寫出遞歸算法
#include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> const int N=1003; int val[N][N],dp[N][N]; int n; int solve(int x,int y) { if (dp[x][y]!=-1) return dp[x][y]; if (x==n) return val[x][y]; int next_max=std::max(solve(x+1,y),solve(x+1,y+1)); dp[x][y]=val[x][y]+next_max; return dp[x][y]; } int main() { memset(dp,-1,sizeof(dp)); scanf("%d",&n); for (int i=1;i<=n;i++) for (int j=1;j<=i;j++) scanf("%d",&val[i][j]); int ans=solve(1,1); printf("%d",ans); return 0; }
4.漢諾塔
在印度,有這么一個古老的傳說:在世界中心貝拿勒斯(在印度北部)的聖廟里,一塊黃銅板上插着三根寶石針。印度教的主神梵天在創造世界的時候,在其中一根針上從下到上地穿好了由大到小的64片金片,這就是所謂的漢諾塔。
不論白天黑夜,總有一個僧侶在按照下面的法則移動這些金片,一次只移動一片,不管在哪根針上,小片必在大片上面。當所有的金片都從梵天穿好的那根針上移到另外一概針上時,世界就將在一聲霹靂中消滅,梵塔、廟宇和眾生都將同歸於盡。
好吧...不得不說這個故事很蛋疼
問題來了,假設移動一次需要一秒鍾,那么從開始移動到世界毀滅,需要多少時間?
我們首先探討一下如何毀滅世界
先蛋疼地把上面的63片從第一根針移動到第二根針(不管中間過程,只關心結果:63片金片移動到了第二根針上)
現在最大的那一片已經在最上面了,直接把它拿到第三根針上就可以了
然后再無比蛋疼地把那63片從第二根針上移到第三根針上(依然不用管中間過程),於是世界就這么蛋疼地毀滅了(Boom!)
下一個問題就是如何移動那63片
先把最上面62片從第一根針移到第三根針
然后把第63片移到第二根針
再把62片從第三根針移到第二根針
不難看出,只要知道如何移動n片,就可以知道如何移動n+1片
如果n=1,那就無需繼續遞歸,可以直接移動
設f(n)是移動n片所需的步數
那么f(n)=f(n-1)*2+1
f(n-1)*2是因為要把n-1片移動到一個“輔助針”上
+1是因為要移動第n片一次
利用高中的知識(也可以找規律),就可以有遞推公式得出通項公式
f(n)=2n-1
f(64)=264-1=18446744073709551615
程序員對這個數應該不陌生,剛好是unsigned long long 的上限(這讓我想起來上帝是個程序員的理論)
5.聰明的學生
一個叫獸,有三個學生,而且三個學生均非常聰明!
一天叫獸給他們出了一個題,叫獸在每個人腦門上貼了一張紙條並告訴他們,每個人的紙條上都寫了一個正整數,且某兩個數的和等於第三個!(每個人可以看見另兩個數,但看不見自己的)
叫獸會輪流詢問三個人是否已經確定自己頭上的數字
當有學生已經確定自己頭上的數字時,游戲結束
問題就是,給出三個學生頭上的數,求叫獸詢問多少次的時候游戲結束
不妨站在學生丙的角度考慮
你現在能看見另外兩個同學的數字,並且認為自己的數字有兩種情況
假設甲和乙頭上的數字分別是3和6,那么你頭上的數字就可能是3或9(6-3或6+3)
叫獸問甲,你知不知道你頭上的數字?
他說,不知道
叫獸又問乙,乙也說不知道
這時候你就在想,如果你頭上的數字是3,那么乙就會看到兩個3,由於每個人頭上的數字都是正整數,不可能是零,那么乙就可以確定他是6
但乙並沒有確定,說明你頭上的數字不是3,是9
於是當叫獸問你的時候,你說你確定你頭上的數字是9
丙之所以可以知道自己的數字,是因為他假設的錯誤情形會在第二次詢問時結束,但第二次詢問的時候還沒有結束,於是並排除了錯誤的情況
縱觀全局,三個人都會假設兩種情況,其中一種是與實際情況一致的,另一種是與實際情況不一致的
當且僅當一個人的錯誤假設被排除,游戲結束
如果計算出三個人的錯誤假設的結束時間,就可以得出游戲結束的時間
如何計算三個人的錯誤假設的結束時間?遞歸!
遞歸終止條件:如果一種情況下,兩個人頭上的數字一樣,那么那個數字與另外兩個人不一樣的人第一次被詢問時就會確定自己的數字