Dijkstra算法

来源:互联网 发布:mysql可视化工具下载 编辑:程序博客网 时间:2024/06/07 13:26

Dijkstra算法

  本文主要讲解的是一种著名的图论算法——Dijkstra算法。该算法由荷兰计算机科学家Dijkstra于1959 年提出,是一种经典的用于计算正权图上的单源最短路的算法。所谓单源最短路(Single-Source Shortest Path, SSSP),指的是从一个结点出发到其他所有结点的最短路。
  简单介绍Dijkstra算法的基本思想。
  设G=(V,E)是一个正权图。先把图中的点集V分成两组。第一组为已经求出最短路径的点的集合,用S表示。初始时,S中只有一个源点。之后每求得一个点的最短路径,就将这点加入到集合S中,直到所有点都加入到S中,求解完成。第二组为其余未确定最短路径的点集,不妨用U表示,即集合U=V\S。
  接下来的求解过程中,我们要将U中的点按最短路径长度从小到大依次加入S中。在加入的过程中,总要保证以下两点:
  (1) 从源点到S中各点的最短路径长度不大于从源点到U中任何点的最短路径长度。
  (2) 每个点对应着一个距离。S中的点对应的距离就是从源点到此点的最短路径长度;而U中的点对应的距离,则是从源点到此点之间只包含S中的点为中间点的当前最短路径长度。
  如果各位看到这里觉得一头雾水的话,没有关系。放慢速度,继续往下看。
  下面讲述Dijkstra算法的实现步骤。
  (1) 初始时,S只包含源点v,点v距离为0,U包含除v外的其他点。若点v与U中点u有邻接的边,则此点u的距离为(u,v)边的权值;否则,若u不是v的出边邻接点,则点u的距离为无穷大。
  (2) 从U中选取距离最小的点k,把k加入S中(该距离即为源点v到点k的最短路径长度)。
  (3) 把点k作为中间点考虑,分析由点k出发的各条边,更新此时U中与点k邻接的各点u所对应的距离:若从源点v经过点k到点u的距离比原来的不经过点k的距离短,则更新点u的距离值。(这称为边(k, u)上的松弛操作(relaxation))
  (4) 重复上述步骤(2)和(3),直到所有点都包含在S中。
  为了便于大家理解,放一张动图。请务必弄清楚求解过程的每个细节。再次强调,如果图中存在着负权的边,则不可使用Dijkstra算法。(请大家自己想一想为什么)
Dijkstra算法示意
  这里,再补充讲一点东西——图的存储。在最坏情况下,边数m和点数n的平方应该是同阶的。但是,在大多数情况下,图中的边并没有那么多。m远小于n平方的图称为稀疏图(Sparse Graph),而m较大的图称为稠密图(Dense Graph)。对于稠密图而言,适合用邻接矩阵存储(如Prim算法中的存储);而对于稀疏图,适合使用结构体,并使用数组(或vector)存储(如Kruskal算法中的存储)。
  事实上,除此之外,稀疏图还有一种十分流行的表示法——邻接表(Adjacency List)。在这种存储方式中,每个结点i都有一个链表,里面保存着从i出发的所有边。对于无向图,每条边会在邻接表中出现两次。为简单起见,这里我们用数组实现链表。先给每条边编号,然后用first[u]保存从结点u出发的“第一条边”的编号,用next[e]表示编号为e的边的“下一条边”的编号。如果学过链表,那么不难写出插入链表的代码如下:

    next[e] = first[u[e]];    first[u[e]] = e;

  上述代码中有一处需要注意:新插入的边插入到了链表的首部而非尾部,这样就避免了插入时对链表的遍历。读者如果学过哈希表,就会发现哈希表中所使用的链表与这里的链表实现很相似。
  到此为止,Dijkstra算法的内容已经基本讲完。找了一道模板题HDU2544,链接为http://acm.hdu.edu.cn/showproblem.php?pid=2544。由于Dijkstra算法在图论中的重要性,希望各位一定把它彻底弄懂,并独立编写程序。
  最后给出我写的Dijkstra模板(也即HDU2544题解):

#include<cstdio>#include<iostream>#include<cstring>#include<algorithm>#define maxn 102using namespace std;int firste[maxn], nexte[maxn*maxn], d[maxn];int n, m, sta, en, cnt;bool vis[maxn];struct E {    int u, v, w;} edge[maxn*maxn];void Init() {    memset(firste, 0, sizeof(firste));    memset(nexte, 0, sizeof(nexte));    memset(edge, 0, sizeof(edge));    memset(vis, false, sizeof(vis));    for(int i = 1; i <= n; i++)        d[i] = 1e9;    sta = 1; en = n;    cnt = 0;}void build(int u, int v, int w) {    E newe;    newe.u = u; newe.v = v; newe.w = w;    edge[++cnt] = newe;    nexte[cnt] = firste[u];    firste[u] = cnt;}void Dijkstra() {    d[sta] = 0;    while(true) {        int x = -1;        for(int i = 1; i <= n; i++) {            if(!vis[i])                if(x == -1 || d[i] < d[x])                    x = i;        }        if(x == -1) break;        vis[x] = true;        for(int i = firste[x]; i; i = nexte[i]) {            int vt = edge[i].v;            if(d[vt] > d[x]+edge[i].w)                d[vt] = d[x]+edge[i].w;        }    }}int main() {    while(cin >> n >> m && n) {        Init();        int tu, tv, tw;        for(int t = 1; t <= m; t++) {            cin >> tu >> tv >> tw;            build(tu, tv, tw);            build(tv, tu, tw);        }        Dijkstra();        cout << d[en] << endl;    }    return 0;}