不敢說是總結,就是把自己看到的一些遞歸相關題目整理一下,並按照自己的理解歸下類~
- 單路遞歸(一個遞歸過程中只有一個遞歸入口)
- 多路遞歸(一個遞歸過程中有多個入口)
- 間接遞歸(函數可通過其他函數間接調用自己)
- 迭代遞歸(每次遞歸調用都包含一次循環遞歸)
- 下面一一整理,注意許多題目都有更優解法,如DP,但是暫不討論。
先說說解遞歸的一般思路吧,把原問題分解爲更小的子問題,再從子問題裏慢慢尋找原問題的解。實際上遞歸是一種思路,解題時首先列出遞歸表達式,然後用程序語言的方式把他表現出來。往往遞歸都可轉化爲循環或者模擬調用棧來實現,但是遞歸表達更利於理解。
一,單路遞歸(遞歸鏈)
1,求n的階乘(經典實例)
int factorial(int n)
{
if (n == 0) //基線條件(base case)
{
return 1;
}
else
{
return n * factorial(n - 1); //將問題規模逐漸縮小,或者說轉化爲更小更簡單的子問題
}
}
2,堆結構的維護(可參考數據結構相關書籍)
上面的例題是比較簡單的單路遞歸,類似的還有遞歸遍歷目錄下所有文件,下面介紹複雜一點的,不同的遞歸調用在不同的條件判斷語句裏,這也是單路遞歸:)
3,迷宮問題(實際上屬於深度優先搜索的範疇)
通用解法:
bool FindPathThroughMaze( Maze maze, Point position ){ //if the position has already been tried, don't try it again if( AlreadyTried( maze, position ) ) { return false; } //if this position is the exit, declare success if( ThisIsTheExit( maze, position ) ) { return true; } //Remember that this position has been tried RememberPosition( maze, position ); //check the path to the left, up, down, and to the right; if //any path is successful, stop looking if( MoveLeft( maze, position, &newPosition ) ) { if( FindPathThroughMaze( maze, newPosition ) ) { return true; } } if( MoveUp( maze, position, &newPosition ) ) { if( FindPathThroughMaze( maze, newPosition ) ) { return true; } } if( MoveDown( maze, position, &newPosition ) ) { if( FindPathThroughMaze( maze, newPosition ) ) { return true; } } if( MoveRight( maze, position, &newPosition ) ) { if( FindPathThroughMaze( maze, newPosition ) ) { return true; } } //can't find the exit. return false; }
4,POJ 2756 http://poj.grids.cn/problem?id=2756
思路:
- 這個題目要求樹上任意兩個節點的最近公共子節點。分析這棵樹的結構不難看出,不論奇數偶數,每個數對2 做整數除法,就走到它的上層結點。
- 我們可以每次讓較大的一個數(也就是在樹上位於較低層次的節點)向上走一個結點,直到兩個結點相遇。
- 如果兩個節點位於同一層,並且它們不相等,可以讓其中任何一個先往上走,然後另一個再往上走,直到它們相遇。
- 設common(x, y)表示整數x 和y的最近公共子節點,那麼,根據比較x 和y 的值,我們得到三種情況:
- (1) x 與y 相等,則common(x, y)等於x 並且等於y
- (2) x 大於y,則common(x, y)等於common(x/2, y)
- (3) x 大於y,則common(x, y)等於common(x y/2)
所以程序如下:
#include int common(int x, int y) { if(x == y) return x; if(x > y) return common(x / 2, y); return common(x, y / 2); } int main(void) { int m, n; scanf("%d%d", &m, &n); printf("%d/n", common(m, n)); return 0; }
5,二分查找(經典例子,可查閱數據結構相關書籍)
二,多路遞歸(遞歸樹)
1,樹的前中後序遍歷(經典例子,也形象地反映了多路遞歸過程的樹狀結構)
2,合併排序,快速排序(都是將問題化解我兩個更小的子問題去解決,也是樹狀結構,各節點是動態構造的)
3,放蘋果(POJ 1664)
問題描述:
把M個同樣的蘋果放在N個同樣的盤子裏,允許有的盤子空着不放,問共有多少種不同的分法? 5,1,1和1,5,1 是同一種分法。(1<=M,N<=10)
如輸入M = 7, N = 3,應輸出8
(7 0 0 )(6 1 0 )(5 2 0 )(5 1 1 )(4 3 0 )(4 2 1 )(3 3 1 )(3 2 2)
思路分析:
- 所有不同的擺放方法可以分爲兩類:至少有一個盤子爲空和所有盤子都不空。對於至少空着一個盤子的情況,則N 個盤子擺放M 個蘋果的擺放方法數目與N-1 個盤子擺放M 個蘋果的擺放方法數目相同。對於所有盤子都不空的情況,則N 個盤子擺放M 個蘋果的擺放方法數目等於N 個盤子擺放M-N 個蘋果的擺放方法數目。我們可以據此來用遞歸的方法求解這個問題。
- 設f(m, n) 爲m 個蘋果,n 個盤子的放法數目,則先對n 作討論,如果n>m,必定有n-m 個盤子永遠空着,去掉它們對擺放蘋果方法數目不產生影響;即if(n>m) f(m,n) =f(m,m)。當n <= m 時,不同的放法可以分成兩類:即有至少一個盤子空着或者所有盤子都有蘋果,前一種情況相當於f(m , n) = f(m , n-1); 後一種情況可以從每個盤子中拿掉一個蘋果,不影響不同放法的數目,即f(m , n) = f(m-n , n)。總的放蘋果的放法數目等於兩者的和,即 f(m,n) =f(m,n-1)+f(m-n,n) 。整個遞歸過程描述如下:
int ways(int m, int n) { if (m == 0 || n == 1) //base case return 1; else if (m < n) return ways(m , m); else return ways(m, n - 1) + ways(m - n, n); //轉化爲更小更簡單的子問題 }
4,斐波那契數(遞歸方法是很搓的,勿用)
int Fib( int n ){ if( n < 2 ) return; else return Fib(n-2) + Fib(n-1); }
5,最大子段和(最優解法是DP)
求a[1:n]的最大子段和可分解爲a[1:n/2], a[n/2+1,n]的最大子段和情況,有如下3種情況
a[1:n]的最大子段和與a[1:n/2]的最大子段相等;
a[1:n]的最大子段和與a[n/2+1,n]的最大子段相等;
a[1:n]的最大子段和經過a[1:n/2]與a[n/2+1,n],這種情況最大子段和等於兩個最大子段和相加;
代碼:
int MaxSubSum(int *a, int left, int right) { int sum = 0; if(left == right) sum = a[left] > 0 ? a[left] : 0; else { int center = (left + right) / 2; int leftsum = MaxSubSum(a, left, center); int rightsum = MaxSubSum(a, center+1, right); int s1 = 0; int lefts = 0; for(int i = center; i >= left; i--) { lefts += a[i]; if(lefts > s1) s1 = lefts; } int s2 = 0; int rights = 0; for(int i = center + 1; i <= right; i++) { rights += a[i]; if(rights > s2) s2 = lefts; } sum = s1 + s2; sum = (sum < leftsum ? leftsum : sum); sum = (sum < rightsum ? rightsum : sum); } return sum; }
三,間接遞歸(類似遞歸鏈)
1,遞歸下降解釋器(解釋器用的比較多,以後我會寫篇相關文章)
原理就是exp1()->exp2()->exp3()->………..->exp1(),其中按優先級,越後面的表達式優先級越高,然後遞歸又從exp1()調用。
四,迭代遞歸(類似圖的深度優先遍歷)
1,圖的深度優先遍歷
通用深度優先搜索框架:
void dfs(int deep, State curState) { if (deep > Max) //深度達到極限 { if (curState == target) //找到目標 { //... } } else { for (i = 1; i <= totalExpandMethod; i++) { dfs(deep + 1, expandMethod(curState, i)); }
}
}
2,廣義水仙花數
問題描述:一個三位數abc如果滿足abc = a^3 + b^3 + c^3 那麼就把這個數叫做水仙花數。
如果一個N位數所有數碼的N次方的和加起來等於這個數字本身,我們把這樣的數叫做廣義水仙花數,容易看出來水仙花數是N = 3的廣義水仙花數現在,我們的任務是,輸入一個m (m < 7) ,讓你求出所有滿足N = m的廣義水仙花數。
3 (153 370 371 407)
5 (54748 92727 93084)
方法:數據規模很小,可以直接枚舉所有情況,然後判斷是否滿足條件。
難點:循環層數不確定
於是我們現在的問題是,怎麼實現這個m重循環?
答案是:遞歸。
完整代碼:
#include #include using namespace std; int m; int Pow(int x, int n) { int res = 1; while (n--) res *= x; return res; } void dfs(int deep, int curNum, int curSum) { if (deep > m) //類似於base case { if (curNum == curSum) printf("%d/n", curNum); } else if (deep <= m) { int start = (deep == 1); //第一位不爲0 for (int i = start; i <= 9; i++) dfs(deep + 1, curNum * 10 + i, curSum + Pow(i, m)); //縮小問題規模 } } int main() { while (scanf("%d", &m), m) { dfs(1, 0, 0); } return 0; }
3,全排列(可網上查閱。。)