介绍
递归
递归是算法的一种基本思想,它是在程序中不断反复调用自身
来达到求解问题的方法,这需要问题具有一个性质:待求解问题可以分解为相同的若干个子问题,这样它才能调用自身。
递归例题
题目描述
输入一个整数n
,请你编写一个函数,int fact(int n)
,计算并输出n
的阶乘。
输入格式
共一行,包含一个整数n
。
输出格式
共一行,包含一个整数表示n
的阶乘的值。
数据范围
1 ≤ n ≤ 10 1≤n≤10 1≤n≤10
输入样例:
3
输出样例:
6
样例解释
3 ! = 3 ∗ ( 2 ! ) = 3 ∗ 2 ∗ ( 1 ! ) = 3 ∗ 2 ∗ 1 = 6 3! = 3 * (2!) = 3 * 2 * (1!) = 3 * 2 * 1 = 6 3!=3∗(2!)=3∗2∗(1!)=3∗2∗1=6
解题思路
从样例解释中很容易可以看出,求
3
!
3!
3!可以分解为求
3
∗
(
2
!
)
3*(2!)
3∗(2!),又可以分解为求
3
∗
2
∗
(
1
!
)
3*2*(1!)
3∗2∗(1!),最后返回答案
6
6
6。这正好符合递归的思想,假设要求n
的阶乘,那么求相当于要求
n
∗
(
n
−
1
)
!
n * (n - 1)!
n∗(n−1)!,而当
n
=
=
1
n == 1
n==1时,则直接返回
1
1
1,由此我们可以写出代码
代码
#include <iostream>
using namespace std;
int fact(int n){
if(n == 1) return 1;
return n * fact(n - 1);
}
int main()
{
int n;
cin >> n;
cout << fact(n);
return 0;
}
DFS
DFS(Depth First Search),又叫深度优先搜索,深度优先遍历,顾名思义,就是按照深度优先的顺序对“问题状态空间”进行搜索的算法,这一操作我们一般用递归来实现。
搜索树
每个递归问题都会产生一棵搜索树,如果我们刚开始对DFS不适应,可以多画画搜索树让自己加深对DFS的理解。
DFS例题
题目描述
给定一个整数n
,将数字
1
∼
n
1∼n
1∼n排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数n
。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1 ≤ n ≤ 9 1≤n≤9 1≤n≤9
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
样例解释+解题思路(回溯法)
n
=
3
n = 3
n=3,需要按字典序输出
1
,
2
,
3
1,2,3
1,2,3三个数的全排列。
此时我们可以先画出搜索树。
我们会发现,
- 每次需要从1到3搜索能用的数字,然后将其加入答案再往下递归。
- 我们需要传入一个能表示当前深度的参数
u
,当u == n
时结束一轮递归 - 我们会发现一个数字需要重复使用,因此我们需要一个用来标记数字
i
是否用过的数组st
- 我们会发现从小到大枚举的情况下得到的全排列是天然按字典序排序的,因此不用额外考虑这个
- 我们会发现一个数字用完后在之后的全排列过程中中还会用到,因此我们还需要
恢复现场
,即将标记为用过的当前数字取消标记。 - 每一轮排列结束后我们都需要输出当前排列,因此我们还需要一个记录当前路径的数组
path
代码
#include<iostream>
using namespace std;
const int N = 10;
int path[N]; // 记录全排列路径
int n;
bool st[N]; // 标记当前数字是否用过
void dfs(int u){ // 当前遍历到第u + 1位(u从0开始)
if(u == n){ // 如果u已经从0 ~ n - 1这n个位置都遍历过
for(int i = 0; i < n; i ++) cout << path[i] << ' '; // 输出当前排列
cout << endl;
return;
}
for(int i = 1; i <= n; i ++) // 从小到大枚举
if(!st[i]){ // 如果当前数字没有用过
st[i] = true; // 当前数字标记为用过
path[u] = i; // 第u位数字填入数字i
dfs(u + 1); // dfs下一位
st[i] = false; // 恢复现场
}
}
int main(){
cin >> n;
dfs(0);
return 0;
}
注意:
在恢复现场时本来应该将path[u] = 0
,但是在之后path[u]
会被覆盖,因此不用对它进行恢复现场。