《数据结构》专题9--图的遍历DFS&BFS

本文介绍了多个关于图论的实验,包括深度优先搜索(DFS)遍历无向图、迷宫探索的广度优先搜索(BFS)策略,以及寻找最短路径的算法。通过邻接矩阵和邻接表的实现,展示了如何在不同场景下运用这些基础图论技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A - 数据结构实验之图论二:图的深度遍历

Description

请定一个无向图,顶点编号从0到n-1,用深度优先搜索(DFS),遍历并输出。遍历时,先遍历节点编号小的。

Input

输入第一行为整数n(0 < n < 100),表示数据的组数。 对于每组数据,第一行是两个整数k,m(0 < k < 100,0 < m < k*k),表示有m条边,k个顶点。 下面的m行,每行是空格隔开的两个整数u,v,表示一条连接u,v顶点的无向边。

Output

输出有n行,对应n组输出,每行为用空格隔开的k个整数,对应一组数据,表示DFS的遍历结果。

Sample

Input 

1
4 4
0 1
0 2
0 3
2 3

Output 

0 1 2 3
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int eps = 1e-8;// 一个极小值
const int N = 1e5 + 100;
const int M = 3e3 + 100;
const int INF = 0x3f3f3f3f;
const LL LINF = 0x3f3f3f3f3f3f3f3f;
#define PI acos(-1.0)
#define Equ(a, b) ((fabs((a) - (b))) < (eps))
int mp[110][110];
int vis[110];
int ans[110];
int cnt;
int k, m;
void dfs(int s){
	vis[s] = 1;
	ans[cnt++] = s;
	for(int i = 0; i < k; i++){
		if(mp[s][i] && !vis[i]){
			dfs(i);
		}
	}
}
int main()
{
    int t;
	cin >> t;
	while(t--){
		cnt = 0;
		memset(vis, 0, sizeof(vis));
		memset(mp, 0, sizeof(mp));
		memset(ans, 0, sizeof(ans));
		cin >> k >> m;
		for(int i = 0; i < m; i++){
			int u, v;
			cin >> u >> v;
			mp[u][v] = mp[v][u] = 1;
		}
		dfs(0);
		for(int i = 0; i < cnt; i++){
			if(i == cnt - 1) cout << ans[i] << endl;
			else cout << ans[i] << " ";
		}
	}
    return 0;
}

B - 数据结构实验之图论四:迷宫探索

Description

有一个地下迷宫,它的通道都是直的,而通道所有交叉点(包括通道的端点)上都有一盏灯和一个开关;请问如何从某个起点开始在迷宫中点亮所有的灯并回到起点?

Input

连续T组数据输入,每组数据第一行给出三个正整数,分别表示地下迷宫的结点数N(1 < N <= 1000)、边数M(M <= 3000)和起始结点编号S,随后M行对应M条边,每行给出一对正整数,表示一条边相关联的两个顶点的编号。

Output

若可以点亮所有结点的灯,则输出从S开始并以S结束的序列,序列中相邻的顶点一定有边,否则只输出部分点亮的灯的结点序列,最后输出0,表示此迷宫不是连通图。
访问顶点时约定以编号小的结点优先的次序访问,点亮所有可以点亮的灯后,以原路返回的方式回到起点。

Sample

Input 

1
6 8 1
1 2
2 3
3 4
4 5
5 6
6 4
3 6
1 5

Output 

1 2 3 4 5 6 5 4 3 2 1

