【C++算法】dfs深度优先搜索(下) ——【全面深度剖析+经典例题展示】

💃🏼 本人简介:男
👶🏼 年龄:18
🤞 作者:那就叫我亮亮叭
📕 专栏:关于C/C++那点破事

0.0 写在前面

书接上文,上次我们讲到了深搜的思想与经典的迷宫问题,这次我们不再细节性讲解其思想,重在针通过几道例题来实践掌握深搜的用法,欢迎大佬们来指点一二,我们一起加油!!还没了解的可以直接点进行学习哦👉👉👉【C++算法】dfs深度优先搜索(上) ——【全面深度剖析+经典例题展示】

1. 中国象棋

1.1 题干信息

① 背景说明概述

中国象棋博大精深,其中的规则最为复杂,也是最难操控的一颗棋子。我们都知道象棋中"日",比如在(2, 4)位置的一个马【如下图所示】,跳一步能到达的位置有(0,3),(0,5),(1,2),(1,6),(3,2),(3,6),(4,3),(4,5)。
中国象棋

  • 注意:上图中,竖直向下的是x,水平向右的是y!!!

② 问题描述

蒜头君正在和花椰妹下棋,蒜头君正在进行战略布局,他需要把在(x, y)位置的马跳到(x’,
y’)位置,以达到威慑的目的。但是棋盘大小有限制,棋盘是一个10 x 9的网格,左上角坐标为(0,0),右下角坐标为(9,
8),马不能走出棋盘,并且有些地方已经有了棋子,马也不能跳到有棋子的点。蒜头君想知道,在不移动其他棋子的情况下,能否完成他的战略目标。

③ 输入格式

  • 输入一共10行,每行一个长度为9的字符串。
  • 输入表示这个棋盘,我们用'.'表示空位置,用'#'表示该位置有棋子,用'S'表示初始的马的位置,用'T'表示马需要跳到的位置
  • 输入保证一定只有一个'S''T'

④ 输出格式

如果在不移动其他棋子的情况下,马能从'S'跳到'T',那么输出一行"Yes",否则输出一行"No"

⑤ 样例输入

.#....#S#
..#.#.#..
..##.#..#
......##.
...T.....
...#.#...
...#.....
...###...
.........
.##......

⑥ 样例输出

Yes

1.2 思路解释

和上一篇例题的思路基本一模一样,只不过上次迷宫只能是上下左右走,而这次的象棋中🐎可以走八个方向,只是对x、y坐标的改变不同而已,其他基本一致,所以这里就不过多赘述了,不太明白的可以点这里哦【C++算法】dfs深度优先搜索(上) ——【全面深度剖析+经典例题展示】

大致思路如下:

  • ①判断象棋终点,记录所走路径
  • ②完善搜索回溯,处理数组边界
  • ③找寻马儿起点,打印结束路径

1.3 code展示

#include<iostream>
#include<stdio.h>
using namespace std;

int n, m;
char pos[105][105];
int trace[105][105];
int dir[8][2] = { {-1 , -2} , {-2 , -1} , {-2 , 1} , {-1 , 2} , {1 , 2} , {2 , 1} , {2 , -1} , {1 , -2} };  // 找出马儿走"日"时x、y坐标变化的所有情况【基本按逆时针来】

bool check_in(int x, int y) {	//	判断数组是否越界
	return (x >= 1 && x <= n && y >= 1 && y <= m);   //这里表示的是如果()里为真,则返回true,否则返回false
}

bool dfs(int x, int y) {
	if (pos[x][y] == 'T') {
		return true;
	}
	pos[x][y] = 'x';  //用x记录路径
	trace[x][y] = 1;  //标记已走过
	for (int i = 0; i < 8; i++) {
		int xx = x + dir[i][0];  //表示x加上第几个方向的第1个数,即变化后行的坐标
		int yy = y + dir[i][1];  //表示y加上第几个方向的第2个数,即变化后列的坐标
		if (pos[xx][yy] != '#' && check_in(xx, yy) && !trace[xx][yy]) {
			if (dfs(xx , yy)) {
			return true;
			}  
		}
	}	
	pos[x][y] = '.';	//	如果一个位置的上下左右都走不了,则取消该位置的路径进行回溯,回溯过程把之前已标记的'x'改回'.'
	trace[x][y] = 0;		//走不通,则回溯过程标记为没走过
	return false;
}

int main() {
	cin >> n >> m;
	//输入象棋地图 找🐎儿的起点
	int x ,  y;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> pos[i][j];
			if (pos[i][j] == 'S') {
				x = i, y = j;
			}
		}
	}
	cout << "--------------------------------" << endl;
	if (dfs(x , y)) {
		cout << "Yes!!!" << endl;
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				cout << pos[i][j];
			}	cout << endl;
		}
	}
	else {
		cout << "No!!!" << endl;
	}

	return 0;
}

