分享内容
1.DFS 与迷宫问题(二)
互联网的诞生和发展(三)
域名系统(DNS)的引入
1983年:随着互联网上计算机数量的增加,使用IP地址来标识计算机变得越来越不方便。因此,域名系统(DNS)被引入。DNS允许用户使用易于记忆的域名(如"www.example.com")来访问网站,而DNS服务器会将域名转换为对应的IP地址。这大大简化了互联网的使用方式,使得普通用户也能够方便地访问网络资源。

深度优先搜索(回顾)
● 深度优先搜索(DFS)是一种搜索算法,目的是从起点开始搜索,直到到达指定
点(终点)。
深度优先搜索从一个点开始,按照一定的顺序选路,沿着某条路往下走,一直走到底(深入到底),如果走完后发现不能达到目标,返回到上一个点(回溯再试),换条路,然后继续走到底,如此往复,直至所有可能的结果都被搜索完。
● 一般使用递归的方式实现深度优先搜索。
谜宫口FS的模版代码(回顾)
适用于n行n列的迷宫(每个格子有4个方向可走):
cpp
//访问标记数组,false:没访问过,true:已经访问过
bool visited [n+1] [n+1];
// 方向数组,4行(4个方向)---1、按题目要求填好方向数组
int dir[4][2]={{第1个方向x,y},{第2个方向x,y},{第3个方向x,y},{第4个方向x,y}};
void dfs(int x, int y) {
// 终止递归
//2、按题目要求设置递归条件
if(判断是否完成目标) return;
// 递归搜索四个方向
for(int i = 0;i<4;i++) {//3、递归搜索以(x,y)为起点的4个方向
int nx = x + dir [i] [0];
int ny = y + dir [i] [1];
// 4、可能需要根据题意改进条件
if( (nx>=1 && nx <= n && ny>=1 && ny <= n) && !visited [nx] [ny]} {
visited [nx] [ny] = true;
dfs (nx,ny);
visited [nx] [ny] = false;//5、统计A到B路径数量时,需要清空访问标记
}
}
}
迷宫口FS的算法复杂度
时间复杂度
· 最坏情况:在最坏情况下,DFS需要遍历迷宫中的每一个单元格。假设迷宫的大小为nxn,那么DFS的时间复杂度为O(n^2),因为每个单元格至多会被访问一次。
· 实际表现:虽然时间复杂度为O(n^2),但实际运行时间还受到迷宫布局和起始位置的影响。如果迷宫中有大量死胡同,DFS可能会陷入较深的路径,导致时间复杂度较高。空间复杂度
· 递归调用栈:DFS的空间复杂度主要取决于递归调用栈的深度。在最坏情况下,递归调用栈的深度可能达到n^2,即整个迷宫的大小。
· 访问标记数组:通常还需要一个与迷宫大小相同的访问标记数组来记录每个单元格是否被访问过,这也会占用O(n^2)的空间。
搜索的是n行m列的迷宫,DFS的模版怎么改?

如果是n行m列,8个方向可走的迷宫,DFS的模版怎么改?

马走日
马在中国象棋以日字形规则移动。
请编写一段程序,给定nxm大小的棋盘,以及马的初始位置(a,b),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。(本题要求路径的数量)
【输入描述】一行四个整数,分别为棋盘的大小以及初始位置坐标n,m,a,b。(0≤a≤n-1,0≤b≤m-1,m≤5,n≤5)。
【输出描述】一个整数,表示马能遍历棋盘的途径总数,0为无法遍历一次。
【输入样例】
5 4 0 0
【输出样例】
32

根据题意修改模版

