基礎演算法:遞迴
遞迴用處很廣,可以將複雜問題簡單化。
很多問題都可以使用遞迴演算法或結合遞迴演算法得到解決。
那麼,設計遞迴演算法的關鍵是什麼? 其關鍵之處在於,正確分析出2種類型的節點:出口節點和入口節點
一 演算法關鍵: 出口節點 , 入口節點
遞迴問題可看做是由各個節點構成,而所有節點只能分為出口節點、入口節點兩類。
1)出口節點: 可直接計算此節點的值,不需藉助其他節點
那什麼情況下,不必再遞迴了呢?
確定結果了,自然就不用繼續遞迴了,這也是遞迴的出口, 遞迴的根基。 所以這個根基必須判斷好,這是遞迴是否成功的一個關鍵之處。
因此,第一個的關鍵是:確定哪些節點是出口節點。
2)入口節點: 無法直接計算此節點的值,但此節點的值可根據後續節點的值計算。
入口節點與後續節點的數學或邏輯關係, 且如何用遞迴方式表示這個關係,則是遞迴演算法的第二個關鍵。
二 什麼問題可以用遞迴來處理?
遞迴的過程,就是搜尋的過程,搜尋一個個的節點,找到出口節點。
若問題滿足下面條件,則可遞迴解決:
A) 問題可看做為在很多節點中進行搜尋
B) 這些節點或者為出口節點 或者為入口節點
C) 入口節點和後續節點間的關係 可用遞迴方式描述
D) 出口節點在入口節點的後續路徑中
下面舉幾個例子:
1 遞增
第5個人的年齡比第4個的年齡大2歲,第4個人的年齡比第3個的年齡大2歲,第3個人的年齡比第2個的年齡大2歲,第2個人的年齡比第1個的年齡大2歲,第1個的年齡10歲.
[cpp] view plaincopy
- <strong>int age(int n)
- {
- int c;
- //是否為出口節點,若是,則不需繼續遞迴
- if(n==1)
- c=10;
- else // 入口節點與後續節點的關係
- c=age(n-1)+2;
- return c;
- }
- </strong>
2 階乘
5的階乘是4的階乘*5,4的階乘是3的階乘*4,3的階乘是2的階乘*3,2的階乘是1的階乘*2,1的階乘是1的時候。
[cpp] view plaincopy
- int fac(int n)
- {
- int c;
- if(n==1) c=1;
- else c=fac(n-1)*n; // 將前n-1個看做一個處理單元
- return c;
- }
3 斐波那契數列
第一個和第二個數分別為1和1,從第三個數開始,每個數等於其前兩個數之和(1,1,2,3,5,8,13…………)
[cpp] view plaincopy
- int rabbitNum(int n)
- {
- if (n==1||n==2) // 不需遞迴 找到出口
- {
- return n;
- }else{
- return rabbitNum(n-1)+rabbitNum(n-2);
- }
- }
4 漢諾塔
[cpp] view plaincopy
- void Hannoi(int n,TCHAR a,TCHAR b,TCHAR c)
- {
- // 已經確定答案了,不需遞迴的情況
- if (n==1)
- {
- Move(1,a,c);
- }else{ //需要繼續遞迴的情況, 在這種情況下,關鍵是看:如何將問題劃分為子問題處理單元,然後處理各個單元間的關係
- // 本例: 將n個盤子 分成兩個單元: 前n-1個盤子為一單元 、最後一個盤子為一單元
- Hannoi(n-1,a,c,b);// 將第一個單元 從 a 移動到 b
- Move(n,a,c); // 將第二個單元 從 a 移動到 c
- Hannoi(n-1,b,a,c);// 將第一個單元 從 b 移動到 c
- }
- }
5 將輸入的字元以相反順序打印出來
[cpp] view plaincopy
- #include <stdio.h>
- #include <string.h>
- void strv(TCHAR* p){
- if(!*p) // 不需繼續遞迴 遞迴出口
- return;
- // 當p+1(包括p+1)之後的作為一個處理單元
- strv(p + 1);
- // 處理完畢 第一個單元后,輸出本單元*p
- putchar(*p);
- };
- int main(){
- TCHAR* p = _T("ABCDEFGHIJK");
- strv(p);
- return 0;
- };