一、DFS
DFS (Depth-First Search) 是一种用于图和树的搜索算法。在DFS中,我们从图或树的某个节点开始,沿着一条路径尽可能深地搜索,直到到达不能再深入的节点为止,然后回溯到前一节点,继续探索其他路径。DFS 通常使用递归或栈来实现。
回溯法是一种解决问题的算法范式,通常用于解决那些具有多个解的问题,例如组合问题、排列问题、子集问题等。其基本思想是逐步构建解决方案,并在构建的过程中不断进行选择,若发现选择导致了无法达到目标或者违反了问题的约束条件,则撤销该选择,回溯到之前的状态,尝试其他的选择,直至找到所有的解或者确定解不存在为止。
DFS 回溯法是在 DFS 过程中的一种特定技术。在使用 DFS 搜索过程中,当达到某一节点后,若发现无法继续前进(可能是没有相邻节点,或者达到了搜索的目标),就需要回溯到前一节点,尝试其他路径。这个回溯的过程就是回溯法。回溯法通过撤销当前的选择(通常是通过修改状态或者撤销对数据结构的操作),尝试其他的选择,以期找到满足条件的解。
DFS 是一种搜索算法,而 DFS 回溯法则是在 DFS 过程中的一种特定应用技巧,用于解决在搜索过程中需要回溯的情况。
二、回溯法模板
#include <iostream>
using namespace std;
//求1-n的全排列
const int N = 1e5 + 5;
int a[N];
bool vis[N];
int n;
void dfs(int dep) {
if (dep == n + 1) {
for (int i = 1; i <=n; ++i) {
cout << a[i] << " ";
}
cout << endl;
return;
}
for (int i = 1; i <= n; ++i) {
if (vis[i]) continue;
//修改状态
vis[i] = true;
a[dep] = i;
dfs(dep + 1); //下一层
//恢复现场
vis[i] = false;
}
}
int main (void) {
cin >> n;
dfs(1);
return 0;
}
2.DFS 回溯法通使用场合
当问题涉及到在多个选择中进行搜索,并且需要穷尽所有可能的解空间时,DFS 回溯法是一种常见且有效的解决方法。
- 组合问题:当需要在一组元素中找到所有可能的组合时,可以使用 DFS 回溯法。例如,组合求和问题,找出数组中所有可以组合成目标数的组合。
- 排列问题:当需要在一组元素中找到所有可能的排列时,也可以使用 DFS 回溯法。例如,全排列问题,给定一个数组,找出其所有可能的排列方式。
- 搜索问题:当需要在图或树中搜索特定路径、寻找目标节点或者满足特定条件的节点时,DFS 回溯法是一种常用的解决方法。例如,在迷宫中寻找一条从起点到终点的路径。
- 决策问题:当需要在多个选择中做出决策,并找到满足某些条件的最优解时,DFS 回溯法也是一种常用的方法。例如,在棋类游戏中的搜索树的构建与遍历,以找到最佳的下一步走法。
3.例题
1.小朋友的崇拜圈 lanqiao182
#include <bits/stdc++.h>
using namespace std;
//dfs
/*
用时间戳dfn,将走过的地方标记一个时间戳(第几步走到的
后续搜索中,每次开始更新最小时间戳
如果走到了已经走过的点就必须停下
根据时间戳的合法性更新最大值
*/
const int mx = 1e5 + 5;
int a[mx];
int ans = 0;
int mindfn = 0;
int idx = 0;
int dfn[mx];
int dfs(int dep) {
dfn[dep] = ++idx; //更新时间戳
if (dfn[a[dep]]) { //如果当前点时间戳有值
if (dfn[a[dep]] >= mindfn) return dfn[dep] - dfn[a[dep]] + 1;
return 0;
}
return dfs(a[dep]);
}
int main (void) {
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= n; ++i) { //没有走过的点
if (!dfn[i]) {
mindfn = idx + 1;
ans = max(ans, dfs(i));
}
}
cout << ans << endl;
return 0;
}
2.N皇后问题 lanqiao1508
#include <bits/stdc++.h>
using namespace std;
int n, ans = 0;
int vis[11][11];
//DFS
/* 每一行一定有且只有一个皇后,可以通过枚举每一行皇后的位置,
去搜索所有可能解
每放置一个皇后就将对应的米字型区域设置为禁区
回溯的时候将禁区解除
行数到了n+1说明找到了一个可行解
*/
void dfs(int dep) {
if (dep == n+1) {
ans++;
return;
}
for (int i = 1; i <= n; ++i) {
if (vis[dep][i]) continue; //如果当前这个已经是禁区了 就跳过
//不是禁区,就把它当作皇后,并且列斜全设为禁区
for (int _i = 1; _i <= n; ++_i) vis[_i][i]++; //列
for (int _i = dep, _j = i; _i >= 1 && _j >= 1; --_i, --_j) vis[_i][_j]++; //左上斜
for (int _i = dep, _j = i; _i <= n && _j <= n; ++_i, ++_j) vis[_i][_j]++; //右下斜
for (int _i = dep, _j = i; _i >= 1 && _j <= n; --_i, ++_j) vis[_i][_j]++; //右上斜
for (int _i = dep, _j = i; _i <= n && _j >= 1; ++_i, --_j) vis[_i][_j]++; //左下斜
dfs(dep + 1);
//恢复现场
for (int _i = 1; _i <= n; ++_i) vis[_i][i]--; //列
for (int _i = dep, _j = i; _i >= 1 && _j >= 1; --_i, --_j) vis[_i][_j]--; //左上斜
for (int _i = dep, _j = i; _i <= n && _j <= n; ++_i, ++_j) vis[_i][_j]--; //右下斜
for (int _i = dep, _j = i; _i >= 1 && _j <= n; --_i, ++_j) vis[_i][_j]--; //右上斜
for (int _i = dep, _j = i; _i <= n && _j >= 1; ++_i, --_j) vis[_i][_j]--; //左下斜
}
}
int main (void) {
cin >> n;
dfs(1);
cout << ans << endl;
return 0;
}
总结
总之,当问题涉及到在多个选择中进行搜索,并且需要穷尽所有可能的解空间时,DFS 回溯法是一种常见且有效的解决方法。唐怡佳继续加油!