深度优先搜索算法超级样例解析来了

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;
}

这是本蒟蒻二刷之前的题目,二刷确实快了,很多,虽然之前的忘了,差不多一天,完成了之前一周的量,大家都可以复习自己以前学过的,也是一种练习,重复理解

复杂的事情简单的做,简单的事情重复的做,重复做的事情用心的做,坚持下去,这样就没有做不成的事情。 大道至简,悟在天成。,感谢大佬观看!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值