在这里插入图片描述

2. 踏青

2.1 题干信息

① 背景说明概述

蒜头君和他的朋友周末相约去召唤师峡谷踏青。他们发现召唤师峡谷的地图是由一块一块格子组成的,有的格子上是草丛,有的是空地。草丛通过上下左右4个方向扩展其他草丛形成一片草地,任何一片草地中的格子都是草丛,并且所有格子之间都能通过上下左右连通。如果用'#"代表草丛'.' 代表空地,则下面的峡谷中有2片草地。

##..
..##

② 问题描述

  • 处在同一个草地的2个人可以相互看到,空地看不到草地里面的人。他们发现有一个朋友不见了,现在需要分头去找,每个人负责一片草地,蒜头君想知道他们至少需要多少人

③ 输入格式

  • 第一行输入n,m(1 ≤ n, m <= 100) 表示峡谷大小。接下来输入n行字符串表示峡谷的地形。

④ 输出格式

  • 输出至少需要多少人。

⑤ 样例输入

5 6
.#....
..#...
..#..#
...##.
.#....

⑥ 样例输出

5

2.2 思路解释

  • 根据题意,最后要求最少人数,而根据一人负责一片草地,则就转换求最少草地数,就是该地形最少有多少片土地。我们先输入地形,再依次遍历每个格子是否为草地,如果不是草地,直接略过;如果是草地,则是否之前找过,如果找过则还是略过,如果没找过,则先记录下已找过,结果+1,并对该草地的上下左右遍历是否是未被找的草地【深搜思想】。最后输出结果。

2.3 code展示

#include<iostream>
#include<stdio.h>
using namespace std;

int n, m, ans = 0;
char pos[105][105];
bool trace[105][105];

//法一
//bool check_in(int x, int y) {
//	return (x >= 1 && x <= n && y >= 1 && y <= m);
//}
//
//void dfs(int x, int y) {
//	if (check_in(x, y) && pos[x][y] == '#' && !trace[x][y]) {
//		trace[x][y] = 1;
//		dfs(x, y + 1);
//		dfs(x + 1, y);
//		dfs(x, y - 1);
//		dfs(x - 1, y);
//	}
//}

//法二
void dfs(int x, int y) {
	if (x < 1 || x > n || y < 1 || y > m || trace[x][y] || pos[x][y] == '.') {
		return;
	}
	trace[x][y] = true;
	dfs(x - 1, y);	  //记录上面
	dfs(x , y - 1);  //记录左面
	dfs(x + 1, y);   // 记录下面
	dfs(x , y + 1);  //记录右面
}

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> pos[i][j];
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (!trace[i][j] && pos[i][j] == '#') {	//	假如是草丛的话且之前没找过
				dfs(i, j);	//	把这个位置和周边草丛标记好
				ans++; //记录草丛数量
			}
		}
	}
	cout << ans << endl;
	return 0;
}

在这里插入图片描述

3. 迷宫解的方案数

3.1 题干信息

① 背景说明概述

蒜头君是一个玩迷宫的高手,天下还没有能难住他的迷宫。但是总有人喜欢习难蒜头君,不停的给蒜头君出难题。这个出题的人很聪明,他知道天下还没有能难住蒜头君的迷宫。

② 问题描述

  • 所以他便转换思维问蒜头君,在不走重复路径的情况下,总共有多少不同可以到达终点的路径呢?蒜头君稍加思索便给出了答案,你要不要也来挑战一下?

③ 输入格式

  • 第一行输入两个整数n(1≤n≤11), m(1≤m≤11), 表示迷宫的。然后有一个n*m的地图,地图由'.''#''s''e' 这四个部分组成。'.'表示可以通行的路'#' 表示迷宫的墙's' 表示起始点'e' 表示终点

④ 输出格式

  • 输出一个整数,表示从's'到达'e'的所有方案数。

⑤ 样例输入

5 5
s####
.####
.####
.####
....e

⑥ 样例输出

1

3.2 思路解释

  • 本题和前面迷宫例题思路差不多,但这个题是要统计到终点的路线的个数,所以如果能到终点,则需要ans++;如果该节点的上下左右都行不通,则在四周判断完后需要清空路径trace[x][y]=0,来继续探索下一条路。

3.3 code展示

#include<iostream>
#include<stdio.h>
using namespace std;

int n, m, ans = 0;
char pos[105][105];
bool trace[105][105];
int dir[4][2] = { {-1, 0},{0,-1},{1,0},{0,1} };

bool check_in(int x, int y) {
	return (x >= 1 && x <= n && y >= 1 && y <= m);
}

