利用 DFS 求两点最小差值问题

前言:个人建议在阅读本博客前先学习图论有关知识,我也有在博客中记录关于图论这一章的学习。

题目

小 L 居住的地方有很多城市,每个城市编号从1到 n,每两个城市之间可以存在多条道路,总共有 m 条道路,每条道路都有一个固定的行驶速度 V。一天,小 L 要从城市 S 出发去另一个城市 T 游玩,现在小 L 想知道从 S 出发到 T 所经过的道路中最大速度 V m a x V_{max} Vmax 和最小速度 V m i n V_{min} Vmin 的差值的最小值是多少?输入数据保证 S 一定可以到达 T。

**注:**题意即为求从 S 到 T的路径中或许是由多条道路构成的,每条路径都有一个最大速度和最小速度的差值,求所有路径的差值的最小值。

输入

一个整数 Q,代表有多少组测试数据,接下来对每组测试数据:

第1行:n, m。分别是城市数目和道路数目。

第2 ~ m + 1行:3个整数 U i , V i , W i , ( i = 1 , . . . , m ) Ui,Vi, Wi, (i = 1, ..., m) Ui,Vi,Wi,(i=1,...,m),道路的两个城市编号和道路的行驶速度。

最后一行:两个整数 S 和 T,代表起点和终点的城市编号。

其中, 2 ≤ Q ≤ 5 , 1 < n ≤ 500 , 0 < m ≤ 5000 , 1 ≤ U i , V i , S , T ≤ n , 0 < W i < 30000 2 ≤ Q ≤ 5 ,1 < n ≤ 500,0 < m ≤ 5000, 1 ≤ Ui, Vi, S, T ≤ n, 0 < Wi < 30000 2Q51<n5000<m5000,1Ui,Vi,S,Tn,0<Wi<30000

输出

一个整数代表所求最小差值。

样例输入

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

样例输出

2
2

存储结构

采用邻接表来存储图,vertex 是顶点的意思,edge 就是边(或邻边)的意思。要注意的是,城市结点中的 firstRoad 指针是指向道路的,所以要用道路结点结构类型来定义。

道路结点结构

struct RoadNode // 道路结点结构
{
	int adjCity; // 该条道路所指向的城市的编号
	int velocity; // 该条道路的行驶速度,也即权重
	struct RoadNode* nextRoad; // 指向下一条道路的指针
};

城市结点结构

struct CityNode // 城市结点结构
{
	RoadNode* firstiRoad; // 指向第一条道路的指针
};

由这两个结构体类型的定义可知,最后得到的邻接表,应该是以多个城市结点为顶点引出的多条单链表。每条单链表上除了顶点结点是城市结点,剩余的都为道路结点。所有的道路结点依次链接,但都是从该条单链表的顶点结点出发的道路。

全局变量

#define MAX_CITY_NUM 500 // 0号城市不使用
#define MAX_ROAD_NUM 5000 // 0号道路不使用
#define MAX_velocity 30010

RoadNode road[(MAX_ROAD_NUM + 1) * 2]; // 道路数组,数组长度要乘2,因为来回都要建立一个结点
CityNode city[MAX_CITY_NUM + 1]; // 城市数组

int visit[MAX_VEX]; // 城市的访问标记,初始值为0,表示未访问过
int roadNodeNum; // 计数已经建立的道路结点数
int minDif; // 全局最小差值
int cityNum, roadNum; // 城市数目和道路数目
int startCity, endCity; // 起点和终点的城市编号

int nowPath[MAX_VEX] = {0}; // 存储当前路径的城市序列
int minPath[MAX_VEX] = {0}; // 最小差值的路径上的道路序列
int top = -1, path; // nowPath 栈的栈顶以及 minPath 栈的长度

程序框架

运行过程

整个系统的运行过程:

  1. 输入Q;
  2. 初始化(关键步骤);
  3. 输入城市和道路的数目;
  4. 输入道路的数据,建立图和邻接表;
  5. 输入起点和终点城市;
  6. 深度优先搜索遍历图;
  7. 计算最小差值;
  8. 判断并输出结果

思路讲解

在下面,顶点就是城市,边就是道路。

由前面的结构我们知道,每个城市结点结构中,只存储了指向第一条道路的指针,城市的编号用数组下标来表示,所以 city 数组的长度是 MAX_CITY_NUM + 1,以便从1开始存储所有的城市。

在道路结点结构中,不仅用整数存储了该条道路所通向的城市和道路的速度,也存储了指向下一条道路的指针。因为这是一个有向图,而道路是可以往返的,所以在建立图时,来回的道路结点都要创建一个,所以道路数组的长度是 (MAX_ROAD_NUM + 1) * 2。也由此可知,在录入一条道路信息时,总是会同时创建两个道路结点,他们在数组 road 中的下标是相邻的,只不过起点城市和通向的城市刚好相反。

