题目描述
有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径。
输入格式
输入说明:输入数据的第1行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0~(N−1);M是高速公路的条数;S是出发地的城市编号;D是目的地的城市编号。随后的M行中,每行给出一条高速公路的信息,分别是:城市1、城市2、高速公路长度、收费额,中间用空格分开,数字均为整数且不超过500。输入保证解的存在。
输出格式
在一行里输出路径的长度和收费总额,数字间以空格分隔,输出结尾不能有多余空格。
输入样例
4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20
输出样例
3 40
题解
题意理解:求图中两点的最短路径,如果路径长度相同,选择花费少的那条。
使用 Dijkstra 算法解决单源有权无向图的最短路径问题。关于该算法已在 07-图5 Saving James Bond - Hard Version 中详细分析,这里不再赘述。
但这里还需要考虑花费少的那条,只需要当遍历的那个结点 i 加入集合内结点 V 到源点距离相等时,如果加入这个 V 能使花费减少,那也选这个 V 。
需要在 Dijkstra 函数内再加一个 Cost[] ,在更新 Dist[] 的同时一起更新 Cost[] ,如果路径长相等不更新 Cost[] 但 Cost 减少则更新 Cost[] 。要记得在新加的条件分支内更新 Path[] 。
图用结构体数组存,包含距离和路费两个分量。
完整代码:
#include <stdio.h>
#include <stdlib.h>
#define INF 65535
typedef struct {
int dist;
int cost;
} Graph;
void BuildGraph(Graph G[][500], int N, int M)
{
int i, j, a, b, d, c;
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
if (i == j) G[i][j].dist = 0;
else G[i][j].dist = G[j][i].dist = INF;
for (i = 0; i < M; i++) {
scanf("%d%d%d%d", &a, &b, &d, &c);
G[a][b].dist = G[b][a].dist = d;
G[a][b].cost = G[b][a].cost = c;
}
}
int MinDistV(int Collected[], int Dist[], int N)
{
int i, min;
for (i = 0; i < N && Collected[i]; i++) ;
if (i == N) return -1;
min = i++;
for (; i < N; i++) {
if (!Collected[i] && Dist[i] < Dist[min])
min = i;
}
return min;
}
void Dijkstra(Graph G[][500], int N, int S, int Path[])
{
int i, V, Collected[500], Dist[500], Cost[500];
for (i = 0; i < N; i++) {
Dist[i] = INF;
Path[i] = -1;
}
Dist[S] = 0;
Cost[S] = 0;
while (1) {
V = MinDistV(Collected, Dist, N);
if (V == -1) break;
Collected[V] = 1;
for (i = 0; i < N; i++) {
if (!Collected[i] && G[V][i].dist < INF) {
if (Dist[V] + G[V][i].dist < Dist[i]) {
Dist[i] = Dist[V] + G[V][i].dist;
Cost[i] = Cost[V] + G[V][i].cost;
Path[i] = V;
} else if (Dist[V] + G[V][i].dist == Dist[i]
&& Cost[V] + G[V][i].cost < Cost[i]) {
Cost[i] = Cost[V] + G[V][i].cost;
Path[i] = V;
}
}
}
}
}
void Solve(Graph G[][500], int N, int S, int D)
{
int i, top, stack[500], Dist, Cost, Path[500];
Dijkstra(G, N, S, Path);
top = 0;
for (i = D; i != -1; i = Path[i])
stack[top++] = i;
Dist = Cost = 0;
while (top > 1) {
top--;
Dist += G[stack[top - 1]][stack[top]].dist;
Cost += G[stack[top - 1]][stack[top]].cost;
}
printf("%d %d", Dist, Cost);
}
Graph G[500][500];
int main()
{
int N, M, S, D;
scanf("%d%d%d%d", &N, &M, &S, &D);
BuildGraph(G, N, M);
Solve(G, N, S, D);
return 0;
}
拓展
单源有权无向图的最短路径还有一些变形:
求最短路径的条数
无法求出各条具体的路径如何,只能数出路径数目
- count[s] = 1;
- 如果找到更短路:count[s] = count[v];
- 如果找到等长路:count[s] += count[v];
void Dijkstra(Graph G[][500], int N, int S, int Path[])
{
int i, V, Collected[500], Dist[500], Count[500];
for (i = 0; i < N; i++) {
Dist[i] = INF;
Path[i] = -1;
}
Dist[S] = 0;
Count[S] = 1;
while (1) {
V = MinDistV(Collected, Dist, N);
if (V == -1) break;
Collected[V] = 1;
for (i = 0; i < N; i++) {
if (!Collected[i] && G[V][i].dist < INF) {
if (Dist[V] + G[V][i].dist < Dist[i]) {
Dist[i] = Dist[V] + G[V][i].dist;
Count[i] += Count[V];
Path[i] = V;
} else if (Dist[V] + G[V][i].dist == Dist[i]) {
Count[i] = Count[V];
//Path[i] = V;
}
}
}
}
}
求边数最少的最短路
把本题的路费全部改为 1 就是求边数最少最短路的方法
- count[s] = 0;
- 如果找到更短路:count[i] = count[v] + 1;
- 如果找到等长路:count[i] = count[v] + 1;
void Dijkstra(Graph G[][500], int N, int S, int Path[])
{
int i, V, Collected[500], Dist[500], Edge[500];
for (i = 0; i < N; i++) {
Dist[i] = INF;
Path[i] = -1;
}
Dist[S] = 0;
Edge[S] = 0;
while (1) {
V = MinDistV(Collected, Dist, N);
if (V == -1) break;
Collected[V] = 1;
for (i = 0; i < N; i++) {
if (!Collected[i] && G[V][i].dist < INF) {
if (Dist[V] + G[V][i].dist < Dist[i]) {
Dist[i] = Dist[V] + G[V][i].dist;
Edge[i] = Edge[V] + 1;
Path[i] = V;
} else if (Dist[V] + G[V][i].dist == Dist[i]
&& Edge[V] + 1 < Edge[i]) {
Edge[i] = Edge[V] + 1;
Path[i] = V;
}
}
}
}
}