void dfs(int x, int y) {
	if (pos[x][y] == 'e') {
		ans++;	//节点为终点,所以找到一条走到终点的路,ans++;
		return ;
	}
	if (check_in(x, y) && pos[x][y] != '#' && !trace[x][y]) {   //如果这个节点不是终点且没越界,不是墙且没走过,则统计四周情况
		trace[x][y] = 1;
		for (int i = 0; i < 4; i++) {
			int xx = x + dir[i][0];
			int yy = y + dir[i][1];
			dfs(xx, yy);
		}
		trace[x][y] = 0;		//清空路线,找下一条路
	}
}

int main() {
	cin >> n >> m;
	int x, y;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> pos[i][j];
			if (pos[i][j] == 's') {
				x = i, y = j;
			}
		}
	}
	dfs(x, y);
	cout << ans << endl;
	return 0;
}

在这里插入图片描述

4. 最大的蛋糕块

4.1 题干信息

① 背景说明概述

这一天蒜头君生日,他的朋友们一起来给蒜头君买一个大的蛋糕过生日。游戏做完后到了切蛋糕的时刻了,朋友们知道蒜头君喜欢吃蛋糕,便让蒜头君自己给自己切-块最大的。蒜头君看朋友们这么热情也就不客气了。

② 问题描述

  • 这块蛋糕是由R*C的网格构成,每个网格上面都放有不同的水果。蒜头君把这些水果分为两类,一类是自己喜欢吃的水果,用'#'来表示;一类是自己不喜欢吃的水果,用'.'来表示。
  • 蒜头君对切出的蛋糕有如下要求:
    • 切出的蛋糕连成一块(可以不为矩形,但必须在网格上连通)
    • 切出的蛋糕只包含自己喜欢吃的水果
  • 请问,蒜头君最大可以吃到多大的蛋糕?

③ 输入格式

  • 第一行输入两个被空格隔开的整数R(1≤R≤1000)和C(1≤C≤1000)。然后会有一个R*C的网格,由'#''.'组成。

④ 输出格式

  • 输出一个整数,表示蒜头君可以吃到的蛋糕最大是多少(即对应到网格中的格子数)。

⑤ 样例输入

5 6
.#....
..#...
..#..#
...##.
.#....

⑥ 样例输出

2

4.2 思路解释

和题2《踏青》的总体思路一样,但《踏青》求的是带'#'每部分的数量,而本题是求每部分里'#'最多的数量。所以这道题只需要在统计每部分时记录'#'的数量再进行比较即可。

4.3 code展示

#include<iostream>
#include<stdio.h>
using namespace std;

int r, c, ans = 0, cnt = 0;
char pos[105][105];
bool trace[105][105];
int dir[4][2] = { {-1, 0},{0,-1},{1,0},{0,1} };

bool check_in(int x, int y) {
	return (x >= 1 && x <= r && y >= 1 && y <= c);
}

void dfs(int x, int y) {
	if (!check_in(x, y) || pos[x][y] != '#' || trace[x][y]) {	//深搜过程中不合要求的返回;
		return;
	}
	trace[x][y] = 1; //标记访问过
	cnt++; //记录数量
	for (int i = 0; i < r; i++) {
		int xx = x + dir[i][0];
		int yy = y + dir[i][1];
		if (pos[xx][yy] == '#') {
			dfs(xx, yy);
		}
	}
}

int main() {
	cin >> r >> c;
	int x, y;
	for (int i = 1; i <= r; i++) {
		for (int j = 1; j <= c; j++) {
			cin >> pos[i][j];
		}
	}
	for (int i = 1; i <= r; i++) {
		for (int j = 1; j <= c; j++) {
			if (!trace[i][j] && pos[i][j] == '#') {	//如果是'#',且之前没被访问过,则用dfs记录下
				cnt = 0;		//需要将该位置能求到的数量重置为0
				dfs(i, j);	//用dfs求一下所有‘#’的数量
				if (ans < cnt) {
					ans = cnt;	//更新一下ans
				}
			}
		}
	}
	cout << ans << endl;
	return 0;
}

在这里插入图片描述

5. 家谱

5.1 题干信息

① 背景说明概述

家谱,又称族谱、宗谱等,是一种以表谱形式,记载一个家族的世系繁衍及重要人物事迹的书。皇帝的家谱称玉牒,如新朝玉牒、皇宋玉牒。它以记载父系家族世系、人物为中心,由正史中的帝王本纪及王侯列传、年表等演变而来。家谱是一种特殊的文献,就其内容而言,是中国五千年文明史中具有平民特色的文献,记载的是同宗共祖血缘集团世系人物和事迹等方面情况的历史图籍。家谱属珍贵的人文资料,对于历史学、民俗学、人口学、社会学和经济学的深入研究,均有其不可替代的独特功能。