马走日(dfs函数)
cpp
bool visited[6][6];//访问标记数组,false:没访问过,true:已经访问过
int dir[8][2]={{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,-1}};//方向数组
int n,m,a,b;
int cnt=0;// 格子计数(某一次遍历经过的格子数量)
int ans=0;//路径数量 (完整遍历的次数)
void dfs (int x, int y) {
if(cnt == n*m) { ans++;return; }
for(int i=0;i<8;i++){//递归搜索8个方向
int nx = x + dir[i] [0];
int ny = y + dir [i] [1] ;
if((nx>=0 && nx <= n-1 && ny>=0 && ny <= m-1) && !visited[nx] [ny] ) {
visited [nx] [ny] = true;
cnt++;
dfs (nx,ny) ;
visited[nx] [ny] = false;
cnt --;//5、【重要】回退时,这个格子的访问标记清空了,格子计数也要-1
}
}
}
马走日(main函数)
main函数
- 输入n、m、a、b
- 从初始位置(a,b)开始dfs
- 输出结果
cpp
int main() {
cin >> n >> m >> a >> b;
visited [a] [b] = true;
cnt++;// 初始位置也要计数
dfs (a,b);
cout << ans << endl;
return 0;
}
马走日(完整代码示例)
cpp
#including<iostream>
using namespace std;
bool visited[6][6];//访问标记数组,false:没访问过,true:已经访问过
int dir[8][2]={{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,-1}};//方向数组
int n,m,a,b;
int cnt=0;// 格子计数(某一次遍历经过的格子数量)
int ans=0;//路径数量 (完整遍历的次数)
//从(x,y)开始深度优先搜索
void dfs (int x, int y) {
if(cnt == n*m) { ans++;return; }//完成一次遍历
for(int i=0;i<8;i++){//递归搜索4个方向
int nx = x + dir[i] [0];
int ny = y + dir [i] [1] ;
if((nx>=0 && nx <= n-1 && ny>=0 && ny <= m-1) && !visited[nx] [ny] ) {
visited [nx] [ny] = true;
cnt++;
dfs (nx,ny) ;
visited[nx] [ny] = false;
cnt --;//5、【重要】回退时,这个格子的访问标记清空了,格子计数也要-1
}
}
}
int main() {
cin >> n >> m >> a >> b;
visited [a] [b] = true;
cnt++;// 初始位置也要计数
dfs (a,b);
cout << ans << endl;
return 0;
}
【试一试】改变搜索顺序,是否会得到不同数量的遍历路径?

DFS与谜宫问题
已经学习过
- 路径连通性问题:检查从A点到达B点是否有可行的线路。
- 统计路径问题:搜索地图中从A点到达B点的路径数量。
接下来学习
- 迷宫的连通块问题
迷宫的连通块
迷宫的连通块是指迷宫中能够相互到达的所有单元格所组成的区域。
例如下图所示迷宫,格子中1表示障碍不能通行,0表示可通行。每个格子只能到达上下左右的
相邻格子。找一找这个迷宫有多少连通块?


迷宫地图如下图所示,1表示不能通行,0表示可通行;从(1,1)位置出发,走时只能是"上下左右"相邻的格子移动,并且不能走出地图;问:从(1,1)位置出发可以访问多少格子?
本题其实要问包含出发点的连通块内有多少格子。
答:8个格子。

下列地图中数字0表示可以通行,数字1表示不能通行,只能是相邻四个格子移动,迷
宫中有_5个连通块,最大的连通块有_8 个格子。

迷宫的连通块问题
迷宫的连通块问题有两类:
- 连通块内的格子数量:搜索地图中某个连通块内的格子数量。这类问题出发点已知,只要从出发点开始DFS并对格子计数即可。我们先看看如何编码实现这类问题。
- 连通块的数量:搜索地图中连通块的数量。
写代码
【编程要求】n行m列的迷宫,1表示不能通行,0表示可通行;只能是"上下左右"相邻的格子移动,并且不能走出地图;输入n,m和迷宫地图,输出(1,1)所在的连通块内有多少格子。(输入数据保证出发点可通行)
【输入样例】
4 5
0 0 0 0 1
1 1 0 0 1
0 1 0 0 1
0 0 1 1 1
【输出样例】8

连通块内有多少格子
【思路】出发点已知
- 从出发点开始DFS并对连通的格子计数
- 输出计数结果
DFS需要先确定一个搜索顺序,假设为"右下左上"
cpp
int dir[4] [2]={{0,1},{1,0},{0,-1},{-1,0}};
int cnt = 0;//格子计数

连通块内有会少格子
写出递归函数dfs()
cpp
//全局变量
int n,m;//n行m列(n,m <= 20)
int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//方向数组"右下左上"
bool visited[21][21];//访问标记数组,
int mp [21] [21];// 地图数组
int cnt =0;//格子计数
void dfs (int x, int y) {
// 关于递归终止条件:连通块内每个格子只搜索一遍,当所有能走到的格子都搜索过了,递归自动结束。
for(int i=0;i<4;i++){//递归搜索四个方向
int nx = x + dir[i]
int ny = y + dir[i]
// n*m在地图内 未搜索 可通行
if((nx>=1&&nx <= n&&ny>=1&&ny <= m) && !visited[nx] [ny]&& mp[nx] [ny] == 0) {
visited [nx] [ny] = true;
cnt++;//搜索一个格子计数一次
dfs (nx,ny);
}
}
}
主函数调用dfs()函数,从(1,1)点开始搜索。
cpp
int main() {
cin >> n >> m;
for(int i=1;i <= n; i++) {
for(int j=1;j <= m; j++) {
cin >> mp[i][j] ;
}
}
visited[1] [1] = true;
cnt++;// 出发点也要计数
dfs(1,1);
cout << cnt;
return 0;
}


