07-图6 旅游规划

141 阅读3分钟

题目描述

有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径。

输入格式

输入说明:输入数据的第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;
                }
            }
        }
    }
}