学习视频链接:程序设计与算法(二)算法基础(北京大学)
第二周:递归
何为递归:在调用函数中继续调用该函数。
递归一般解决三个问题:
一、替代多重循环(不过有时候也用多重循环替代递归),特别是在多重循环的重数未知的时候
二、解决本身就是递归形式的问题
三、将一个大问题分解为小问题进行求解
递归需要注意的就是,一定要找对“边界条件”,否则会造成栈溢出(数组范围过大或者递归无结束)
例题1:汉诺塔问题(分解为小问题)
题目意思:
汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘
分析:
我们设三个柱子分别为A,B,C
要把n个圆盘从A移动到C,其中B可以用作中转
那么我们把问题分解为:
先把n-1个圆盘从A移动到B,此时可以利用C为中转
再把A上剩下的1个圆盘从A移动到C
最后再把n-1个圆盘从B移动到C,此时可以利用A为中转
而边界条件就是:当n==1时,直接从要移动的左边直接到要移动到的位置
所以主要是一个函数
void Hanoi(int n, char l, char m, char r)
意思就是n个圆盘从l 移动到r,此时m为中转
代码:
#include<cstdio>
#include<iostream>
using namespace std;
// 函数的作用,把n从 l 弄到 r
void Hanoi(int n,char l,char m,char r)
{
if(n==1)
{
printf("%c-->%c\n",l,r);
return;
}
Hanoi(n-1,l,r,m);
printf("%c-->%c\n",l,r);
Hanoi(n-1,m,l,r);
return;
}
int main()
{
int n;
cin >> n;
Hanoi(n,'A','B','C');
return 0;
}
问题2:N皇后问题(替代多重循环)
题目:
在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上。
你的任务是,对于给定的N,求出有多少种合法的放置方法。
分析:
我们要摆放皇后位置,由于会攻击同一行,所以一行一定而且只能放一个
那我们在摆放当前行的皇后位置的时候,我们就认为前几行已经摆放好了(当前解决的时候,认为前面已经是解决的)
所以主要就是一个函数:
void NQueen(int k)
k表示第k行,(从0开始的),那么边界条件就是:当k == N的时候,说明都已经摆放完了,此时顺便输出摆放的位置
那么在摆放当前行,皇后的位置,有0到N-1总共N个可能,那我们就全部尝试,而且要注意满足条件,皇后不能相互攻击才能放。
不能互相攻击的条件:不会同一列,不会斜线(即如果,列差的绝对值=行差的绝对值,这就会斜线)
如果摆放好当前行,就继续递归到下一行的摆放
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int N;
int pos[100];
int sum = 0;
// 函数作用;认为前 k-1行已经摆放好了,现在准备摆放第 k行
void NQueen(int k)
{
// 边界条件
if(k == N)
{
//sum++;
for(int i = 0;i < N;i++)
{
printf("%d",pos[i]+1);
if(i < N-1)
printf(" ");
}
printf("\n");
return ; // 只要是函数,即使是void,也最好return结束
}
for(int i = 0;i < N;i++)
{
int j;
// 判断能不能放,用前面的k-1行判断
for(j = 0;j < k;j++)
{
if(pos[j]==i || abs(k-j)==abs(pos[j]-i))
break;
}
// 如果j == k说明,没有break,也就是条件都不会冲突
if(j == k)
{
pos[k] = i;
NQueen(k+1);
}
}
}
int main()
{
cin >> N;
// 从第0行开始放
NQueen(0);
//cout << sum << endl;
return 0;
}
// 最后计算了1到10皇后的数量
//1 0 0 2 10 4 40 92 352 724
问题3:逆波兰表达式(本身就是一个递归形式的问题)
题目链接:
https://vjudge.net/problem/OpenJ_Bailian-2694
分析:
逆波兰表达式的完整定义:
1)一个数也是一个逆波兰表达式,值为该数
2)“运算符 逆波兰表达式 逆波兰表达式”,是一个逆波兰表达式子,值为两个表达式进行运算符计算的结果
所以这就是一个典型的递归形式问题,即定义之中有定义。
那么边界条件其实就是:当表达式是一个数的时候
那么我们主要用一个函数来计算
double exp()
在函数里面进行cin输入处理
当输入一个东西的时候,我们先要判断,如果是运算符,那么就是第2)中情况,需要继续递归两个表达式的加减乘除
如果是第1)种情况,那就是将字符串转为浮点型(atof 函数),进行返回值即可
其实代码跟简单,主要要理解思路;
AC代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;
double exp()
{
char s[20];
cin >> s;
switch(s[0])
{
case '+':
return exp()+exp();
case '-':
return exp()-exp();
case '*':
return exp()*exp();
case '/':
return exp()/exp();
default:
return atof(s);
}
}
int main()
{
printf("%lf\n",exp());
return 0;
}