Hint

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1e3 + 100;
const int M = 2e2 + 100;
const int MAXN = 0x3f3f3f3f;
const LL LMANX = 0x3f3f3f3f3f3f3f3f;
#define PI 3.14159265358979323846
int t, n, m, s;
int u, v;
int cnt = 0;
int mp[N][N];
int vis[N], ans[N];
void dfs(int start){
    vis[start] = 1;
    ans[++cnt] = start;// 注意:这里数组下标从1开始
    for(int i = 1; i <= m; i++){
        if(vis[i] == 0 && mp[start][i] != 0){
            // 该点没遍历过且从顶点到该点有边
            dfs(i);
            ans[++cnt] = start;// 倒着存储进去(按原路返回)
        }
    }
}
int main()
{
    scanf("%d", &t);
    while(t--){
        cnt = 0;
        memset(vis, 0, sizeof(vis));
        memset(mp, 0, sizeof(mp));
        scanf("%d%d%d", &n, &m, &s);
        for(int i = 1; i <= m; i++){
            scanf("%d%d", &u, &v);
            mp[u][v] = mp[v][u] = 1;
        }
        dfs(s);
        if(cnt == n * 2 - 1){
            for(int i = 1; i <= cnt; i++){
                printf("%d%c", ans[i], i == cnt ? '\n' : ' ');
            }
        }
        else{
            for(int i = 1; i <= cnt; i++){
                printf("%d ", ans[i]);
            }
            printf("0\n");
        }
    }
    return 0;
}

C - 数据结构实验之栈与队列十:走迷宫

Description

一个由n * m 个格子组成的迷宫,起点是(1, 1), 终点是(n, m),每次可以向上下左右四个方向任意走一步,并且有些格子是不能走动,求从起点到终点经过每个格子至多一次的走法数。

Input

       第一行一个整数T 表示有T 组测试数据。(T <= 110)

对于每组测试数据:

第一行两个整数n, m,表示迷宫有n * m 个格子。(1 <= n, m <= 6, (n, m) !=(1, 1) ) 接下来n 行,每行m 个数。其中第i 行第j 个数是0 表示第i 行第j 个格子可以走,否则是1 表示这个格子不能走,输入保证起点和终点都是都是可以走的。

任意两组测试数据间用一个空行分开。

Output

 对于每组测试数据,输出一个整数R,表示有R 种走法。

Sample

Input 

3
2 2
0 1
0 0
2 2
0 1
1 0
2 3
0 0 0
0 0 0

Output 

1
0
4
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 15;
const int M = 2e2 + 100;
const int MAXN = 0x3f3f3f3f;
const LL LMANX = 0x3f3f3f3f3f3f3f3f;
#define PI 3.14159265358979323846
int n, m;
int mp[N][N];
int vis[N][N];
int r = 0;
void dfs(int x, int y){// x代表行,y代表列
    if(x < 1 || x > n || y < 1 || y > m || mp[x][y])// 越界或遇到障碍
        return ;// 跳出if语句
    if(x == n && y == m){// 到终点
        r++;
        return ;
    }
    if(vis[x][y] == 0){
        vis[x][y] = 1;
        // 按照顺时针的方向递归 右->下->左->上
        dfs(x, y + 1);
        dfs(x + 1, y);
        dfs(x, y - 1);
        dfs(x - 1, y);
        vis[x][y] = 0;// 尝试结束,取消这个点的标记!!这样才能再次从(x,y)出发
    }
}

int main()
{
    int t;
    scanf("%d", &t);
    while(t--){
        memset(mp, 0, sizeof(mp));
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++){// 题目规定地图从(1,1)开始
            for(int j = 1; j <= m; j++){
                scanf("%d", &mp[i][j]);
            }
        }
        r = 0;
        dfs(1, 1);
        printf("%d\n", r);
    }
    return 0;
}

D - 数据结构实验之图论一:基于邻接矩阵的广度优先搜索遍历

Description

给定一个无向连通图,顶点编号从0到n-1,用广度优先搜索(BFS)遍历,输出从某个顶点出发的遍历序列。(同一个结点的同层邻接点,节点编号小的优先遍历)

Input

输入第一行为整数n(0< n <100),表示数据的组数。
对于每组数据,第一行是三个整数k,m,t(0<k<100,0<m<(k-1)*k/2,0< t<k),表示有m条边,k个顶点,t为遍历的起始顶点。
下面的m行,每行是空格隔开的两个整数u,v,表示一条连接u,v顶点的无向边。

Output

