递归: 一个函数调用其自身。不同名字空间上的循环。
注意:使用递归策略时,必须有一个明确的递归结束条件,否则递归将会无限进行下去。
1 阶乘n!
int Factorial(int n) {
if( n == 0 ) {
return 1;
} else {
return n * Factorial(n-1);
}
}
执行
{ 注:F(3)2代表执行Factorial(3)函数的第二行 }
F(3)2 -> F(3)5 -> F(2)2 -> F(2)5 -> F(1)2 -> F(1)5 -> F(0)2 -> F(0)3:返回1 ->
F(1)5:返回1*1 -> F(2)5:返回2*1 -> F(3)5:返回3*2 -> 函数执行结束
实现
通过栈实现
递归作用
1. 替代多重循环
2. 解决本来就是用递归形式定义的问题
3. 将问题分解为规模更小的的子问题进行求解
2 N皇后
替代多重循环,八皇后问题用多重循环求解,N皇后则用递归
Wiki-国际象棋Queen走法
输入整数n,要求n个国际象棋的皇后,摆在n*n的棋盘上,同一行、同一列、同一斜线上的皇后都会自动攻击,输出全部方案。
输出结果每行代表一种摆法,行里第i个数字如果是n,就代表第i行皇后放在第n列,皇后的行列编号从1开始
样例输入
4
样例输出
2 1 3 4
3 1 4 2
思路分析(递归以及非递归思路)
#include <iostream>
#include <cmath>
using namespace std;
int N;
int queenPos[100]; // 存算好的皇后位置,最左上角是(0,0)
void NQueen(int k);
int main() {
cin >> N;
NQueen(0); // 从第0行开始摆皇后
return 0;
}
// 在0~k-1行皇后摆好情况下,摆第k行及其以后的皇后
void NQueen(int k) {
int i;
if( k==N ) { // N个皇后已经摆好
for( i=0; i<N; i++ ) {
cout << queenPos[i]+1 << " ";
}
cout << endl;
return ;
} else {
for( i=0; i<N; i++ ) { // 尝试第k个皇后的位置
int j;
for( j=0; j<k; j++ ){
// 和已经摆好的k个皇后位置比较,看是否冲突
if( queenPos[j]==i ||
abs(queenPos[j]-i)==abs(k-j) ) break; // 冲突,测试下一位置
}
if( j==k ) {
queenPos[k] == i;
NQueen(k+1);
}
} // for( i=0; i<N; i++ )
}
}
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)
边界条件 设定可以有多种做法
{0,n<01,n=0{1,n=01,n=1{1,n=12,n=2
#include<iostream>
using namespace std;
int N; // N级台阶
int stairs(int n) {
if( n<0 )
return 0;
if( n==0 )
return 1;
return stairs( n-1 ) + stairs( n-2 );
}
int main() {
int n;
while( cin>>n )
cout << stairs(n) << endl;
return 0;
}
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
分析
设 f(m,n) 为m个相同的苹果放到n个相同的盘子中的方法总数。
下面对n进行讨论,
当 n>m 时,则必有n-m个盘子是空的。去掉这些空盘子对总的摆放情况数目不影响。有 f(m,n) = f(m,m)
当 n<=m 时,所有的情况可以分成两大类,一类是有空盘的情况,一类是无空盘的情况。
1 有空盘,至少有一个空盘,这类情况数目 f(m,n-1)
2 无空盘,即所有的盘子至少一个苹果。先把每个盘子里都放一个苹果,还剩m-n个苹果;接下来就是把m-n个苹果放到n个盘子里,此类情况放法总数为 f(m-n,n)
综上
{f(m,n)=f(m,n−1)+f(m−n,n),n<=mf(m,n)=f(m,m),n>m
边界条件
当n=1时返回1;当m=0时返回1;
#include<iostream>
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;
cout << f(m,n) << endl;
}
return 0;
}
问题延伸 - 整数划分
把一个正整数n表示成一系列正整数之和,加数s不超过m。
6
5+1
4+2,4+1+1
3+3,3+2+1,3+1+1+1
2+2+2,2+2+1+1,2+1+1+1+1
1+1+1+1+1+1
⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪f(1,m)=1,m>=1f(n,1)=1,n>=1f(n,m)=f(n,n),m>=nf(n,n)=1+f(n,n−1)f(n,m)=f(n,m−1)+f(n−m,m),n>m>1只有一种划分即1个1只有一种划分即n个1最大加数m不能超过ns=n的划分和s<=n-1的划分s=m的划分和s<=m-1的划分
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的问题
枚举先算的两个数,以及这两个数的运算方式。
注意:浮点数比较是否相等,不能用 ==
边界条件
#include<iostream>
#include<cmath>
using namespace std;
double a[5];
#define EPS 1e-6 //10^(-6),当浮点数小于这个数可认为是0
bool isZero(double x){ //判断浮点数是否为0
return fabs(x) <= EPS;
}
bool count24(double a[], int n) { //数组a中n个数能否算出24
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++) { //将剩下的n-2个数存放到数组b中
if( k!=i && k!=j )
b[m++] = a[k];
}
b[m] = a[i]+a[j]; //元素b[m]是a[i]和a[j]相加
if( count24(b,m+1) ) return true;
b[m]=a[i]-a[j]; //元素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;
b[m]=a[i]*a[j]; //元素b[m]是a[i]和a[j]相乘
if( count24(b,m+1) ) return true;
if( !isZero(a[j]) ) { //元素b[m]是a[i]和a[j]相除
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++ ) //输入4个元素
cin >> a[i];
if( isZero(a[0]) && isZero(a[1]) && isZero(a[2]) && isZero(a[3]) )
break;
if(count24(a,4)) //可以算出24
cout << "YES" << endl;
else
cout << "NO" << endl;
}
return 0;
}
6 汉诺塔
有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆:
1 每次只能移动一个圆盘
2 大盘不能叠在小盘上
提示:可将圆盘临时置于B杆,也可将从A杆移出的圆盘重新移回A杆,但都必须遵循上述两条规则。
问:如何移?最少要移动多少次?
#include <iostream>
using namespace std;
// 将A的盘子以B为中转移动到C盘
void Hanoi(int n, char A, char B, char C) {
if( n==1 ) { // 只需要移动一个盘子
cout << A << "->" << C << endl; // 直接将盘子从A移动到C
return ; // 递归终止
} else {
Hanoi( n-1, A, C, B ); // 将n-1个盘子从src移动到B
cout << A << "->" << C << endl; // 再将一个盘子从A移到C
Hanoi( n-1, B, A, C ); // 最后将n-1个盘子从B移到C
return ;
}
}
int main() {
int n;
cin >> n;
Hanoi( n, 'A', 'B', 'C' );
return 0;
}