Dijkstra算法

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 102
using 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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值