递归问题整理

不敢说是总结,就是把自己看到的一些递归相关题目整理一下,并按照自己的理解归下类~

  • 单路递归(一个递归过程中只有一个递归入口)
  • 多路递归(一个递归过程中有多个入口)
  • 间接递归(函数可通过其他函数间接调用自己)
  • 迭代递归(每次递归调用都包含一次循环递归)
  • 下面一一整理,注意许多题目都有更优解法,如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,全排列(可网上查阅。。)
 
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章