dijkstra算法 基础版+邻接表、优先队列优化 双版本

0 例题

Problem
在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,
却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?

Input
输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。输入保证至少存在1条商店到赛场的路线。

Output
对于每组输入,输出两行行,第一行表示工作人员从商店走到赛场的最短时间,第二行表示路径各点。

Sample Input
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0

Sample Output
3
1 2
2
1 3

1 例题分析

本题求得是源点1到点N的最短路径最少花费以及具体路径点。所以很容易地想到求单源最短路径求解算法——dijkstra算法。
在每条边权值为非负实数的有向带权图中,给定一个点称为源点,求该源点到其他点的最短路径长度就是单源最短路径问题。在带权图中,两个顶点的路径长度指它们之间的路径上各边的权值之和。
dijkstra算法是解决单源最短路径问题的贪心算法,它先求出长度最短的一条路径,然后参照该路径求出长度次短的一条路径,直到求出源点到其它各个顶点的最短路径。
dijkstra算法的基本思想是假定源点为u,顶点集合V被划分为两部分,集合S和集合V-S。初始时集合S中只有源点u,S中的顶点到远点的最短路径已确定,V-S中的顶点到源点的最短路径待定。从源点出发只经过S中的点到达V-S中的点的路径为特殊路径,用数组dist[]记录每个顶点所对应的最短特殊路径长度。
dijkstra算法采用的贪心策略是选择特殊路径长度最短的路径,将其连接的V-S中的顶点加入集合S,同时更新数组dist[]。一旦S包含了所有顶点,dist[]就是从源点到其他所有顶点的最短路径长度。

2 代码

2.1 邻接矩阵原始代码

#include <iostream>
#include <queue>

#define MAX 1<<30
#define MAXN 1105
using namespace std;
int map[MAXN][MAXN], dist[MAXN], flag[MAXN], p[MAXN], n, m;


/*map[][]为邻接矩阵,dist[]记录各点到源点的距离;
 * flag[]记录顶点的最短路径是否已经找出;所有的点在V集合中,找出就放在S集合里,
 * 否则就在V-S集合中,flag=1代表在S集合,否则在V-S集合中;
 * p[]是记录最短路径上某一顶点的前驱顶点*/
void dij(int);

void findpath(int);

int main() {
    while (cin >> n >> m, n && m) {
        for (int i = 0; i < 1105; i++) {
            for (int j = 0; j < 1105; j++) {
                map[i][j] = MAX;
            }
        }
        for (int i = 1; i <= m; i++) {
            int a, b, c;
            cin >> a >> b >> c;
            map[a][b] = c;
            map[b][a] = c;
        }/*存储图*/
        dij(1);
        cout << dist[n] << endl;
        findpath(1);

        cout << endl;
    }
    return 0;
}

void dij(int v) {
    for (int i = 1; i <= n; i++) {
        dist[i] = map[v][i];
        flag[i] = 0;
        if (dist[i] == MAX) {
            p[i] = -1;/*源点到该点的距离为无限大,说明源点与该点不相邻*/
        } else {
            p[i] = v;/*说明与源点v相邻*/
        }
    }
    flag[v] = 1;/*初始化dist[]和flag[]*/
    for (int i = 1; i <= n; i++) { /*找到每个点距离源点最短路径*/
        int temp = MAX, t = v;
        for (int j = 1; j <= n; j++) {
            if (!flag[j] && dist[j] < temp) {/*找到V-S集合(flag==0代表属于该集合)中距离源点最小的点,即dist[]最小的点*/
                t = j;
                temp = dist[j];
            }
        }
        if (t == v) {
            return;
        }
        flag[t] = 1;/*将找到的最小dist[]的点t加入S集合*/
        for (int j = 1; j <= n; j++) {/*将找到的点t加入S集合后,更新与t相邻的点的dist[]值*/
            if (!flag[j] && map[t][j] < MAX) {
                if (dist[j] > temp + map[t][j]) {
                    dist[j] = temp + map[t][j];
                    p[j] = t;
                }
            }
        }
    }
}

void findpath(int v) {
    deque<int> path;
    path.clear();
    /*for (int i = 1; i <= n; i++) {
        int x = n;
        if (x == -1 && v != i) {
            cout << "源点到第" << i << "点" << ",无路可达" << endl;
            continue;
        }
        while (x != -1) {
            path.push_front(x);
            x = p[x];
        }
        cout << "源点到第" << i << "点的最短路径为:";
        while (!path.empty()) {
            cout << path.front();
        if (path.size() != 1) {
            cout << "--";
        }
        path.pop_front();
        }
        cout << endl;
    }*/
    /*由于此题只需要找出第N点的最短路径,所以不需要写出上面的代码,上面的代码是所有点的最短路径
     * 本题的代码如下*/

    int x = n;
    if (x == -1 && v != n) {
        cout << "no path" << endl;
    }
    while (x != -1) {
        path.push_front(x);
        x = p[x];
    }
    cout << "path:";
    while (!path.empty()) {
        cout << path.front();
        if (path.size() != 1) {
            cout << "--";
        }
        path.pop_front();
    }
    cout << endl;
}

2.2 邻接表+优先队列优化后的代码

dijkstra算法实际上就是BFS算法的“升级版”。BFS算法每次选择队列中的队首元素进行扩展,扩展出队首元素的相邻元素(并且未曾访问过),将这些元素加入队列;dijkstra算法是BFS的升级版。当一个图中的每条边都加上权值后,BFS就没办法求一个点到另一个点的最短路径了。这时候,需要用到dijkstra算法。从最基本原理上讲,把BFS改成dijkstra算法,只需要把“队列”改成“优先队列”就可以了。

posted @ 2020-09-02 17:48  leiyangace  阅读(315)  评论(0)    收藏  举报