目录
递归概念
递归特点
递归作用
例题
1.求阶乘n!
2.汉诺塔问题
3.N皇后问题
4.逆波兰表达式
一个函数调用其自身,就是递归
主要特点有两个
1) 替代多重循环
2) 解决本来就是用递归形式定义的问题
3) 将问题分解为规模更小的子问题进行求解
- 题目
输入一个数,求其阶乘。
- 输入
一个正整数N
- 输出
N的阶乘
- 样例输入
4
- 样例输出
24
解题思路
这道题一般都是递归的入门题目,其实可以使用循环快速求解,并且不会占用栈的空间,但是这道题目也是我们理解递归的起点,也是规模分解的例子,所以还是来介绍一下,来分析一下基准情况以及递归调用。
- 基准情况
1!=1
- 不断推进
N!= N*(N - 1)!
代码实现如下所示
#include
int Factorial(int n)
{
// 基准情况
if (n == 0)
return 1;
// 不断推进
else
return n * Factorial(n - 1);
}
int main(void)
{
int N;
scanf("%d", &N);
printf("%d",Factorial(N));
return 0;
}
运行结果
总结
一般来说,递归都会用到if-else判断语句,if为基准情况,else后面的为递归调用,当判断好了之后,列出相应表达式,即可求解。
- 题目
古代有一个梵塔,塔内有三个座A、B、C,A座上有64个盘子,盘子大小 不等,大的在下,小的在上(如图)。有一个和尚想把这64个盘子从A座移到C座,但每次只能允许移动一个盘子,并且在移动过程中,3个座上的盘子始终保持大盘在下,小盘在上。在移动过程中可以利用B座,要求输出移动的步骤。
- 输入
一个正整数N
- 输出
移动的步骤。
- 样例输入
3
- 样例输出
A -> C
A -> B
C -> B
A -> C
B -> A
B -> C
A -> C
解题思路
又是一道入门级的递归题,也是用到了规模分解的思想。
- 基准情况
移动一个盘子时,直接从A移到C
- 不断推进
简化为把N-1个盘子移到B,再移动最后一个盘子,再把N-2个盘子移到c,如此循环。
代码如下所示
#include
void Hanoi(int n, char src,char mid,char dest) //将src座上的n个盘子,以mid座为中转,移动到dest座
{
if( n == 1) { //只需移动一个盘子
printf("%c -> %c\n", src, dest);//直接将盘子从src移动到dest即可
return ; //递归终止
}
Hanoi(n-1,src,dest,mid); //先将n-1个盘子从src移动到mid
printf("%c -> %c\n", src, dest);
//再将一个盘子从src移动到dest
Hanoi(n-1,mid,src,dest); //最后将n-1个盘子从mid移动到dest
return ;
}
int main()
{
int n;
scanf("%d",&n); //输入盘子数目
Hanoi(n,'A','B','C');
return 0;
}
运行结果如下所示
总结
其实还是递归的基本方法,找到基准情况,找到不断推进条件,就能解决相应的问题。
- 题目
n皇后问题:输入整数n, 要求n个国际象棋的皇后,摆在 n*n的棋盘上,互相不能攻击,输出全部方案。(皇后可以横竖斜吃子)
- 输入
输入一个正整数N
- 输出
输出N皇后问题的全部摆法。 输出结果里的每一行都代表一种摆法。行里的第i个数字如果是n,就代表第i行的皇后应该放在第n列。 皇后的行、列编号都是从1开始算。
- 样例输入
4
- 样例输出
2 4 1 3
3 1 4 2
解题思路
之前遇到过八皇后的题目,用八重循环来解决,现在是N皇后,不能使用N重循环了,就可以使用我们的递归来解决,用递归替代多重循环是递归的一个比较常见的用途。
- 基准条件
摆一个皇后
- 不断推进
一直摆到N个皇后且符合条件
代码如下所示
#include
#include
int N = 0;
int queenPos[100];
void NQueen( int k) { //在0~k-1行皇后已经摆好的情况下,摆第k行及其后的皇后
int i;
if( k == N ) { // N 个皇后已经摆好
for( i = 0; i < N;i ++ )
printf("%d ",queenPos[i] + 1 );
printf("\n");
return ;
}
// 遍历逻辑
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 ) { //当前选的位置 i 不冲突
queenPos[k] = i; //将第k个皇后摆放在位置 i
NQueen(k+1);
}
} //for( i = 0;i < N;i ++ )
}
int main(void)
{
scanf("%d", &N);
NQueen(0); //从第0行开始摆皇后
return 0;
}
运行结果
总结
N皇后的问题,用遍历不太好解决,于是可以使用递归来解决,但是这种递归与之前的有点不太一样,少了回溯部分,所以以后也需要注意递归的这种用法。
- 题目
逆波兰表达式是一种把运算符前置的算术表达式(其实一般教科书上称这种表达式为波兰表达式,而且这属于离散里面树的那部分内容,使用前序遍历法得到表达式),例如普通的表达式2 + 3的逆波兰表示法为+ 2 3。逆波兰表达式的优点是运算符之间不必有优先级关系,也不必用括号改变运算次序,例如 (2 + 3) * 4的逆波兰表示法为* + 2 3 4。本题求解逆波兰表达式的值,其中运算符包括+ - * /四个。
- 输入
输入为一行,其中运算符和运算数之间都用空格分隔,运算数是浮点数
- 输出
输出为一行,表达式的值。
- 样例输入
* + 11.0 12.0 + 24.0 35.0
- 样例输出
1357.000000
解题思路
如果按树来理解的话,就比较好理解为什么这道题目要用递归,在写离散作业的时候就明显发现了波兰表达式以及逆波兰表达式的递归关系,对应这种本来就是递归定义的题目,使用递归理所当然。
代码如下所示
#include
#include
double exp() {
//读入一个逆波兰表达式,并计算其值
char s[20];
scanf("%s",&s);
switch(s[0]) {
case '+': return exp()+exp();
case '-': return exp()-exp();
case '*': return exp()*exp();
case '/': return exp()/exp();
default: return atof(s);
// atof()是将字符串变为数值的函数
break;
}
}
int main(void)
{
printf("%lf",exp());
return 0;
}
运行结果
总结
这道题目有着明显的递归特征,所以使用递归是一个解决问题的好方法,基准条件就是当遍历到数值的时候。