您的位置:首页 > 其它

[转载] 递归问题整理

2013-08-27 19:01 148 查看
不敢说是总结,就是把自己看到的一些递归相关题目整理一下,并按照自己的理解归下类~

单路递归(一个递归过程中只有一个递归入口)

多路递归(一个递归过程中有多个入口)

间接递归(函数可通过其他函数间接调用自己)

迭代递归(每次递归调用都包含一次循环递归)

下面一一整理,注意许多题目都有更优解法,如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#includeusing 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,全排列(可网上查阅。。)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: