什么是递归?
古之欲明明德于天下者,先治其国;欲治其国者,先齐其家;欲齐其家者,先修其身;欲修其身者,先正其心;欲正其心者,先诚其意;欲诚其意者,先致其知,致知在格物。物格而后知至,知至而后意诚,意诚而后心正,心正而后身修,身修而后家齐,家齐而后国治,国治而后天下平。
从上一段话中,我们可以简单的理解什么叫做递归,将“明德于天下”视作一个函数,进入这个函数之后,我们不断的缩小问题规模到“治国”“齐家”“修身”,最后到作为递归基(即平凡情况)的“格物”,开始逐层返回,直到我们一开始的入口处:“明德于天下”,问题也就得到了解决。下面我们看几个典型的通过递归求解的问题
1.N皇后问题
问题描述:
输入一个正整数N,则程序输出N皇后问题的全部摆法。输出结果里的每一行都代表一种摆法。行里的第i个数字如果是n,就代表第i行的皇后应该放在第n列。皇后的行、列编号都是从1开始算。
样例输入:4
样例输出:
2 4 1 3
3 1 4 2
解题思想:
准确来说,解决N皇后问题算法思想是回溯法,我们不断的试探问题可能的解的集合,一旦发现有不符合题设的情况,就返回上一层,重新开始试探;
我们从第0行开始摆皇后,将第0~K-1行都摆好之后,我们开始摆第K行的皇后,先测试K是否已经达到了我们要测试的问题规模N(皇后数量),输出当前的解,然后返回到上一级,因为问题可能有很多种不同的解,返回到上一级再进行试探,我们要将他们一一输出。该行的操作完成后,我们递归的调用NQueen(K+1)进入到下一行,重复对上一行的操作,以实现对问题的不断推进
源代码:
#include<iostream>
#include<cmath>
using namespace std;
int N;//问题规模
int queenPos[100];
//存放结果的数组,代表放在第几列
//使用一维数组,i代表行数,queenPos[i]代表皇后在第几列
//每一次都会根据问题规模,更新前N个数据
void NQueen(int k);
int main()
{
//count=0;
cout<<"please enter a number that represents the scale of the question "<<endl;
cin>>N;
cout<<"The answer is: "<<endl;
NQueen(0);//从第0行开始摆皇后
return 0;
}
void NQueen(int k)//在0~k-1行皇后已经摆好的情况下,摆第k行及其后的皇后
{
int i;
if(k==N)//如果已经测试到了要求的问题规模,输出解
{
for(i=0;i<N;i++)
cout<<queenPos[i]+1<<" ";
cout<<endl;
return ;
//此处是回溯入口,当K=N,解决了一个情况下的摆放之后,由于函数
//是递归调用的,采用这个return返回到上一级,去测试其他情况
}
for(i=0;i<N;i++)//逐尝试第k个皇后的位置
{
int j;
for(j=0;j<k;j++)//和已经摆好的 k 个皇后的位置比较,看是否冲突,j其实代表0~k-1
{
if(queenPos[j]==i||abs(queenPos[j]-i)==abs(k-j))//看是否在同一对角线
break;//冲突,则试下一个位置
}
if(j==k) //当前选的位置 i 不冲突
{
queenPos[k]=i;//将第k个皇后摆放在位置 i
NQueen(k+1);
}
}//for( i = 0;i < N;i ++ )
}
2.表达式求解:
问题描述:
表达式计算输入为四则运算表达式,仅由整数、+、-、*、/ 、(、)组成,没有空格,要求求其值。假设运算符结果都是整数。"/"结果也是整数
样例输入:(2+3)*(5+7)+9/3
样例输出:63
解题思想:
这是一个典型的递归问题,我们可以看到,表达式其实就是一个一个的子项构成的,我们只要算出每一个子项的值,然后将他们按照表达式规定的加减法则进行计算,就能得到表达式的结果。我们又可以进而将每一个项看作是因子通过乘除构成的,那么什么是因子,也就是我们这次递归的平凡情况呢?简单来说,就是一个整数或者,被括号括起来的一个算式。所以我们可以用递归的方法不断下探,从表达式到项再到因子,得出因子的值之后再按规则逐层返回,就可以得到表达式的值。
源代码:
//我们把一个表达式分成:表达式、项、因子三个部分来看待
//表达式是由项加减而来的,项是由因子乘除而来的
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
int factor_value();
int term_value();
int expression_value();
int main()
{
cout<<expression_value()<<endl;
return 0;
}
//cin.get()的作用是从输入流中删去一个字符
int expression_value()//求表达式的值
{
int result = term_value();
bool more = true;
while(more)
{
char op=cin.peek();//瞄一眼函数,不会取走输入流中的东西
if(op=='+'||op=='-')
{
cin.get();
int value = term_value();
if(op=='+')
result += value;
else
result -= value;
}
else
more=false;
}
return result;
}
int term_value()//求一个项的值
{
int result = factor_value();
while(true)
{
char op=cin.peek();
if(op=='*'||op=='/')
{
cin.get();
int value = factor_value();
if(op=='*')
result *= value;
else
result /= value;
}
else
break;
}
return result;
}
int factor_value()//求一个因子的值
{
int result =0;
char op=cin.peek();
if(op=='(')
{
cin.get();
result=expression_value();//我们将括号内视作一个表达式来处理
cin.get();
}
else
{
while(isdigit(op))
{
result=result*10+op-'0' ;//是op不是'op'
cin.get();
op=cin.peek();
}
}
return result;
}
3.爬楼梯
问题描述:
树老师爬楼梯,他可以每次走1级或者2级,输入楼梯的级数,求不同的走法数例如:楼梯一共有3级,他可以每次都走一级,或者第一次走一级,第二次走两级,也可以第一次走两级,第二次走一级,一共3种方法。输入输入包含若干行,每行包含一个正整数N,代表楼梯级数,1 <=N <= 30输出不同的走法数,每一行输入对应一行爬楼梯输出不同的走法数,每一行输入对应一行
输出样例
输入
5
8
10
样例输出
8
34
89
解题思路:
同样,这也是一个典型的递归问题
n级台阶的走法 =先走一级后,n-1级台阶的走法 +先走两级后n-2级台阶的走法f(n) = f(n-1)+f(n-2)
问题的关键是如何设置边界条件。即负数台阶和只有零级台阶的情况,显然,负数台阶是不存在的,也就是没有走法,零级台阶就只有一种走法
源代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<memory>
int N;
using namespace std;
int stairs(int n);
int main()
{
while(cin>>N)
{
cout<<stairs(N)<<endl;
}
return 0;
}
int stairs(int n)
{
if(n<0)
return 0;
else if(n==0)
return 1;
else
return stairs(n-1)+stairs(n-2);
}
4.放苹果
问题描述:
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?5,1,1和1,5,1 是同一种分法。输入第一行是测试数据的数目t(0 <= t <= 20)。以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10。输出对输入的每组数据M和N,用一行输出相应的K。
样例输入
1
7 3
样例输出
8
解题思路:
这个问题里的盘子和苹果都是没有区别的,不用讨论编号的问题。这题的关键在于,如何将大问题分解为子问题,当有i个苹果,k个盘子的时候,当k>i时,f(i,k)=f(i,i)。因为在盘子多出来的时候,必然有盘子是不放苹果的,其实第一步就是选一波盘子扔了不要,等效于盘子和苹果数量相同的情况。当盘子数量比苹果少的时候,总放法=有盘子为空+没盘子为空,只有这两种情况。
即:f(i,k)=f(i,k-1)+f(i-k,k)。有盘子为空就是先剔除一个盘子,k-1;没盘子为空就是先每一个盘子放一个苹果,然后再考虑剩下的苹果的摆放,i-k;同时我们还要设置递归基的情况:如果没有苹果,自然就只有一种放法:空盘。如果没有盘子
源代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int f(int m,int n)
{
if(n>m)
return f(m,m);
if(m==0)
return 1;
if(n==0)
return 0;
return f(m,n-1)+f(m-n,n);
}
int main()
{
int t,m,n;
cin>>t;
while(t--)
{
cin>>m>>n;
f(m,n);
cout<<f(m,n)<<endl;
}
return 0;
}
5.24点计算
问题描述:
给出4个小于10个正整数,你可以使用加减乘除4种运算以及括号把这4个数连接起来得到一个表达式。现在的问题是,是否存在一种方式使得得到的表达式的结果等于24。这里加减乘除以及括号的运算结果和运算的优先级跟我们平常的定义一致(这里的除法定义是实数除法)。比如,对于5,5,5,1,我们知道5 * (5 – 1 / 5) = 24,因此可以得到24。又比如,对于1,1,4,2,我们怎么都不能得到24。
输入
输入数据包括多行,每行给出一组测试数据,包括4个小于10个正整数。最后一组测试数据中包括4个0,表示输入的结束,这组数据不用处理。
输出
对于每一组测试数据,输出一行,如果可以得到24,输出“YES”;否则,输出“NO”。
样例输入
5 5 5 1
1 1 4 2
0 0 0 0
样例输出
YES
NO
解题思路:
n个数算24,必有两个数要先算。这两个数算的结果,和剩余n-2个数,就构成了n-1个数求24的问题枚举先算的两个数,以及这两个数的运算方式。
边界条件:一个数算24
注意:浮点数比较是否相等,不能用 ==
源代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
double a[5];
#define EPS 1e-6;
bool isZero(double x)
{
return fabs(x)<=EPS;
}
bool count24(double a[],int n)
{
if(n==1)//只剩一个数的时候
{
if(isZero(a[0]-24))
return true;
else
return false;
}
double b[5];
for(int i=0;i<n-1;i++)
for(int j=i+1;j<n;j++)//两重循环,每次枚举两个数进行操作
{
int m=0;
for(int k=0;k<n;k++)
if(k!=i&&k!=j)
b[m++]=a[k];
b[m]=a[i]+a[j];
if(count24(b,m+1))//放入剩下n-1个数,和计算出来的一个数,看能否算出24
return true;
b[m]=a[i]-a[j];
if(count24(b,m+1))
return true;
b[m]=a[i]*a[j];
if(count24(b,m+1))
return true;
b[m]=a[j]-a[i];
if(count24(b,m+1))
return true;
if(!isZero(a[j]))//注意分母不能为0
{
b[m]=a[i]/a[j];
if(count24(b,m+1))
return true;
}
if(!isZero(a[i]))
{
b[m]=a[j]/a[i];
if(count24(b,m+1))
return true;
}
}
return false;
}
int main()
{
while(true)
{
for(int i=0;i<4;i++)
cin>>a[i];
if(isZero(a[0]))
break;
if(count24(a,4))
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
}
return 0;
}
对于递归的一些有趣理解:
1.「不宜公开讨论的政治内容」的定义和范畴本身也属于「不宜公开讨论的政治内容」
2. 递归就是包子馅的包子。它的极限是馒头。
3. 一个洋葱是一个带着一层洋葱皮的洋葱。
---------------------
作者:热心市民小黎
来源:CSDN
原文:https://blog.csdn.net/qq_33657357/article/details/79662763
版权声明:本文为博主原创文章,转载请附上博文链接!