1.什么是dfs,以及算法的基础是什么?
dfs:深度优先搜索算法,是一种用于遍历或搜索树或图的算法.沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。属于盲目搜索。简单来说就是一条路走到黑,直到没路了,或者找到结果才返回。
深度优先搜索算法的基础:递归与回溯
递归与回溯是相辅相成的,有递归就会有回溯,递归函数的下面部分就是回溯的过程以及回溯的逻辑
回溯是一种纯暴力搜索。
2.使用原因
虽然深度优先搜索算法的效率很低,但是有些问题只能通过dfs来解决,有些时候多重循环就找不出来结果
能够解决的问题有:
1.组合问题
2.切割问题
3.子集问题
4.排列问题
5.棋盘问题
3.如何去理解深度优先搜索的回溯算法
类似于递归,打个比方斐波拉契数列,递归,本质还是递归
回溯算法是一个很抽象的东西,但是所有的回溯算法都可以抽象成一个树状结构,可以将其抽象成一个n叉树问题。树的深度取决于要搜索问题的层数,树的宽度取决于每个节点处理集合的大小。
回溯法的模版:
void 函数名(参数)//回溯算法的参数一般比较多,要根据具体情况进行分析
{
if(终止条件)
{
收集最后节点结果
return ;
}
//单层搜索的逻辑:
for(集合的元素)//遍历的是集合里的每一个元素,也可能是集合节点的子节点个数
{
处理节点
递归函数
回溯操作(撤销处理节点的操作)
}
return ;
}
P1605 迷宫 - 洛谷
#include<bits/stdc++.h> // 包含常用头文件(非标准,但竞赛常用)[1,2](@ref)
using namespace std; // 使用标准命名空间[8,9](@ref)
#define ma 100 // 定义最大迷宫尺寸为100x100
int vis[ma][ma]; // 标记数组:0=可访问,1=已访问/障碍物
int Map[ma][ma]; // 地图数组(代码中未实际使用)
int dx[] = { 1,-1,0,0 }; // 方向数组:下、上、右、左的x增量
int dy[] = { 0,0,1,-1 }; // 方向数组:下、上、右、左的y增量
int n, m, t, ans; // n=行数, m=列数, t=障碍数, ans=路径总数
int si, sj, di, dj; // 起点(si,sj) 终点(di,dj)
int qx, qy; // 临时存储障碍物坐标
// 深度优先搜索函数
void dfs(int si, int sj) {
// 到达终点:路径数+1并返回
if (si == di && sj == dj) {
ans++;
return;
}
// 越界检查(此处逻辑有误,应为 xx>n || yy>m)
if (si > n || sj > m || si < 1 || sj < 1) return;
// 遍历四个方向
for (int i = 0; i < 4; i++) {
int xx = si + dx[i];
int yy = sj + dy[i];
// 越界检查(需修正为 xx>n || yy>m)
if (xx<1 || yy<1 || xx>n && yy>m) continue;
// 如果该位置可访问
if (vis[xx][yy] == 0) {
vis[xx][yy] = 1; // 标记为已访问
dfs(xx, yy); // 递归搜索
vis[xx][yy] = 0; // 回溯(取消标记)
}
}
}
int main() {
// 输入迷宫尺寸和障碍数
cin >> n >> m >> t;
// 输入起点终点坐标
cin >> si >> sj >> di >> dj;
vis[si][sj] = 1; // 标记起点为已访问
// 输入并标记障碍物
while (t--) {
cin >> qx >> qy;
vis[qx][qy] = 1;
}
dfs(si, sj); // 开始深度优先搜索
printf("%d", ans);
return 0;
}
P1162 填涂颜色 - 洛谷
#include<bits/stdc++.h> // 包含常用头文件(非标准,但竞赛常用)
using namespace std; // 使用标准命名空间
int Map[35][35]; // 定义迷宫数组,最大尺寸为35x35
int n; // 迷宫的实际尺寸为n x n
int dx[] = {1, -1, 0, 0};// 方向数组:下、上、右、左的x增量
int dy[] = {0, 0, 1, -1};// 方向数组:下、上、右、左的y增量
// 深度优先搜索函数
void dfs(int si, int sj) {
// 越界检查:如果坐标超出迷宫范围,直接返回
if (si < 0 || sj < 0 || si > n + 1 || sj > n + 1) return;
// 如果当前位置是墙(值为1),直接返回
if (Map[si][sj] == 1) return;
// 遍历四个方向
for (int i = 0; i <= 3; i++) {
int xx = si + dx[i]; // 计算新坐标的x值
int yy = sj + dy[i]; // 计算新坐标的y值
// 如果新坐标是空地(值为0)
if (Map[xx][yy] == 0 /*&& Map[xx][yy] != 3*/) {
Map[xx][yy] = 3; // 标记为已访问
dfs(xx, yy); // 递归搜索
}
}
}
int main() {
// 输入迷宫尺寸
cin >> n;
// 输入迷宫数据
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> Map[i][j];
// 从(0,0)开始深度优先搜索,标记所有可达的空地
dfs(0, 0);
// 输出处理后的迷宫
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (Map[i][j] == 3) cout << "0 "; // 被标记的区域输出0
else if (Map[i][j] == 1) cout << "1 "; // 墙输出1
else cout << "2 "; // 未被标记的区域输出2
}
cout << endl; // 换行
}
return 0;
}
P1596 [USACO10OCT] Lake Counting S - 洛谷
#include<bits/stdc++.h>
char Map[101][102];
int vis[101][102];
using namespace std;
int n, m,ans;
int dx[] = { 1,1,1,0,0,-1 ,- 1,-1 };
int dy[] = { 0,1,-1,1,-1,0,1,-1 };
void dfs(int si, int sj) {
if (si < 0 || sj < 0 || si >= n || sj >= m)return;
if (Map[si][sj] == '.')return;
Map[si][sj] = '.';
for (int i = 0; i < 8; i++) {
int x = si + dx[i];
int y = sj + dy[i];
if (si < 0 || sj < 0 || si >= n || sj >= m)continue;
if (x>=0&&y>=0&&x<n&&y<m&&Map[x][y] == 'W') {
Map[si][sj] = '.';
dfs(x, y);
}
}
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) {
cin >> Map[i];
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (Map[i][j] == 'W') {
dfs(i, j);
ans++;
}
}
}
cout << ans << endl;
return 0;
}
P1219 [USACO1.5] 八皇后 Checker Challenge - 洛谷
主要是要注意从左上到右下,还有从左下到右上
#include<bits/stdc++.h>
using namespace std;
int a[100][100];//放皇后的位置
int vis1[100];//标记列
int vis2[100];//左上到右下
int vis3[100];//左下到右上
int n,ans;
void dfs(int i) {
if (i == n) {
ans++;
if (ans <= 3) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (a[i][j] == 1)
cout << j+1 << " ";
}
}
}
cout << ans << endl;
}
for (int j = 0; j < n; j++) {
if (vis1[j] == 0 && vis2[i - j + n] == 0 && vis3[i + j] == 0) {
a[i][j] = 1;
vis2[i - j + n] = 1;
vis1[j] = 1;
vis3[i + j] = 1;
dfs(i + 1);
a[i][j] = 0;
vis2[i - j + n] = 0;
vis1[j] = 0;
vis3[i + j] = 0;
}
}
}
int main() {
cin >> n;
dfs(0);
return 0;
}
巧妙地对角线法
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
int step = 0;
int n;
int v[15];
int ans = 0;
int check(int x, int y) {
for (int i = 1; i < x; i++) { // 只需检查已放置皇后的列和两个对角线
if (v[i] == y || abs(i - x) == abs(v[i] - y)) // 描述两点在对角线可以使用斜率的绝对值等于1
return 0;
}
return 1;
}
void dfs(int x) {
if (x > n ) {
if (ans < 3) {
for (int i = 1; i <= n; i++) {
cout << v[i] << " ";
}
cout << endl;
}
ans++;
return;
}
for (int k = 1; k <= n; k++) {
if (check(x, k)) {
v[x] = k;
dfs(x + 1);
v[x] = 0;
}
}
}
int main() {
cin >> n;
memset(v, 0, sizeof(v));
dfs(1);
cout << ans;
return 0;
}
P2392 kkksc03考前临时抱佛脚 - 洛谷
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define N 25
int s[N], b[N];
int sum;
int min(int a, int b) {
return a > b ? b : a;
}
int max(int a, int b) {
return a > b ? a : b;
}
void dfs(int i, int l, int r, int q) {//如果到了q就停止q==s[i]即数组最大值
if (i > q) {
sum = min(sum, max(l, r));
return;
}
dfs(i + 1, l + b[i], r, q);
dfs(i + 1, l, r + b[i], q);
}
int main() {
for (int i = 1; i <= 4; i++)scanf("%d", &s[i]);
int k = 0;
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= s[i]; j++) {
scanf("%d", &b[j]);
}
sum = 0x7fffffff;
dfs(1, 0, 0, s[i]);
k += sum;
}
printf("%d", k);
return 0;
}
P2392 kkksc03考前临时抱佛脚 - 洛谷
二刷才学会的,最大值最小,min(max),就是用dfs两边相加取最大值,模拟01背包,赞
#include<bits/stdc++.h>
using namespace std;
int ans,sum;
int s[100];
int q[1000];
void dfs(int i, int b, int c, int d) {
if (i > d) {
sum = min(sum,max(b,c));
return;
}
dfs(i + 1, b + q[i], c, d);
dfs(i + 1, b, c + q[i], d);
}
int main() {
for (int i = 1; i <= 4; i++)cin >> s[i];
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= s[i]; j++) {
cin >> q[j];
}
sum = 1000000;
dfs(1, 0, 0, s[i]);
ans += sum;
}
cout << ans;
return 0;
}
P2036 [COCI 2008/2009 #2] PERKET - 洛谷
此题与上题相似
#include<bits/stdc++.h>
using namespace std;
int n;
int a[20], b[20], k = INT_MAX;
void dfs(int i, int q, int p) {
if (i > n) {
if (q == 1 && p == 0)return;
k = min(abs(q - p), k);
return;
}
dfs(i + 1, q * a[i], p + b[i]);
dfs(i + 1, q, p);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];\
}
dfs(1, 1, 0);
cout << k;
return 0;
}
问题 - 1241 --- Problem - 1241
#include <iostream> // 输入输出流操作
#include <string> // 字符串处理
using namespace std;
int m, n, ans; // m:行数, n:列数, ans:连通块计数
char a[1000][1000]; // 存储二维网格数据
// DFS遍历标记连通区域
void dfs(int x, int y) {
a[x][y] = '*'; // 标记当前点为已访问
// 遍历8邻域
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
int u = x + i;
int v = y + j;
// 边界检查
if (u < 0 || v < 0 || u >= m || v >= n) continue;
// 发现相邻未访问点则递归处理
if (a[u][v] == '@') {
dfs(u, v);
}
}
}
}
int main() {
// 循环处理多组测试数据
while (cin >> m >> n) {
ans = 0;
if (m == 0) break; // 终止条件
// 读取网格数据
for (int i = 0; i < m; i++) cin >> a[i];
// 遍历所有网格点
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (a[i][j] == '@') {
dfs(i, j); // 发现新连通块
ans++; // 计数器增加
}
}
}
cout << ans << endl;
}
return 0;
}
这是本蒟蒻二刷之前的题目,二刷确实快了,很多,虽然之前的忘了,差不多一天,完成了之前一周的量,大家都可以复习自己以前学过的,也是一种练习,重复理解
复杂的事情简单的做,简单的事情重复的做,重复做的事情用心的做,坚持下去,这样就没有做不成的事情。 大道至简,悟在天成。,感谢大佬观看!!!