当前位置: 首页 > web >正文

DFS(深度优先搜索)入门介绍

目录

基本概念

实现方法

算法思想

例一 全排列问题

例二 n皇后问题

例三 01背包问题


基本概念

深度优先搜索算法(Depth First Search,简称DFS):一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。

设想我们处在一个迷宫的入口,从起点开始行进,当遇到岔道口的时候我们总是会选择其中一条继续向前行进(比如总是选择最右边的岔道),当遇到死胡同时,回退到最近的岔路口选择另一条岔路。当碰到岔道口时,我们以“深度”作为关键词,不碰南墙不回头。这就是“深度优先搜索”。

从迷宫的例子还可以看出,深度优先搜索会走遍所有路径,每次碰到死胡同就代表一条完整路径的形成。也就是说,深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法

实现方法

由于我们不断的要探索-回溯,相当于入栈-出栈的操作,所以DFS对应的数据结构是栈。当然,由于用栈实现把不轻松,所以我们往往会选择递归来实现(使用递归时,系统会使用系统栈来存放递归中每一层的状态,因此递归实现DFS的本质还是栈)。

算法思想

回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。 回溯相当于递归中的“剪枝”,意思是对于已经知道错误的路径没有必要再探索下去。比如在一个有序数列1 2 3 4 5中寻找和为5的所有集合,当搜索进行到3的时候显然就没有必要继续往下走了,因此结束这段搜索。这是对搜索过程的一种优化。

来看例题:

例一 全排列问题

活动 - AcWing

给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

输入格式

共一行,包含一个整数 n。

输出格式

按字典序输出所有排列方案,每个方案占一行。

数据范围

1≤n≤7

