前言:个人建议在阅读本博客前先学习图论有关知识,我也有在博客中记录关于图论这一章的学习。
题目
小 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 2≤Q≤5,1<n≤500,0<m≤5000,1≤Ui,Vi,S,T≤n,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 栈的长度
程序框架
运行过程
整个系统的运行过程:
- 输入Q;
- 初始化(关键步骤);
- 输入城市和道路的数目;
- 输入道路的数据,建立图和邻接表;
- 输入起点和终点城市;
- 深度优先搜索遍历图;
- 计算最小差值;
- 判断并输出结果
思路讲解
在下面,顶点就是城市,边就是道路。
由前面的结构我们知道,每个城市结点结构中,只存储了指向第一条道路的指针,城市的编号用数组下标来表示,所以 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;
}
希望本篇博客能对你起到一点的帮助作用,也希望能动动小手点个赞啦~~。