递归问题整理

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值