② 问题描述

  • 这一天蒜头君拿到了自己家的家谱,蒜头君便想知道,在自己家的家谱中,每位祖先有多少直系后代(直系后代包括他的孩子和他孩子的直系后代)。但是家族历史源远流长,家谱实在太庞大了,自己一个人完全数不过来。热心的你便自告奋勇帮蒜头君写一个程序,来统计每位祖先有多少直系后代

③ 输入格式

  • 输入的第一行有一个整数n(1<n≤100000),表示家谱中的总人数。接下来读入n- 1行,每行有两个整数x(1≤x≤n),y(1≤y≤n),表示x是y的父母

④ 输出格式

  • 输出n行,每行有一个整数,表示第i个人有多少个直系后代。

⑤ 样例输入

4
1 2
1 3
2 4

⑥ 样例输出

3
1
0
0

5.2 思路解释

本题输入数值的大小不确定,所以如果指定数组大小,则可能报错,所以我们采用动态数组vector来统计son数量。所以首先,我们先找根节点,所以要找直系儿子,则需要把所有儿子的后代标记一下,最后未被标记的就是直系儿子。我们找到根节点,需要先对根节点的后代进行搜索,直到找到答案。

5.3 code展示

#include<iostream>
#include<stdio.h>
#include<vector>
using namespace std;
const int N = 1e5 + 10;

int n, x, y, u;
vector<int> son[N];	//使用动态数组,便于插入和删除
bool judge_son[N];	//判断是否是儿子
int ans[N];

int dfs(int a) {	//	找有多少个直系后代【不包括自己】,但统计时为了方便,则统计自己
	int cnt = 0;
	int len = son[a].size();
	for (int i = 0; i < len; i++) {	//首先,找根节点的后代
		cnt += dfs(son[a].at(i));	//找根节点的直接后代,或者写dfs(son[u][i])
	}
	ans[u] = cnt;	//统计后代个数
	return cnt + 1; //加上自己
}

int main() {
	cin >> n;
	for (int i = 1; i < n; i++) {
		cin >> x >> y; //输入x,y 表示x是y的父亲
		son[x].push_back(y); //将y放入son[x]的动态数组中
		judge_son[y] = true; //y已经作为儿子了,所以标为true;
	}
	for (int i = 1; i <= n; i++) {	//找起始位置,即找“根”节点
		if (!judge_son[i]) {	//找到赋给u,并break
			u = i;
			break;
		}
	}
	dfs(u);
	for (int i = 1; i <= n; i++) {
		cout << ans[i] << endl;
	}
	return 0;
}

最后,感谢大家支持u (^ _ ^)

如果感觉这篇文章对你有帮助的话,不妨三连支持下,十分感谢(✪ω✪)。

printf("点个赞吧*^*");
cout << "收藏一下叭o_o";
System.out.println("评论一下吧^_^");
print("关注一下叭0-0")
  • 28
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 19
    评论
深度优先搜索DFS)是一种用于图和树的遍历算法,它按照深度优先的方式进行搜索。在DFS算法中,我们从一个起始节点开始,沿着一条路径一直向下搜索,直到达到最深的节点,然后回溯到上一个节点,继续搜索其他路径,直到找到问题的解或者遍历完所有可能的路径。 以下是一个使用C++实现深度优先搜索算法的示例: ```cpp #include <iostream> #include <vector> using namespace std; // 定义图的邻接表表示 class Graph { private: int V; // 图的顶点数 vector<vector<int>> adj; // 邻接表 public: Graph(int v) { V = v; adj.resize(V); } // 添加边 void addEdge(int u, int v) { adj[u].push_back(v); adj[v].push_back(u); } // 深度优先搜索 void DFS(int v, vector<bool>& visited) { visited[v] = true; cout << v << " "; // 递归访问邻接节点 for (int i = 0; i < adj[v].size(); i++) { int u = adj[v][i]; if (!visited[u]) { DFS(u, visited); } } } // 对图进行深度优先搜索 void DFS() { vector<bool> visited(V, false); // 记录节点是否被访问过 // 从每个未被访问的节点开始进行DFS for (int v = 0; v < V; v++) { if (!visited[v]) { DFS(v, visited); } } } }; int main() { // 创建一个包含5个节点的图 Graph g(5); // 添加边 g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(1, 3); g.addEdge(1, 4); // 对图进行深度优先搜索 cout << "DFS traversal: "; g.DFS(); return 0; } ``` 输出结果为: ``` DFS traversal: 0 1 3 4 2 ``` 这是一个简单的深度优先搜索算法的示例,它通过邻接表表示图,并从每个未被访问的节点开始进行深度优先搜索。在搜索过程中,我们使用一个visited数组来记录节点是否被访问过,以避免重复访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卫冕711

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值