输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
#include <iostream>
using namespace std;
const int N = 10;
int n;
int path[N]; //path为当前排列 
bool st[N];  //st来记录整数x是否已经在当前path中 
void DFS(int u){if(u == n){ //搜索边界,已经处理完排列的1~n位 for(int i = 0; i < n; i ++)printf("%d ", path[i]);puts("");return;}for(int i = 1; i <= n; i ++){ //枚举1~n,试图将x填入path[u] if(!st[i]){ //st[i] == 0代表i不在当前排列中 path[u] = i; //于是将i加入当前排列 st[i] = true; //记录:i已经在当前排列中 DFS(u + 1); //继续深入搜索,处理path[u+1] st[i] = false; //搜索完后还原现场 }}
}
int main(){scanf("%d", &n);DFS(0);return 0;
}

说个题外话,如果你熟悉STL,可以尝试如下解法:

#include <iostream>
#include <algorithm>
using namespace std;
int main(){int a[10];int n;cin >> n;for(int i = 1; i <= 10; i ++)a[i] = i;do{for(int i = 1; i <= n; i ++)cout << a[i] << " ";puts("");}while(next_permutation(a + 1, a + n + 1));return 0;
} 

第一种解法即是我们的深搜算法,从中可以得出DFS的基本模板:

void DFS(int step){if(满足边界条件){相应操作 }for(尝试每种可能){if(满足条件){标记继续下一步DFS(step + 1)恢复初始状态(回溯的时候需要) }} 
}

例二 n皇后问题

843. n-皇后问题 - AcWing题库

n−皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

现在给定整数 n,请你输出所有的满足条件的棋子摆法。

输入格式

共一行,包含整数 n。

输出格式

每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。

其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。

注意:行末不能有多余空格。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围

1≤n≤9

输入样例:

4

输出样例:

.Q..
...Q
Q...
..Q...Q.
Q...
...Q
.Q..

 对于这个问题,如果采用组合数的方式来暴力枚举(从n^2个位置中选择n个位置),当n = 8时就是54 502 232 次枚举,n较大时显然是无法承受的。

但换个思路,我们可以按行展开搜索,因为每行只允许有一个皇后,对于每行都找出一个合法的位置,直到组成一个答案

代码如下:

#include <iostream>
using namespace std;
const int N = 21;
int n;
char g[N][N]; //模拟出一个棋盘 
bool col[N], dg[N], udg[N];
//用于判断该列、对角线、反对角线有无皇后存在 
void DFS(int u){ //处理第u行皇后的放法 if(u == n){ //到达搜索边界,返回 for(int i = 0; i < n; i ++)puts(g[i]);puts("");return;} for(int i = 0; i < n; i ++){ //遍历第u行每个格子 if(!col[i] && !dg[u + i] && !udg[n - u + i]){ //该位置合法 g[u][i] = 'Q'; //将该位置标记 col[i] = dg[u + i] = udg[n - u + i] = true; //将该位置对应列、对角线标记 DFS(u + 1); //搜索下一行 col[i] = dg[u + i] = udg[n- u + i] = false; //恢复初始状态 g[u][i] = '.'; //恢复初始状态 }}
}
int main(){cin >> n;for(int i = 0; i < n; i ++) //初始化 for(int j = 0; j < n; j ++)g[i][j] = '.';DFS(0);return 0;
}

当然,也可以按每个格子“放”与“不放”皇后来进行搜索。请读者自行思考,这里直接给出代码:

#include <iostream>
using namespace std;
const int N = 20;
int n;
char g[N][N];
bool row[N], col[N], dg[N], udg[N];
void DFS(int x, int y, int s){if (y == n) y = 0, x ++;if (x == n){if(s == n){for(int i = 0; i < n; i ++)puts(g[i]);puts("");}return;}//不放皇后 DFS(x, y+1, s);//放皇后 if(!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n]){g[x][y] = 'Q';row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;DFS(x, y + 1, s + 1);row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;g[x][y] = '.';}
}
int main(){cin>>n;for(int i = 0; i < n; i ++){for(int j = 0; j < n; j ++){g[i][j] = '.';}}DFS(0, 0, 0);return 0;
}

对于上面这种“每个格子选或不选两种选择”,有点像01背包问题。下面我们将通过一道背包问题来展示“剪枝”的思想。

例三 01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

 (注意:本题只用作算法展示,在数据量过大的情况下请使用动态规划求解)

解一

看到问题我们很容易想到深搜的写法,本题的岔路就是——每件物品装与不装。

#include <iostream>
using namespace std;
const int maxn = 1010;
int N, V, v[maxn], w[maxn];
int maxValue = 0; //所求最大价值 
void DFS(int u, int sumV, int sumW){ //sumV是当前装入物品体积,sumC是当前物品价值 if(u == N){ //到达递归边界 if(sumV <= V && sumW > maxValue)maxValue = sumW;return;}DFS(u + 1, sumV, sumW); //不装 DFS(u + 1, sumV + v[u], sumW + w[u]); //装 
}
int main(){cin >> N >> V;for(int i = 0; i < N; i ++)cin >> v[i] >> w[i];DFS(0, 0, 0);cout << maxValue;return 0;
}

这样的代码交上去会很轻松的TLE(超过时间限制),因为没有考虑到在没有搜索完N的情况下还有一种可以返回的可能——背包里的物品已经到达背包最大容量。在这种情况下,后面的物品我们就无需再考虑,就像放弃一些已经知道是枯萎的树枝分岔一样,称为剪枝。

附上剪枝后的代码:

#include <iostream>
using namespace std;
const int maxn = 1010;
int N, V, v[maxn], w[maxn];
int maxValue = 0;
void DFS(int u, int sumV, int sumW){if(u == N){if(sumV <= V && sumW > maxValue)maxValue = sumW;return;}DFS(u + 1, sumV, sumW);if(sumV + v[u] <= V){ //剪枝操作 if(sumW + w[u] > maxValue)maxValue = sumW + w[u];DFS(u + 1, sumV + v[u], sumW + w[u]);}
}
int main(){cin >> N >> V;for(int i = 0; i < N; i ++)cin >> v[i] >> w[i];DFS(0, 0, 0);cout << maxValue;return 0;
}

http://www.xdnf.cn/news/11661.html

相关文章:

  • shuffle
  • JSP详细基础教学
  • Python网络爬虫之Xpath详解
  • Python的包安装工具——pip命令大全
  • netstat详解
  • Linux-文件查找find命令
  • Echarts热力图配置项,一篇文章告诉你。
  • 神仙级Python入门教程(非常详细),从零基础入门到精通,看这篇就够了
  • 一文详细说明spring cloud和Spring Cloud Alibaba的各自组件以及联系和区别
  • Validate表单验证插件之常用参数介绍
  • 网关 GateWay 的使用详解、路由、过滤器、跨域配置
  • 神经网络(NN)网络构建及模型算法介绍
  • 从计网的角度讲明白什么是网关
  • Apollo入门使用手册
  • Java资源大全(更新中)
  • Keil(MDK)STM32和51版本详细安装
  • GPU 性能测试软件:GPU-Z,2023 年 9 月 12 日更新
  • 【19】linux进阶——后台运行()和nohup命令
  • ESFP型人格的特征,ESFP型人格的优势和劣势分析
  • react Native 环境安装配置——图解版一目了然
  • Netty基础入门和基本使用
  • TortoiseSVN使用教程[多图超详细]
  • Numpy的用法详细总结
  • 百度程序员删库跑路被逮捕!
  • 一文看懂Mesh组网
  • Android Gradle开发与应用 (一) : Gradle基础
  • iview--使用总结
  • 抖音越狱版本App下载
  • Verilog基础语法(13)之case语句
  • Element-UI介绍:主题定制、自定义组件和插件扩展