以样例输入中的第二组测试数据举例。首先我们建立好整个图,建立图的过程又需要先建立邻接表。当然在草稿纸上可以直接先将图画好再去写出它的邻接表。

建立邻接表时我使用的是头插法,这一点体现在函数 GreatGraph() 和 CreatEdge() 中。这样便使得后输入的道路,其结点更靠近城市结点。如图所示,每个结点我都用数组元素来表示,箭头即为相应的结点指针。city[x] 表示城市 x,road[y] 道路 y,其通向的城市可在图中看出。

整个 DFS() 函数所做的事情就这一个:遍历所有能从 S 到 T 的路径,求出最大速度和最小速度的差值,比较看是否为全局最小值,是则更新;不是则继续看下一条路径。退出DFS的标志是起点城市 = 终点城市。详细的 DFS 运行过程请看图,结合代码食用效果更佳。由最后可以知道 p 又回到了 city[1],开始遍历第二条路径。所以采用深度优先搜索遍历,我们可以遍历完所有从 S 到 T 的路径,这也是设置双向道路结点的原因。
在这里插入图片描述

更多的测试数据

2 1
1 2 500
1 2

3 2
1 2 1
2 3 3
1 3

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

4 7
1 2 5
1 2 6
1 2 7
2 3 4
2 3 8
3 4 1
3 4 10
1 4

5 13
1 2 2
1 2 3
1 2 4
1 2 7
2 3 2
2 3 6
2 3 5
3 4 3
3 4 9
3 4 8
3 4 10
4 5 1
4 5 4
1 5

该图为下面一组测试数据用图

12 17
1 2 5
2 3 1
4 5 6
5 6 10
7 8 14
8 9 7
10 11 4
11 12 14
1 4 3
4 7 6
7 10 14
2 5 7
5 8 11
8 11 5
3 6 1
6 9 6
9 12 5
1 12

该图为下面一组测试数据用图

9 10
1 2 10
1 4 10
2 3 8
2 5 5
3 6 13
4 7 11
5 6 2
5 8 1
6 9 3
7 8 15
1 9

该图为下面一组测试数据用图

9 12
1 2 12
1 4 6
2 3 12
2 5 11
4 7 4
4 5 9
3 6 8
5 6 8
5 8 10
7 8 9
6 9 12
8 9 8
1 9

完整代码

#include <iostream>
using namespace std;

#define MAX_CITY_NUM 50 // 0号城市不使用
#define MAX_ROAD_NUM 500 // 0号道路不使用
#define MAX_velocity 30010

#define MAX(a, b) (a)>(b)?(a):(b)
#define MIN(a, b) (a)<(b)?(a):(b)

struct RoadNode // 道路结点结构
{
	int adjCity; // 该条道路所指向的城市的编号
	int velocity; // 该条道路的行驶速度,也即权重
	struct RoadNode* nextRoad; // 指向下一条道路的指针
};

struct CityNode // 城市结点结构
{
	RoadNode* firstiRoad; // 指向第一条道路的指针
};


RoadNode road[(MAX_ROAD_NUM + 1) * 2]; // 道路数组,数组长度要乘2,因为来回都要建立一次联系
CityNode city[MAX_CITY_NUM + 1]; // 城市数组

int visit[MAX_CITY_NUM]; // 城市的访问标记,初始值为0,表示未访问过
int roadNodeNum; // 计数已经建立的道路结点数
int minDif; // 全局最小差值
int cityNum, roadNum; // 城市数目和道路数目
int startCity, endCity; // 起点和终点的城市编号

int nowPath[MAX_CITY_NUM] = {0}; // 存储当前路径的城市序列
int minPath[MAX_CITY_NUM] = {0}; // 最小差值的路径上的道路序列
int top = -1, path; // nowPath 栈的栈顶以及 minPath 栈的长度

void CreatRoad(int a, int b, int v) // 建立道路 ab 的结点
{
	road[roadNodeNum].adjCity = b; // 该条道路通向城市 b
	road[roadNodeNum].velocity = v; // 该条道路速度
	// 头插法:令道路 ab 的 nextRoad 指向城市 a 的 firstRoad 指向的道路
	road[roadNodeNum].nextRoad = city[a].firstiRoad;
	city[a].firstiRoad = &road[roadNodeNum]; // 令城市 a 的 firstRoad 指向该条道路
	++roadNodeNum; // 道路结点数量+1
}

