递归算法

本文深入浅出地讲解了递归算法的基本概念及其在多种典型问题中的应用,包括汉诺塔问题、图形绘制、树的遍历等,并通过具体实例展示了递归的思想和实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

递归算法:有点像高中学的数列知识,定义说不清楚,可以百度,最常见的斐波那契数列的通项公式就可以使用递归的方法实现,还有一个就是汉诺塔的问题,又称河内塔,递归实现的方法是: 

#include<iostream>
#include<cstdio>
using namespace std;
int step,n;
void hanoi(int m,char a,char b,char c)//将a上编号为1到m的圆盘从a经过b移动到c 
{   if(m==1) printf("%d:Move %d from %c to %c\n",step++,m,a,c);//打印移动过程,将编号为m的圆盘由a移动到c 
    if(m==1) return ;
    hanoi(m-1,a,c,b);//将a上编号为1到m-1的圆盘从a经过c移动到b 
    printf("%d:Move %d from %c to %c\n",step++,m,a,c);//将编号为m的圆盘从a移到c  
    hanoi(m-1,b,a,c);//将b上编号为1到m-1的圆盘从b经过a移动到c 
}
int main()
{   while(cin>>n,n!=-1)
    {   step=1;
        hanoi(n,'a','b','c');  
        printf("%d\n",step-1);//n个圆盘移动到目标过程的步数为:2^n-1 
    }
    return 0;
} 

以汉诺塔为内容的题目HDU上有很多,从汉诺塔(I).....一直到了好多,可以做下~~~

例题2:poj 2083题目链接:http://poj.org/problem?id=2083

题目分析:题目已经给出里一个模型,这五个位置的图形都是一样的,只是位置不同,可以先计算出对于一般的n这个图形输出有多少行,可知有这样一个递推的关系式:mi[n]=3*mi[n-1],即mi[n-1]=3^(n-1),所以当n=7时为729行,我们可以设左上角的图形的位置为(Startx,Starty),其余的坐标就都可以用这个表示出来了,

B(n - 1) (左上角)                            B(n - 1)(右上角)

(Startx,Starty)                        (Startx,Starty+2*mi[m-2])

                            B(n - 1)(中间)

              (Startx+mi[m-1],Starty+mi[m-2])

B(n - 1)(左下角)                                  B(n - 1)(右下角)

(Startx+2*mi[m-1],Starty)       (Startx+2*mi[m-1],Starty+2*mi[m-1])

#include<iostream>
#include<cstring>
using namespace std;
const int MAX=729;
#define CLR(arr,val) memset(arr,val,sizeof(arr))
int n,mi[10]={1,3,9,27,81,243,729,2187};
char map[MAX][MAX];
void Create(int Startx,int Starty,int m)
{   if(m==1) map[Startx][Starty]='X';
    if(m==1) return ;    
    Create(Startx,Starty,m-1); //左上角图形 
    Create(Startx,Starty+2*mi[m-2],m-1); //右上角图形 
    Create(Startx+mi[m-2],Starty+mi[m-2],m-1); //中间图形 
    Create(Startx+2*mi[m-2],Starty,m-1); //左下角图形 
    Create(Startx+2*mi[m-2],Starty+2*mi[m-2],m-1); //右下角图形 
}
int main()
{   while(cin>>n,n!=-1) 
    {   CLR(map,' ');
        Create(0,0,n); 
        for(int i=0;i<mi[n-1];i++)
        {   for(int j=0;j<mi[n-1];j++)
                cout<<map[i][j];
            cout<<endl;
        }
        cout<<"-"<<endl;   
    }
    return 0;
}

例题3:poj 2255题目链接:http://poj.org/problem?id=2255

题目大意是给出前序和中序遍历,输出后序遍历。可以使用递归实现:先序遍历(根左右)的第一个字母是根,然后我们可以在中序遍历中找到根的左子树和右子树,例如题目中第一个测试数据:DBACEGF ABCDEFG,我们由先序遍历知D为根结点,由中序遍历知D的左子树为(ABC),右子树为(EFG),同样对D的左右子树进行前序遍历,后进行中序遍历,可知这棵树的结构,然后只需后序遍历输出即可。当然更为简单的方法就是直接在递归中输出后序遍历结果,而可以减少创树的过程。