完整代码
cpp
#include<iostream>
using namespace std;
//全局变量
int n,m;//n行m列(n,m <= 20)
int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//方向数组"右下左上"
bool visited[21][21];//访问标记数组,
int mp [21] [21];// 地图数组
int cnt =0;//格子计数
void dfs (int x, int y) {
// 关于递归终止条件:连通块内每个格子只搜索一遍,当所有能走到的格子都搜索过了,递归自动结束。
for(int i=0;i<4;i++){//递归搜索四个方向
int nx = x + dir[i]
int ny = y + dir[i]
// n*m在地图内 未搜索 可通行
if((nx>=1&&nx <= n&&ny>=1&&ny <= m) && !visited[nx] [ny]&& mp[nx] [ny] == 0) {
visited [nx] [ny] = true;
cnt++;//搜索一个格子计数一次
dfs (nx,ny);
}
}
}
int main() {
cin >> n >> m;
for(int i=1;i <= n; i++) {
for(int j=1;j <= m; j++) {
cin >> mp[i][j] ;
}
}
visited[1] [1] = true;
cnt++;// 出发点也要计数
dfs(1,1);
cout << cnt;
return 0;
}
迷宫的连通块问题
迷宫的连通块问题有两类:
- 格子数量:搜索地图中某个连通块内的格子数量。连通内格子数量问题只需要调用一次dfs()。
- 连通块的数量:搜索地图中连通块的数量。
① 可能有多个连通块,需要多次调用dfs();
②思路:从连通块内的任一个格子作为出发点,可完成这个连通块的DFS搜索。
我们看看如何编码实现。
连通块数量
一个nm的方格图,一些格子被涂成了黑色,在方格图中被标为1,白色格子标为0。问有多少个四连通的黑色格子连通块。四连通的黑色格子连通块指的是一片由黑色格子组成的区域,其中的每个黑色格子能通过四连通的走法(上下左右),只走黑色格子,到达该连通块中的其它黑色格子。
【输入描述】
第一行两个整数n,m(1≤n,m≤100),表示一个n m的方格图。
接下来n行,每行m个整数,分别为0或1,表示这个格子是黑色还是白色。数之间空格隔开。
【输出描述】
一行一个整数ans,表示图中有ans个黑色格子连通块。

连通块数量
- 顺序遍历找到一个未访问过且可通行格子,即要找!visitedxy&& mpxy == 1-------连通块计数+1
- 以这个格子为起点DFS,可以标记这个连通块内所有格子。
- 重复1、2步骤直到整个迷宫遍历完成。----输出连通块计数。

从(1,1)顺序遍历,
①(1,1)未访问且可通行格子----计数+1
②dfs(1,1),标记所有与(1,1)连通的格子
③继续顺序遍历地图,找到下一个未访问且可通行格子(3,1)----计数+1
④ dfs(3,1)可以标记所有与(3,1)连通格子
⑤找到下一个未访问且可通行格子(3,3)----计数+1
⑥dfs(3,3)可以标记所有与(3,3)连通格子遍历结束。
写出递归函数dfs()
cpp
//全局变量
int n,m;//n行m列(n,m <= 100)
int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//方向数组"右下左上"
bool visited[101][101];//访问标记数组,
int mp[101][101];//地图数组
int cnt = 0;//连通块计数
void dfs(int x, int y) {
// 关于递归终止条件:连通块内每个格子只搜索一遍,当所有能走到的格子都搜索过了,递归自动结束。
for(int i=0;i<4;i++){// 递归搜索四个方向
int nx = x + dir [i] [0];
int ny = y + dir [i] [1];
if((nx>=1&&nx <= n&&ny>=1&&ny <= m) && !visited[nx] [ny] && mp[nx] [ny] == 1) {
visited [nx] [ny] = true;
//cnt++;//此处只能统计格子数,不能统计连通块数
dfs (nx,ny);
}
}
}
主函数调用dfs()函数,从(1,1)点开始遍历。
cpp
int main() {
cin >> n >> m;
for(int i=1;i <= n; i++) {
for(int j=1;j <= m; j++) {
cin >> mp[i][j];
}
}
//连通块数量问题的关键代码。需要多次调用dfs()。
//从左到右,从上到下遍历
for(int i=1;i <= n; i++) {
for(int j=1;j <= m; j++) {
if(!visited[i] [j] && mp[i] [j] == 1) {
cnt++;
visited[i] [j] = true;
dfs(i,j);
}
}
}
cout << cnt;
return 0;
}
完整代码示例
cpp
#include<iostream>
using namespace std;
//全局变量
int n,m;//n行m列(n,m <= 100)
int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//方向数组"右下左上"
bool visited[101][101];//访问标记数组,
int mp[101][101];//地图数组
int cnt = 0;//连通块计数
void dfs(int x, int y) {
// 关于递归终止条件:连通块内每个格子只搜索一遍,当所有能走到的格子都搜索过了,递归自动结束。
for(int i=0;i<4;i++){// 递归搜索四个方向
int nx = x + dir [i] [0];
int ny = y + dir [i] [1];
if((nx>=1&&nx <= n&&ny>=1&&ny <= m) && !visited[nx] [ny] && mp[nx] [ny] == 1) {
visited [nx] [ny] = true;
//cnt++;//此处只能统计格子数,不能统计连通块数
dfs (nx,ny);
}
}
}
int main() {
cin >> n >> m;
for(int i=1;i <= n; i++) {
for(int j=1;j <= m; j++) {
cin >> mp[i][j];
}
}
//连通块数量问题的关键代码。需要多次调用dfs()。
//从左到右,从上到下遍历
for(int i=1;i <= n; i++) {
for(int j=1;j <= m; j++) {
if(!visited[i] [j] && mp[i] [j] == 1) {
cnt++;
visited[i] [j] = true;
dfs(i,j);
}
}
}
cout << cnt;
return 0;
}
连通块数量
【思考】如果本题其他要求不变,迷宫格子从四连通变成八连通,如何统计连通块数量?
1、把4行的方向数组改成8行方向数组,指定8个方向的顺序。例如下图顺序,对应的方向数组如下:

cpp
int dir[8][2]={{-1,1},{0,1},{1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0} };
2、dfs()中要搜索8个方向

3、其他代码不变
本次课程的知识点
-
DFS进行马走日的遍历
-
DFS搜索地图中某个连通块内的格子数量
-
DFS搜索地图中连通块的数量
1、根据深度优先搜索思想,下图按照先左后右的顺序从1号开始搜索,搜索的路径是?(搜索过的点不再统计)D

A、1-2-3-4-5-6-7-8
B、1-2-4-7-3-6-5-8
C、1-2-4-7-5-8-3-6
D、1-2-4-7-8-5-3-6
2、下图所示迷宫,格子中1表示障碍不能通行,0表示可通行。每个格子只能
到达上下左右的相邻格子。找一找这个迷宫有 个连通块?4

水洼个数
有一块N×M的土地,雨后积起了水,有水标记为'W',干燥为'。八连通的积水被认为是连接在一起的。请求出院子里共有多少水洼?
【输入描述】
第一行为N,M(1≤N;M≤100)。后面为N行,每行M个字符,表示这行的土地状况。
【输出描述】
一行,共有的水洼数。

【提示】
-
本题地图信息是字符
-
迷宫类型是八连通的迷宫
-
要统计W格子组成的连通块的数量
-
输入的地图信息没有空格隔开,需要处理
cpp
#include<iostream>
using namespace std;
int n,m;//n. m <= 100
int visited[101][101];//访问标记数组
int dir[8][2]=(4-1,1}.10,1},11,1},11,0},{1,-1},{0,-1},{-1,-1},{-1,0}};//方向数组
char mp[101][101]; // 地图数组(字符)
int cnt = 0,
void dfs (int x, int y) {
//递归搜索8个方向
for(int i=0;i<8;i++) {
int nx = x + dir [i] [0] ;
int ny = y + dir[i] [1] ;
//注意,本题中地图标记'w'可通行
if((nx>=1 && nx <= n && ny>=1 && ny <= m) && !visited [nx] [ny] && |mp [nx] [ny] == 'w'
visited [nx] [ny] = true;
dfs (nx,ny) ;
}
}
}
int main() {
cin >> n >> m;
char t[101];
for(int i=1;i <= n;i++) {
cin>>t;//没有空格,开,需要处理
for(int j=0;j<m;j++)
mp[i][i+1]= t[i];//mP[][]行列都从下标1开始放
}
for(int i=1;i <= n;i++){//从左到右,从上到下遍历
for(int j=1;j <= m;j++) {
if(!visited[i][j] && mp[i][j] == 'W'){//未访问且可通行的格子
cnt++;
visited[i] [j] = true;
dfs(i,j);//以(i,j)为起点DFS,标记这个连通块所有格子已访问
}
}
}
cout << cnt;
return 0;
}