输出有n行,对应n组输出,每行为用空格隔开的k个整数,对应一组数据,表示BFS的遍历结果。

Sample

Input 

1
6 7 0
0 3
0 4
1 4
1 5
2 3
2 4
3 5

Output 

0 3 4 2 5 1

Hint

以邻接矩阵作为存储结构。

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1e2 + 7;
const int M = 2e2 + 100;
const int MAXN = 0x3f3f3f3f;
const LL LMANX = 0x3f3f3f3f3f3f3f3f;
#define PI 3.14159265358979323846
int k, m, t;
int u, v;
int cnt = 0;
int ans[N];
int mp[N][N], vis[N];// vis数组保存该点是否被遍历过
void bfs(int s){
    int x;
    queue<int> q;
    vis[s] = 1;// 第一个点先标记上
    q.push(s);
	ans[cnt++] = s;
    // 队列--先进先出,后进后出
    while(!q.empty()){
        x = q.front();
        q.pop();
        for(int i = 0; i < k; i++){
            if(mp[x][i] == 1 && vis[i] == 0){
                // 如果该边存在且没遍历过
                vis[i] = 1;// 标记上
                q.push(i);
				ans[cnt++] = i;
            }
        }
    }
    printf("\n");
}
int main()
{
    int n;
    scanf("%d", &n);
    while(n--){
		cnt = 0;
        memset(mp, 0, sizeof(mp));
        memset(vis, 0, sizeof(vis));// 清空不能少
        scanf("%d%d%d", &k, &m, &t);
        for(int i = 0; i < m; i++){
            scanf("%d%d", &u, &v);
            mp[u][v] = mp[v][u] = 1;
        }
        bfs(t);
		for(int i = 0; i < cnt; i++){
			if(i == n - 1) printf("%d\n", ans[i]);
			else printf("%d ", ans[i]);
		}
    }
    return 0;
}

E - 数据结构实验之图论二:基于邻接表的广度优先搜索遍历

Description

给定一个无向连通图,顶点编号从0到n-1,用广度优先搜索(BFS)遍历,输出从某个顶点出发的遍历序列。(同一个结点的同层邻接点,节点编号小的优先遍历)

Input

输入第一行为整数n(0< n <100),表示数据的组数。
对于每组数据,第一行是三个整数k,m,t(0<k<100,0<m<(k-1)*k/2,0< t<k),表示有m条边,k个顶点,t为遍历的起始顶点。
下面的m行,每行是空格隔开的两个整数u,v,表示一条连接u,v顶点的无向边。

Output

输出有n行,对应n组输出,每行为用空格隔开的k个整数,对应一组数据,表示BFS的遍历结果。

Sample

Input 

1
6 7 0
0 3
0 4
1 4
1 5
2 3
2 4
3 5

Output 

0 3 4 2 5 1

Hint

用邻接表存储。

#include<bits/stdc++.h>
using namespace std;

const int N = 1e2 + 100;
const int MAXN = 0x3f3f3f3f;
typedef long long ll;
const ll LMANX = 0x3f3f3f3f3f3f3f3f;
vector<int> g[N];// 用邻接表存储
int n, m;
int vis[N];// 标记点,判断顶点是否遍历过
vector<int> ans;
// 广度优先搜索--用队列
void bfs(int s){
    queue<int> q;
    q.push(s);// 把起点放进去
    vis[s] = 1;// 表明这个点已经走过了
    while(q.size()){// 只要没有遍历完
        int u = q.front();// 取出头部的点
        q.pop();
        ans.push_back(u);
        for(int v: g[u]){
            if(vis[v]) continue;// 如果已经走过这个点就跳过
            vis[v] = 1;
            q.push(v);
        }
    }
}