#include<iostream>
#include<string>
using namespace std;
#define len(s) s.length()
void CreateTree(string s1,string s2)
{   if(len(s1)<=0) return ;
    int pos=s2.find(s1[0]); //找到根节点在中序遍历中的位置 
    CreateTree(s1.substr(1,pos),s2.substr(0,pos)); //根的左子树递归 
    CreateTree(s1.substr(pos+1,len(s1)-pos),s2.substr(pos+1,len(s1)-pos)); //根的右子树递归
    cout<<s1[0]; //遵循后序遍历:左右根 
}
int main()
{   string s1,s2;
    while(cin>>s1>>s2)
    {   CreateTree(s1,s2);
        cout<<endl;              
    }
    return 0;
}

例题4:百练2756题目链接:http://poj.grids.cn/practice/2756,简单的题目,还是中文题Nice!!!只写下递归的代码:

int Tree(int x,int y)
{   if(x>y) return Tree(x/2,y);
    if(x<y) return Tree(x,y/2);
    if(x==y) return x; 
}

例题5:百练1664题目链接:http://poj.grids.cn/practice/1664

设f(m,n)表示有m个苹果要放入n个盘中的放法总数,

(1)、假设只有一个盘子或者苹果为0时,那么肯定只有一种放法,即:f(m,1)=1或f(0,n)=1;

(2)、盘子数n>m,那么必定有盘子空着,此时就相当于只有m的盘子一样,即:f(m,n)=f(m,m);

(3)、盘子数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 f(int m,int n)
{   if(n==1||m==0) return 1;
    if(n>m) return f(m,m); 
    return f(m,n-1)+f(m-n,n);
} 

同这种做法的题目还有:足球赛票

题目大意是:有一场很激烈的球赛开始前,会进行售票工作,每张球票为50元,现有2n个人排队等着买票,其中有n个人手中拿着50元的钱,其余n个人拿着100元的钱,开始的时候售票处没有零钱,问这2n个人有多少种排队方式,使售票处不至于找不开钱的情况? 

输入:一个非负整数n(1<=n<=1000),输出这2n个人的排队方式,例如输入:3 4输出:5 14

我们同样可设f(m,n)为有m个人拿50元的钱,n个人拿100元 的排队方法总数,

(1)当n=0时,那么这m个人排队的方式就只有一种:即f(m,0)=1;

(2)当m<n时,无论怎么排都会存在找不开钱的情况,即f(m,n)=0;

(3)当m>=n时,有(m+n)个人排队买票,可以假设第(m+n)个人站在第(m+n-1)个人的后面,那么:

     ①、若第(m+n)个人拿的是100元的,那么在他之前的(m+n-1)个人中必定有m个人拿50元的钱,有n-1个人拿100的,这时有f(m,n-1)种方法

     ②、若第(m+n)个人拿的是50元的,那么在他之前的(m+n-1)个人中必定有m-1个人拿50元的,有n个人拿100的,这时有f(m-1,n)种方法

由加法原理有:f(m,n)=f(m-1,n)+f(m,n-1)种方法。

=========================================================================================================

Stirling数:包含m个元素的集合划分为正好n个非空子集的方法的数目。

递推式为:f(m,n)=0;(m<n||n=0);f(m,m)=f(m,1)=1;f(m,n)=f(m-1,n-1)+n*f(m-1,n);

分析:设有m个不同 的元素,从中取出某个元素a,那么这个a有两种放法:

①、a自己划分为单个子集,那么其他的m-1元素只能划在n-1个子集中;

②、他和别的球放在同一个盒子,那么只需把其余的m-1个元素放在n个子集中,后将该元素插入到各个盒子中,有k种插法,所以n*f(m-1,n)

=========================================================================================================

递归调用之数的划分:nyist 90,276

例题1:nyist 90题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=90

同上面两例,设f(m,n)为将整数m划分为小于或等于n份的划分总数。

①、当m=1或n=1时,一定只有一种划分方法,即:f(m,1)=f(1,n)=1;

②、当m<n,即需划分的数小于划分的个数,那么就相当于将m划分为m份,即:f(m,n)=f(m,m)(m<n)

