目录
例子源于慕课课程:程序设计与算法二
递归(recursion)思想用于
- 代替多重循环
- 有递归式子的问题
- 可以将问题分解成多个子问题求解
例 汉诺塔
问题描述略
分析
- 分解成子问题,原位置src;目的地dest;中转位置mid
- 1. 如果是只有一个盘子,直接从src移动到dest
- 2. 如果多于一个盘子
- 先将n-1个以dest为中转移到mid
- 再将剩余的一个src移到dest
- 最后将mid 上n-1个以src为中转移到dest
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<set>
using namespace std;
void Hanoi(int n, char src, char mid, char dest);
void Hanoi(int n, char src, char mid, char dest)
{
if (n == 1) {
cout << src << "->" << dest << endl;//如果只有一个,直接移到dest
return;//递归中止
}
//否则分解任务
Hanoi(n - 1, src, dest, mid);//如果不止一个,先将n-1个以dest为中转移到mid
cout << src << "->" << dest << endl;//再将剩余的src移到dest
Hanoi(n - 1, mid, src, dest);//最后将n-1个mid的,以src为中转移到dest
}
int main() {
char src = 'A';
char mid = 'B';
char dest = 'C';
Hanoi(2, src, mid, dest);
return 0;
}
练习 面试题 08.06. 汉诺塔问题
参考代码
class Solution {
public:
void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
int n = A.size();
move(n, A, B, C);
}
void move(int n, vector<int>& A, vector<int>& B, vector<int>& C) {
if (n == 1) {
C.push_back(A.back());
A.pop_back();
return;
}
else {
move(n - 1, A, C, B);
C.push_back(A.back());
A.pop_back();
move(n - 1, B, A, C);
}
}
};
例 N皇后
问题描述略,先了解一下八皇后,参考文章解释很清晰: 八皇后问题(递归回溯算法详解+C代码)_苍之羽-优快云博客_八皇后递归
八皇后代码
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<set>
using namespace std;
int notDanger(int row, int col); //判断是否冲突
void Print(); //打印结果 “Q”表示皇后,“.”表示没有摆放皇后
void EightQueen(int row); //row-1行已经摆好,从第row行开始摆
int cnt = 0; //记录解法
int chess[8][8] = { 0 }; //0为没摆,1为摆放,注意我们只记录摆或没摆,而下一个会不会冲突通过列和对角线来判断,而不记录
int notDanger(int row, int col) //判断该位置对应的列、左上和右上对角线是否已经摆放(因为该行左下右下不会摆放),如果摆放,则冲突,返回 0
{
int i, j;
//判断列
for (i = 0; i < 8; i++) {
if (chess[i][col] == 1)
return 0;
}
//判断左上对角线
for (i = row, j = col; i >= 0 && j >= 0; i--, j--) {
if(chess[i][j]==1)
return 0;
}
//判断右上对角线
for (i = row, j = col; i >= 0 && j < 8; i--, j++) {
if (chess[i][j] == 1)
return 0;
}
return 1;
}
void Print() //打印结果
{
int row, col;
cout << "No: " << cnt+1<<endl;
for (row = 0; row < 8; row++) {
for (col = 0; col < 8; col++) {
if (chess[row][col] == 1)
{
cout << "Q";
}
else cout << ".";
}
cout << endl;
}
cout << endl;
}
void EightQueen(int row) //row-1行已经摆好,从第row行开始摆
{
if (row ==8 ) {
Print();
cnt++;
return ;
}
for (int l = 0; l < 8; l++) {//从第l列开始尝试
if (notDanger(row, l)) {//如果没有危险,摆放,进行下一行
chess[row][l] = 1;
EightQueen(row + 1);
chess[row][l] = 0; //如果row+1不能摆,说明row行位置需要调整,清零
}
}
}
int main()
{
EightQueen(0);//从第0行开始摆放
printf("总共有 %d 种解决方法!\n\n", cnt);
return 0;
}
根据八皇后改写得N皇后代码
改动部分
- main里读入皇后个数n
- 二维数组大小更改为 chess[MAX][MAX](更好的方法为用vector)
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<set>
#define MAX 1000
using namespace std;
int notDanger(int row, int col); //判断是否冲突
void Print(); //打印结果 “Q”表示皇后,“.”表示没有摆放皇后
void EightQueen(int row); //row-1行已经摆好,从第row行开始摆
int n;
int cnt = 0; //记录解法
int chess[MAX][MAX];
int notDanger(int row, int col) //判断该位置对应的列、左上和右上对角线是否已经摆放(因为该行左下右下不会摆放),如果摆放,则冲突,返回 0
{
int i, j;
//判断列
for (i = 0; i < n; i++) {
if (chess[i][col] == 1)
return 0;
}
//判断左上对角线
for (i = row, j = col; i >= 0 && j >= 0; i--, j--) {
if(chess[i][j]==1)
return 0;
}
//判断右上对角线
for (i = row, j = col; i >= 0 && j < n; i--, j++) {
if (chess[i][j] == 1)
return 0;
}
return 1;
}
void Print() //打印结果
{
int row, col;
cout << "No: " << cnt+1<<endl;
for (row = 0; row < n; row++) {
for (col = 0; col < n; col++) {
if (chess[row][col] == 1)
{
cout << "Q";
}
else cout << ".";
}
cout << endl;
}
cout << endl;
}
void EightQueen(int row) //row-1行已经摆好,从第row行开始摆
{
if (row ==n ) {
Print();
cnt++;
return ;
}
for (int l = 0; l < n; l++) {//从第l列开始尝试
if (notDanger(row, l)) {//如果没有危险,摆放,进行下一行
chess[row][l] = 1;
EightQueen(row + 1);
chess[row][l] = 0; //如果row+1不能摆,说明row行位置需要调整,清零
}
}
}
int main()
{
cout << "请输入皇后个数" << endl;
cin >> n;
EightQueen(0);//从第0行开始摆放
printf("总共有 %d 种解决方法!\n\n", cnt);
return 0;
}
例 逆波兰数
输入:* + 11.0 12.0 + 24.0 35.0
输出:1357.000000
提示:上式为 (11.0+12.0)*(24.0+35.0);注意输入空格不要少!!
代码
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);
//atof功能:解析字符串str,将其内容解释为浮点数,并将其值返回为double。
//无法执行有效的转换,该函数将返回 0.0
break;
}
}
int main() {
printf("%1f\n", exp());
return 0;
}
注:atof的功能 atof()函数_没有西瓜汁-优快云博客_atof()
运行结果
例 表达式求值
解读下图:
1.表达式:可以由一项组成,也可以由多个项的加减运算组成
2.项:可以由一个因子组成,也可以由多个因子的乘除运算组成
3.因子:可以由(表达式)组成,也可以是一个整数(终止条件)
代码
int expression_value(); //输入表达式,计算其值
int factor_value(); //输入因子,计算其值
int term_value(); //输入项,计算其值
//表达式由项组成
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 == '+')
return result += value;//原来的结果加上下一项表达式
else
return result -= value;
}
else more = false;
}
return result;
}
//项由因子组成
int term_value() {
int result= factor_value();
while (1) {
char op = cin.peek();
if (op == '*' || op == '/') {
cin.get();
int value= factor_value();
if (op == '*')
return result *= value;
else return result /= value;
}
else break;
}
return result;
}
//因子由表达式|整数组成
int factor_value() {
int result = 0;
char c = cin.peek();
if (c == '(') {
cin.get();
result = expression_value();
cin.get();
}
else {
while (isdigit(c)) {
result = 10 * result + c - '0';//数字字符c减去0的ascii码就是0-9的数
cin.get();
c=cin.peek();
}
}
return result;
}
int main() {
cout << expression_value() << endl;
return 0;
}
例 爬楼梯
分析
- 因为N可以很大,我们将问题分解成子规模来求解
- 不管怎么走,总要迈出第一步,第一步可以走一级,也可以走两级,那么总的解法就是 第一次走一级时,后n-1步的解法+第一次走两级时,后n-2步的解法
- 即: f(n)=f(n-1) + f(n-2)
- 接下来是递归边界条件,判断在n满足什么条件时不需要递归,可知以下三种都是可以的
n<0 0 n=0 1(即不走) | n=0 1 n=1 1 | n=1 1 n=2 2 |
代码
#include<iostream>
using namespace std;
int stairs(int n) {
if (n < 0)
return 0;
if (n == 1)
return 1;
else return stairs(n - 1) + stairs(n - 2);
}
int main() {
int n;//台阶数
while (cin >> n) {
cout << stairs(n) << endl;
}
}
例 放苹果
分析
- 记f(i,k)为把i个苹果放进k个盘子里,则
- 1. m<n,f(m,m)
- 2. m>=n,f(m,m) 苹果数大于等于盘子数,总方法=有空盘子时的放法+没空盘子的放法,即f(m,n)=f(m,n-1)+f(m-n,n)
- 有空盘子时的放法:有空盘子时至少有一个是空的,这时剩余n-1个盘子,故为f(m,n-1)
- 没空盘子的放法:没空的时,每个盘子至少放一个,剩余m-n个苹果,故为f(m-n,n)
- 边界条件:剩余0个苹果时,只有一种放法,即不放;剩余0个盘子时,0种放法
代码
#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 N;
cin >> N;
while (N--) {
int m, n;
cin >> m >> n;
cout << f(m, n)<<endl;
}
}
输入输出:
练习 面试题 08.05. 递归乘法
分析
- A*B可以解释为B个A
- 用一个变量保存结果,逐个相加就行
- 如果没有要求用递归,两个for循环可实现
class Solution {
public:
int multiply(int A, int B) {
int tmp = A;
return re(tmp,A, B - 1);
}
int re(int tmp,int A, int B) {
if (B == 0)
return tmp;
tmp += A;
return re(tmp,A, B - 1);
}
};