int main()
{
    ios::sync_with_stdio(0);
    int t;
    cin >> t;
    while(t--){
        int s;
        memset(vis, 0, sizeof vis);
        ans.clear();
        cin >> n >> m >> s;
        for(int i = 0; i < n; i++) g[i].clear();// 最多只用到n
        for(int i = 1; i <= m; i++){
            int u, v;
            cin >> u >> v;
            g[u].push_back(v), g[v].push_back(u);// 无向边
        }
        //for(int i = 0; i < n; i++) sort(g[i].begin(), g[i].end());
        // 由于邻接表是按照输入顺序存储的,而本题要求:
        // 同一个结点的同层邻接点,节点编号小的优先遍历
        // 所以要先排序
        bfs(s);// 本题对于起点有规定
        for(int i: ans){
            cout << i << " "; 
        }
    }
    return 0;
}

**常用的是用递归实现dfs,用队列实现bfs。 

F - 数据结构实验之图论三:判断可达性

Description

 在古老的魔兽传说中,有两个军团,一个叫天灾,一个叫近卫。在他们所在的地域,有n个隘口,编号为1..n,某些隘口之间是有通道连接的。其中近卫军团在1号隘口,天灾军团在n号隘口。某一天,天灾军团的领袖巫妖王决定派兵攻打近卫军团,天灾军团的部队如此庞大,甚至可以填江过河。但是巫妖王不想付出不必要的代价,他想知道在不修建任何通道的前提下,部队是否可以通过隘口及其相关通道到达近卫军团展开攻击。由于n的值比较大(n<=1000),于是巫妖王找到了擅长编程的你 =_=,请你帮他解决这个问题,否则就把你吃掉变成他的魔法。为了拯救自己,赶紧想办法吧。

Input

 输入包含多组,每组格式如下。

第一行包含两个整数n,m(分别代表n个隘口,这些隘口之间有m个通道)。

下面m行每行包含两个整数a,b;表示从a出发有一条通道到达b隘口(注意:通道是单向的)。

Output

 如果天灾军团可以不修建任何通道就到达1号隘口,那么输出YES,否则输出NO。

Sample

Input 

2 1
1 2
2 1
2 1

Output 

NO
YES