③、当m=n时,那么就相当于将m划分为n-1份+1,即f(m,n)=f(m,n-1)+1;(这里不规定划分份数)

其它m>n时,假设未分为n份的话,(因为在这里不规定划分的份数),那么此时减少一份的话仍然不改变结果,即:f(m,n)=f(m,n-1);假设分为n份的话,那么从每一份中拿走一个1也不会对结果造成影响,即:f(m,n)=f(m-n,n),由加法原理有:f(m,n)=f(m,n-1)+f(m-n,n);

#include<iostream>
using namespace std;
int f(int m,int n)
{   if(n==1||m==1) return 1;
    if(m<n) return f(m,m);
	if(m==n) return f(m,n-1)+1;
	return f(m,n-1)+f(m-n,n);
}
int main()
{   int m,n;
    cin>>n;
	while(n--)
	{   cin>>m;
	    cout<<f(m,m)<<endl;
	}
	return 0;
}

涉及到递归的题目有POJ 1979(红与黑)--简单的DFS,3768(图形问题----和2083比较的相似),2506(只是用递归思想---分析:题目意思是有两种规格的地板2*1和2*2的地板,要拼成2*n的地板,总共有多少种方法?用a[n]表示,若是拼成了2*(n-1)的地板,最后一块就只能用2*1的地板,若拼成了2*(n-2)的地板,就能用一块2*2的地板或两块2*1的地板,所以递推式为:a[n]=a[n-1]+2*a[n-2],考虑到n的大小,得用高精度。),1145(树的遍历),1941(又是一个画图形的题目),2676(数字九宫格问题--DFS)2790(迷宫问题---DFS),3931(约瑟夫环求人数--递归),1321(棋盘问题,类似于八皇后问题--DFS),2876等.

贴下POJ 3768的代码:

#include<iostream> 
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int MAX=3010;
#define mi(n,m) (int)pow((double)n,(double)m) 
#define CLR(arr,val) memset(arr,val,sizeof(arr))
char graph[10][10],map[MAX][MAX];
int n,m;
void copy(int x,int y)//在(x,y)的位置复制图形
{   for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            map[x+i][y+j]=graph[i][j]; 
} 
void Create(int Startx,int Starty,int num)
{   if(num==1) copy(Startx,Starty);
    else
    {   for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                if(graph[i][j]!=' ') Create(Startx+i*mi(n,num-1),Starty+j*mi(n,num-1),num-1);
    }
}
int main()
{   while(scanf("%d",&n),n!=0)
    {   getchar();
        CLR(map,' ');
        for(int i=0;i<n;i++)
            gets(graph[i]);    
        scanf("%d",&m);
        Create(0,0,m);
        for(int i=0;i<mi(n,m);i++)
        {   map[i][mi(n,m)]='\0';//使用puts输出时候要注意!!!! 
            puts(map[i]); 
        }
    }
    return 0;
}

POJ 1941代码(仔细点即可,记得每组数据后一个空行)

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAX=2050;
#define CLR(arr,val) memset(arr,val,sizeof(arr))
char map[MAX][MAX];
int n,mi[12]={1,2,4,8,16,32,64,128,256,512,1024,2048}; 
void Graph(int x,int y)
{   map[x+1][y-1]=map[x][y]='/';
    map[x+1][y]=map[x+1][y+1]='_';//注意'_'前后的空格,这个已经可以了~~ 
    map[x][y+1]=map[x+1][y+2]='\\';//'\'的表示法 
} 
void Create(int Startx,int Starty,int m)
{   if(m==1) Graph(Startx,Starty);
    if(m==1) return ; 
    Create(Startx,Starty,m-1);
    Create(Startx+mi[m-1],Starty-mi[m-1],m-1);
    Create(Startx+mi[m-1],Starty+mi[m-1],m-1);   
}
int main()
{   while(scanf("%d",&n)&&n)
    {   CLR(map,' ');
        Create(1,mi[n],n); 
        for(int i=1;i<=mi[n];i++)
        {   for(int j=1;j<=mi[n+1];j++)//注意最底下的一行是2^(n+1)个字符 
                putchar(map[i][j]);
            printf("\n");    
        }
        printf("\n");
    }
    return 0;
}


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值