数据结构实验之图论七:驴友计划
Description
做为一个资深驴友,小新有一张珍藏的自驾游线路图,图上详细的标注了全国各个城市之间的高速公路距离和公路收费情况,现在请你编写一个程序,找出一条出发地到目的地之间的最短路径,如果有多条路径最短,则输出过路费最少的一条路径。
Input
连续T组数据输入,每组输入数据的第一行给出四个正整数N,M,s,d,其中N(2 <= N <= 500)是城市数目,城市编号从0~N-1,M是城市间高速公路的条数,s是出发地的城市编号,d是目的地的城市编号;随后M行,每行给出一条高速公路的信息,表示城市1、城市2、高速公路长度、收费额,中间以空格间隔,数字均为整数且不超过500,输入数据均保证有解。
Output
在同一行中输出路径长度和收费总额,数据间用空格间隔。
Sample
Input
1 4 5 0 3 0 1 1 20 1 3 2 30 0 3 4 10 0 2 2 20 2 3 1 20
Output
3 40
解题思路:
本题用Dijkstra算法,
解释1:Dijkstra 思路是维护一个集合 s ,集合内的点是已经确定最短路的点,可以视为一个大整体,每次操作找出与集合相邻的点中距离起点最近的点加入集合中,并确定它的最短路为它的上家的最短路+该边权值,存在 dis 中
解释2:Dijkstra算法算是贪心思想实现的,首先把起点到所有点的距离存下来找个最短的,然后松弛一次再找出最短的,所谓的松弛操作就是,遍历一遍看通过刚刚找到的距离最短的点作为中转站会不会更近,如果更近了就更新距离,这样把所有的点找遍之后就存下了起点到其他所有点的最短距离。
两种解释都在阐述Dijkstra算法的操作,可以一同理解。
#include<iostream>
#include<cstdio>
#include<cstring>
#define MAX 0x3f3f3f3f///此处值的定义有待研究,我用10000就会导致后面memset出问题,现在这个值是看资料看到的
using namespace std;
/// 数据结构实验之图论七:驴友计划
/// Dijkstra 算法 有权图的单源最短路
int dist[550];///记录源点s到某点v的最短路径,算法执行过程中存储的不一定是最终结果,其值会更新
int cost[550];///记录源点s到某点v的最小路费,算法执行过程中存储的不一定是最终结果,其值会更新
int collected[550];///用来收录最短路径上的点,被收录置1,否则为0
int map[550][550];///高速公路长度(权值1)
int money[550][550];///通行费(权值2)
void Dijkstra(int, int, int);
int main()
{
int N;///城市数目
int M;///城市间高速公路的条数
int T, s, d, i;///T组数据,s是出发地的城市编号,d是目的地的城市编号
scanf("%d", &T);
while(T--)
{
memset(map, MAX, sizeof(map));
memset(collected, 0, sizeof(collected));
scanf("%d %d %d %d", &N, &M, &s, &d);
for(i = 0; i < N; i++)
{///起点和终点都是自己时,路的长度为0
map[i][i] = 0;
}
int u, v, _distance, _money;///城市1,城市2,高速公路长度,收费额
while(M--)
{
scanf("%d %d %d %d", &u, &v, &_distance, &_money);
if(map[u][v] > _distance)
{///初始的距离为MAX,意为两点间没有通路,现输入已知距离,则更新
map[u][v] = map[v][u] = _distance;
money[u][v] = money[v][u] = _money;
}
}
Dijkstra(N, s, d);///城市数,出发地,目的地
}
return 0;
}
void Dijkstra(int n, int s, int d)
{
int i, j, u, v, min_dis;
for(i = 0; i < n; i++)
{///初始化最开始的dist数组,将起点s的邻接点都放到dist数组中
dist[i] = map[s][i];
cost[i] = money[s][i];
}
dist[s] = 0;///初始化3个数组,在源点s处的值
cost[s] = 0;
collected[s] = 1;///将起点s收录进最短路径中
for(i = 1; i < n; i++)///除了源点s,剩下n-1个点需要计算其到源点s的最短路径,故循环n-1次
{
///在dist[]中寻找下一最短路径点
min_dis = MAX;///临时变量置为最大值,用来寻找当前dist[]中距离源点s短的路径
for(j = 0; j < n; j++)///dist[]中的路径会更新,不是最终值,最短路径点需要在dist[]找
{///遍历dist[j]找到距离源点s最短的路径,然后记录下来该路径的终点的下标j,收录该终点
if(collected[j] == 0 && dist[j] < min_dis)
{///寻找dist(起点到某点的最短路径)中未被收录的点的路径最小值
min_dis = dist[j];///记录新的即将被收录点的dist值
u = j;///记录新的即将被收录点的下标
}
}
collected[u] = 1;///在全部点中找到符合条件的点后,标记为被收录进最短路径
///将u点收录进最短路径中后,将u点的邻接点放到dist[]中用来寻找下一最短路径
for(v = 0; v < n; v++)///遍历全部点找到点u的每个邻接点v
{
if(collected[v] == 0 && map[u][v] < MAX && dist[v] > dist[u] + map[u][v])
{///如果该点未被收录
///并且u到v的路径存在(map[u][v] = 1)
///并且起点s到u的dist+u到v的距离 小于 已收录的起点s到v的dist
///则更新dist[v]
dist[v] = dist[u] + map[u][v];
cost[v] = cost[u] + money[u][v];
}
else if(collected[v] == 0 && map[u][v] < MAX && dist[v] == dist[u] + map[u][v])
{///如果距离相等,则判断路费
if(cost[v] > cost[u] + money[u][v])
{///保存最小的路费
cost[v] = cost[u] + money[u][v];
}
}
}
}
printf("%d %d\n", dist[d], cost[d]);///输出终点d位置的最短路径长度和最少花费
}
更多参考:
1、 https://blog.csdn.net/KO812605128/article/details/98876698 这个讲的很好
关于MAX的值的问题,关系到memset函数的使用,我找了一些文章资料可以做参考
https://www.cnblogs.com/freeyouth/p/10771009.html
https://blog.csdn.net/STRVE/article/details/99814628
https://blog.csdn.net/dragon60066/article/details/69292236
关于 Dijkstra 算法 的知识:
1、Dijkstra算法详细(单源最短路径算法) https://www.cnblogs.com/bigsai/p/11537975.html 这个讲的也很好
2、帮忙通俗解释一下Dijkstra算法 ? - 绝顶我为峰的回答 - 知乎 https://www.zhihu.com/question/20630094/answer/758191548