bfs:(dfs当然也可以)

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1e3 + 100;
const int M = 2e2 + 100;
const int MAXN = 0x3f3f3f3f;
const LL LMANX = 0x3f3f3f3f3f3f3f3f;
#define PI 3.14159265358979323846
int mp[N][N];
int vis[N];
int n, m, a, b;
int flag;
void bfs(int x){
    int top;
    vis[x] = 1;
    queue<int>q;
    q.push(x);
    while(!q.empty()){
        top = q.front();
        q.pop();
        if(top == 1) flag = 1;// 如果能到达
        else{
            for(int i = 1; i <= n; i++){
                if(mp[top][i] && !vis[i]){// 边存在且点没遍历过
                    vis[i] = 1;
                    q.push(i);
                }
            }
        }
    }
}
int main()
{
    while(scanf("%d%d", &n, &m) != EOF){
        memset(mp, 0, sizeof(mp));
        memset(vis, 0, sizeof(vis));
        for(int i = 0; i < m; i++){
            scanf("%d%d", &a, &b);
            mp[a][b] = 1;
        }
        flag = 0;
        bfs(n);
        if(flag) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

G - 数据结构实验之图论五:从起始点到目标点的最短步数(BFS)

Description

 在古老的魔兽传说中,有两个军团,一个叫天灾,一个叫近卫。在他们所在的地域,有n个隘口,编号为1..n,某些隘口之间是有通道连接的。其中近卫军团在1号隘口,天灾军团在n号隘口。某一天,天灾军团的领袖巫妖王决定派兵攻打近卫军团,天灾军团的部队如此庞大,甚至可以填江过河。但是巫妖王不想付出不必要的代价,他想知道在不修建任何通道的前提下,部队是否可以通过隘口及其相关通道到达近卫军团展开攻击;如果可以的话,最少需要经过多少通道。由于n的值比较大(n<=1000),于是巫妖王找到了擅长编程的你 =_=,请你帮他解决这个问题,否则就把你吃掉变成他的魔法。为了拯救自己,赶紧想办法吧。

Input

 输入包含多组,每组格式如下。

第一行包含两个整数n,m(分别代表n个隘口,这些隘口之间有m个通道)。

下面m行每行包含两个整数a,b;表示从a出发有一条通道到达b隘口(注意:通道是单向的)。

Output

 如果天灾军团可以不修建任何通道就到达1号隘口,那么输出最少经过多少通道,否则输出NO。

Sample

Input 

2 1
1 2
2 1
2 1

Output 

NO
1
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1e3 + 100;
const int M = 2e2 + 100;
const int MAXN = 0x3f3f3f3f;
const LL LMANX = 0x3f3f3f3f3f3f3f3f;
#define PI 3.14159265358979323846
int n, m;
int a, b;
int vis[N], step[N];// step[]用来存储到达每个隘口所用的步数
int mp[N][N];
int cnt = 0;
void bfs(int x){
    int top;
    vis[x] = 1;
    queue<int>q;
    q.push(x);
    step[x] = 0;
    while(!q.empty()){
        top = q.front();
        q.pop();
        for(int i = 1; i <= n; i++){
            if(mp[top][i] && !vis[i]){
                q.push(i);
                step[i] = step[top] + 1;
                vis[i] = 1;
            }
        }
    }
}
int main()
{
    while(~scanf("%d%d", &n, &m)){
        memset(mp, 0, sizeof(mp));
        memset(vis, 0, sizeof(vis));
        memset(step, 0, sizeof(step));
        for(int i = 1; i <= m; i++){
            scanf("%d%d", &a, &b);
            mp[a][b] = 1;// 单向的
        }
        cnt = 0;
        bfs(n);
        if(vis[1]) printf("%d\n", step[1]);
        else printf("NO\n");
    }
    return 0;
}

H - 找朋友

Description

X,作为户外运动的忠实爱好者,总是不想呆在家里。现在,他想把死宅Y从家里拉出来。问从X的家到Y的家的最短时间是多少。

为了简化问题,我们把地图抽象为n*m的矩阵,行编号从上到下为1 到 n,列编号从左到右为1 到 m。矩阵中’X’表示X所在的初始坐标,’Y’表示Y的位置 , ’#’表示当前位置不能走,’ * ’表示当前位置可以通行。X每次只能向上下左右的相邻的 ’*’ 移动,每移动一次耗时1秒。

Input

多组输入。每组测试数据首先输入两个整数n,m(1<= n ,m<=15 )表示地图大小。接下来的n 行,每行m个字符。保证输入数据合法。

Output

若X可以到达Y的家,输出最少时间,否则输出 -1。

Sample

Input 

3 3
X#Y
***
#*#
3 3
X#Y
*#*
#*#

Output 

4
-1

Hint

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 20;
const int M = 2e2 + 100;
const int MAXN = 0x3f3f3f3f;
const LL LMANX = 0x3f3f3f3f3f3f3f3f;
#define PI 3.14159265358979323846
char mp[N][N];
int vis[N][N];
int X1, Y1;
int n, m;
int Next[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
struct node{
    int x, y, step;// 这是每个点都具备的三个状态(学废了T_T)
}a, b;// a代表一个点,b代表该点的下一个点
void bfs(int x, int y){
    a.x = x; a.y = y;
    a.step = 0;
    queue<node> q;
    q.push(a);
    vis[x][y] = 1;
    int k;
    while(!q.empty()){
        a = q.front();
        q.pop();
        if(mp[a.x][a.y] == 'Y'){
            printf("%d\n", a.step);
            return ;
        }
        // 枚举四种走法
        for(k = 0; k < 4; k++){
            b.x = a.x + Next[k][0];
            b.y = a.y + Next[k][1];
            if(b.x >= 0 && b.x < n && b.y >= 0 && b.y < m && mp[b.x][b.y] != '#' && !vis[b.x][b.y]){
                vis[b.x][b.y] = 1;
                b.step = a.step + 1;// 每走一步花费一秒,所以步数就是时间啦
                q.push(b);
            }
        }
    }
    printf("-1\n");
}
int main()
{
    while(~scanf("%d%d", &n, &m)){
        //getchar();
        memset(mp, 0, sizeof(mp));
        memset(vis, 0, sizeof(vis));
        for(int i = 0; i < n; i++){
            scanf("%s", mp[i]);
            for(int j = 0; j < m; j++){
                if(mp[i][j] == 'X'){
                    X1 = i; Y1 = j;
                }
            }
        }
        /*这种方式也行
        int i, j;
        for(i = 0; i < n; i++){
        	for(j = 0; j < m; j++){
        		if(mp[i][j] == 'X'){
        			X1 = i; Y1 = j;
        			break;
				}
			}
			if(j < m) break;
		}
        */
        bfs(X1, Y1);
    }
    return 0;
}

汤圆の拯救计划

Description


又到了汤圆星球一年一度的汤圆节了,但是大魔王却过来把汤圆公主抓走了Σ( ° △ °|||)︴

身为汤圆骑士的QAQ蒟蒻自然而然的肩负着拯救汤圆的使命

QAQ蒟蒻经历了千辛万苦(并没有)之后,来到了大魔王的城堡,根据情报,汤圆公主就被大魔王放在城堡内,然后QAQ蒟蒻发现自己是一个路

痴,所幸的是他拿到了大魔王的城堡的地图,而且在这上面标注了自己和汤圆公主的位置,那么问题来了,聪明的你能帮他计算出需要多少单位

的时间来赶到汤圆公主的位置吗?


Ps:QAQ蒟蒻每一次都可以移动到相邻的非墙的格子中,每次移动都要花费1个单位的时间

有公共边的格子定义为相邻

Input

一开始为一个整数T代表一共有T组数据

每组测试数据的第一行有两个整数n,m (2<=n,m<=300)

接下来的n行m列为大魔王的迷宫,其中

’#’为墙壁,‘_‘为地面

A代表QAQ蒟蒻,O代表汤圆公主:

Output

一组数据输出一个整数代表从QAQ蒟蒻到汤圆的位置的最短时间

如果QAQ蒟蒻不能到达汤圆的位置,输出-1

Sample

Input 

2
3 3
__A
_##
__O
2 2
A#
#O

Output 

6
-1
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int eps = 1e-8;// 一个极小值
const int N = 3e2 + 100;
const int M = 3e3 + 100;
const int INF = 0x3f3f3f3f;
const LL LINF = 0x3f3f3f3f3f3f3f3f;
#define PI acos(-1.0)
#define Equ(a, b) ((fabs((a) - (b))) < (eps))

int t, n, m;
int sx, sy;
int point[4][2] = {{0, 1}, {0, -1}, {-1, 0}, {1, 0}};
char mp[N][N];
int vis[N][N];
struct arte{
    int x, y;
    int cost;
};// 每个点具有的三个属性
void bfs(int xx, int yy){
    vis[xx][yy] = 1;
    arte p;
    queue<arte>q;
    p.x = xx;
    p.y = yy;
    p.cost = 0;
    q.push(p);
    while(!q.empty()){
        arte top = q.front();
        q.pop();
        if(mp[top.x][top.y] == 'O'){
            cout << top.cost << endl;
            return ;
        }
        for(int i = 0; i < 4; i++){
            p.x = top.x + point[i][0];
            p.y = top.y + point[i][1];
            if(p.x >= 0 && p.x < n && p.y >= 0 && p.y < m && !vis[p.x][p.y] && mp[p.x][p.y] != '#'){
                vis[p.x][p.y] = 1;
                p.cost = top.cost + 1;
                q.push(p);
            }
        }
    }
    cout << "-1" << endl;
}
int main()
{
    scanf("%d", &t);
    while(t--){
        memset(mp, 0, sizeof(mp));
        memset(vis, 0, sizeof(vis));
        scanf("%d%d", &n, &m);
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                cin >> mp[i][j];
                if(mp[i][j] == 'A') sx = i, sy = j;
            }
        }
        bfs(sx, sy);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值