void CreatGraph() // 建立图
{
	cout << "请输入" << roadNum << "行道路数据" << endl;
	int a, b, v, cnt = roadNum;
	roadNodeNum = 1;

	while (cnt--)
	{
		cin >> a >> b >> v;
		CreatRoad(a, b, v); // 两个方向的道路都要建立一次
		CreatRoad(b, a, v);
	}
}

void DFS(int start, int vMax, int vMin)
{
	if (start == endCity) // start 标志每次 DFS 时的起始城市,等于 end 时表明到达终点,输出差值
    {
        cout << "vMax:" << vMax << "  vMin:" << vMin << "  差值是:" << vMax - vMin << "\t";
        for (int i = 0; i <= top; ++i)
            cout << nowPath[i] << "-->";
        cout << endCity << endl;

        if (vMax - vMin < minDif) // 如果该路径的差值更小
        {
            minDif = vMax - vMin; // 更新最小差值
            for (int i = 0; i <= top; ++i) // 保存该最小路径
                minPath[i] = nowPath[i];
            path = top; // 记录路径长度
        }
    }
	else
	{
		visit[start] = 1; // 标记为访问过,避免陷入死循环
		RoadNode* p; // 遍历指针
		for (p = city[start].firstiRoad; p != NULL; p = p->nextRoad) // 开始遍历所有道路
		{
		    if (visit[p->adjCity] == 0) // 如果该道路指向的城市未访问过
            {
                nowPath[++top] = start;
                DFS(p->adjCity, MAX(p->velocity, vMax), MIN(p->velocity, vMin)); // 递归执行DFS
                nowPath[top] = 0;
                top--;
            }
		}
		visit[start] = 0; // 由递归返回时设置为未访问过
	}
}

int main()
{
	int Q;
	cout << "请问有多少组测试数据:";
	cin >> Q;

	while (Q--)
	{
	    for (int i = 0; i <= cityNum; ++i) // 城市、道路、访问数组初始化
        {
            city[i].firstiRoad = NULL;
            road[i].nextRoad = NULL;
            visit[i] = 0;
        }

		cout << "\n请输入城市数目和道路的数目:";
		cin >> cityNum >> roadNum;
		CreatGraph();

		cout << "请输入起点和终点的城市编号:";
		cin >> startCity >> endCity;
		minDif = MAX_velocity;
		DFS(startCity, -1, MAX_velocity); // 求最大值初始化形参为最小,求最小值初始化形参为最大

		if (minDif <= MAX_velocity)
        {
            cout << "得到的最小差值是:" << minDif << endl;
            cout << "最小差值的路径为:";
            for (int i = 0; i <= path; ++i) // 输出存在最小差值的路径
                cout << minPath[i] << "-->";
            cout << endCity << endl;
        }
		else
			cout << "差值越界,请重新输入!" << endl;
	}

	return 0;
}


希望本篇博客能对你起到一点的帮助作用,也希望能动动小手点个赞啦~~。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,让我来为您讲解如何使用DFS算法来解决迷宫问题DFS算法是一种搜索算法,它通过深度优先遍历图或树来寻找目标节点。在迷宫问题中,我们可以将每一个可行的位置看做是图中的一个节点,然后使用DFS算法来搜索从起点到终点的路径。 具体的实现步骤如下: 1. 定义一个二维数组来表示迷宫,其中0表示可以通过的通道,1表示障碍物。 2. 定义一个visited数组来记录每个节点是否被访问过。 3. 定义一个栈来保存搜索过程中经过的节点。 4. 从起点开始,将其加入栈中,并标记为已经访问。 5. 对于栈中的每个节点,依次遍历其相邻节点,如果相邻节点未被访问过且可以通过,则将其加入栈中,并标记为已经访问。 6. 如果最终找到了终点,搜索结束,返回路径;否则,回溯到上一个节点,继续搜索直到找到终点或者栈为空。 7. 如果栈为空,表示没有找到从起点到终点的路径。 下面是一个简单的实现示例代码: ```python def dfs(maze, visited, path, start, end): if start == end: return path + [end] visited[start[0]][start[1]] = True for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]: x, y = start[0] + dx, start[1] + dy if 0 <= x < len(maze) and 0 <= y < len(maze[0]) and not visited[x][y] and maze[x][y] == 0: res = dfs(maze, visited, path + [start], (x, y), end) if res: return res return None # 测试代码 maze = [[0, 1, 0, 0, 0], [0, 1, 0, 1, 0], [0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 0, 0, 1, 0]] visited = [[False] * len(maze[0]) for _ in range(len(maze))] path = [] start = (0, 0) end = (4, 4) res = dfs(maze, visited, path, start, end) if res: print(res) else: print("找不到路径!") ``` 希望这个回答可以帮助您解决迷宫问题

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值