码迷,mamicode.com
首页 > 编程语言 > 详细

Dijkstra算法 --- 单源最短路

时间:2015-12-13 23:39:43      阅读:276      评论:0      收藏:0      [点我收藏+]

标签:

Dijkstra算法适用于边权值为正的情况,可用于计算正权图上的单元最短路。

其伪代码如下:

设d[v0] = 0, 其他d[i] = INF

循环n次{

  在所有未标号的结点中,选取d值最小的结点x

  给结点x加上永久标号

  对于从x出发的所有边,执行松弛操作。

}

//松弛操作的伪代码如下:

RELAX(u,v,w)

  if(u.d + w(u,v) < v.d){

    v.d = w.d + w(u,v);

    pre[v] = u;

  }

 

Dijkstra算法代码:

技术分享
/* Dijkstra 单源最短路算法 */
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 100; //定义顶点数
const int INF = 65535; //定义最大权值
int v[maxn], w[maxn][maxn];//v为标号,w是边,点自动按顺序编号
int d[maxn]; //d用于最短路长度的记录
int pre[maxn]; //用于记录路径, prev[i]为i的前一点的下标

/* 0到其他点的最短路 */
void dijkstra(int n, int v0){
    memset(v, 0, sizeof v);
    //如果是其他结点,只要更改i==?就可以
    for (int i = 0; i < n; ++i){
        d[i] = (i == v0 ? 0 : INF);
    }
    //如果上面初始化为0的d,那么此处可以从1开始
    for (int i = 0; i < n; ++i){
        int x, m = INF;
        //遍历找最小的边
        for (int y = 0; y < n; ++y){
            if (!v[y] && d[y] <= m){
                m = d[x = y];
            }
        }//for(y)
        v[x] = 1; //永久标号 
        for (int y = 0; y < n; ++y){
            //更新最短路径
            //若无须记录路径只需要下面的一行
            //d[y] = min(d[y], d[x] + w[x][y]);

            //记录路径代码
            if (d[x] + w[x][y] < d[y]){
                d[y] = d[x] + w[x][y];
                pre[y] = x;
            }
        }
    }//for(i)
}

int main()
{
    int n, m; //点数和边数
    scanf("%d%d", &n, &m); //点自动按数组编号
    int u1, v1, w1;
    for (int i = 0; i < n; ++i){
        for (int j = 0; j < n; ++j){
            w[i][j] = INF;
        }
    }
    for (int i = 0; i < m; ++i){
        scanf("%d%d%d", &u1, &v1, &w1);
        w[u1][v1] = w1;
        w[v1][u1] = w1;  //无向图
    }
    dijkstra(n, 0);
    for (int i = 0; i < n; ++i){
        printf("%3d", d[i]);
    }
    printf("\n");
    //打印0-8的最短路径走过的点 注意是逆序的
    int k = 8;
    while (k){
        printf("%3d", k);
        k = pre[k];
    }
    printf("%3d", k);
    printf("\n");
    
    return 0;
}

/*
测试数据
9 16
0 1 1
0 2 5
1 2 3
1 3 7
1 4 5
2 4 1
2 5 7
3 4 2
3 6 3
4 5 3
4 6 6
4 7 9
5 7 5
6 7 2
6 8 7
7 8 4

结果: 数组d 0 1 4 7 5 8 10 12 16
0~8路径反序: 8 7 6 3 4 2 1 0 
*/
View Code

 

其实Dijkstra是可以优化的,优化后的Dijkstra适用于系数图(m << n*n)

优化的Dijkstra(以点为存储结构):

技术分享
/* 改进的Dijkstra --- 以点为存储结构 */
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
using namespace std;

const int INF = 65535;
const int maxn = 100;

/* 定义结点类型 用于存储图结构 */
struct node{
    int x; //终点坐标
    int w; //边的权值
    node(){}
    node(int a, int b) :x(a), w(b){}
};

/* 用于优化的结点类型 */
struct HeapNode{
    int x, d; //终点的坐标及其维护的最短路径
    HeapNode(){}
    HeapNode(int a, int b) :x(a), d(b){}
    bool operator<(const HeapNode& rhs) const{
        return d > rhs.d;
    }
};

//Vertex[i]表示以i为起点的点集 Vertex[i][j]为其某一个终点(共有Vertex[i].size()-1个终点)
vector<node> Vertex[maxn]; 
int dis[maxn], n;//dis维护最短路的值, n为结点数
bool visit[maxn]; //求最短路时用于标记的数组
int pre[maxn]; //记录路径

/* 求v0到其他点的最短路 */
void Dijkstra(int v0){
    for (int i = 0; i < n; ++i){
        dis[i] = INF;
    }
    dis[v0] = 0;
    memset(visit, 0, sizeof visit);

    priority_queue<HeapNode> q;
    q.push(HeapNode(v0, 0));
    while (!q.empty()){
        HeapNode x = q.top(); q.pop(); //取得最小的路径的点

        //此处应该判别x是否已经访问!已经加入永久标号的点,到达其更长的路径可能还在队列中
        //但使用这个点已经不可能更新最短路径表,因为已经比其更小的路径已经用来更新过,
        //故应该设置一个visit数组标记是否访问过!
        if (visit[x.x]){
            continue; //如果已经是永久标号 结束
        }
        
        //从点x出发,更新和其相连的端点的最短路
        //x.x表示点的编号  Vertex[x.x]表示以x.x为起点集合
        for (int i = 0; i < Vertex[x.x].size(); ++i){
            node y = Vertex[x.x][i]; //y是终点 x是起点
            //若经过点x可使得路径更小 ---> 更新
            if (dis[x.x] + y.w < dis[y.x]){
                dis[y.x] = dis[x.x] + y.w;
                pre[y.x] = x.x;
                //将更新的更小的路径入队
                //此时队列中可能存在相同的点但比其更长的路径,这也是为什么需要上面的visit数组
                q.push(HeapNode(y.x, dis[y.x]));
            }
        }
    }
}

int main()
{
    int m;
    int u, v, w;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; ++i){
        scanf("%d%d%d", &u, &v, &w);
        Vertex[u].push_back(node(v, w));
        Vertex[v].push_back(node(u, w));
    }
    Dijkstra(0);
    for (int i = 0; i < n; ++i){
        printf("%3d", dis[i]);
    }
    printf("\n");

    int k = 8;
    while (k){
        printf("%3d", k);
        k = pre[k];
    }
    printf("%3d\n", k);
    
    return 0;
}

/*
测试数据
9 16
0 1 1
0 2 5
1 2 3
1 3 7
1 4 5
2 4 1
2 5 7
3 4 2
3 6 3
4 5 3
4 6 6
4 7 9
5 7 5
6 7 2
6 8 7
7 8 4

结果: 数组d 0 1 4 7 5 8 10 12 16
0~8路径反序: 8 7 6 3 4 2 1 0
*/
View Code

优化的Dijkstra(以边为存储结构):

技术分享
/* 改进的Dijkstra算法 ---  以边为存储结构 */
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;

const int maxn = 100;
const int INF = 65535;

/* 边结构 */
struct Edge{
    int from, to, dist;//from,to,dist分别为边起点、终点和权值
    Edge(int u, int v, int d) :from(u), to(v), dist(d){}
};

/* 此节点为优先队列的元素 相当于(i,d[i])二元组 */
struct HeapNode{
    int d, u; //u记录最短路径,u为起点
    bool operator<(const HeapNode& rhs) const{
        return d > rhs.d; //小的优先
    }
    HeapNode(int a, int b) :d(a), u(b){}
};

/* 定义最短路算法结构体 由于边是用vector存储,且自动编号,似乎只适用于有向图 */
struct Dij{
    int n, m;
    vector<Edge> edges; //边集---保存边信息(自动编号为0~m-1) 
    vector<int> G[maxn]; //按点记录边 G[i]表示以i为起点的集合 
                        //G[i][j]表示以i为起点的集合中其中一条边的在edges中的序号
    bool visit[maxn];    //是否已经是永久标号
    int d[maxn];        //s到各点的距离 s是起点 ---> 记录最短路
    int pre[maxn];        //记录路径,pre[i]表示以i为终点的边的编号

    /* n为顶点数 */
    void init(int n){
        this->n = n; 
        for (int i = 0; i < n; ++i){
            G[i].clear(); //以i为起点的集合清空
        }//for(i)
        edges.clear(); //清空边集
    }

    void AddEdge(int from, int to, int dist){
        edges.push_back(Edge(from, to, dist)); //加入边集
        m = edges.size(); //m-1即为当前加入的边的序号
        G[from].push_back(m - 1);
        //无向图 加上反向边 有向图可省去下面三行
        edges.push_back(Edge(to, from, dist));
        m = edges.size();
        G[to].push_back(m - 1);
    }

    void dijkstra(int s){
        priority_queue<HeapNode> Q;
        for (int i = 0; i < n; ++i){
            d[i] = INF;
        }//for(i)
        d[s] = 0;
        memset(visit, 0, sizeof visit);
        Q.push(HeapNode(0,s)); //0,s分别为距离和起点
        while (!Q.empty()){
            HeapNode x = Q.top(); Q.pop(); //优先小地出栈,即先找到最小的d
            int u = x.u;
            //if (visit[u])
            //    continue;
            visit[u] = true;
            for (int i = 0; i < G[u].size(); ++i){
                Edge& e = edges[G[u][i]]; //取得以u为起点的边e
                if (d[u] + e.dist < d[e.to]){
                    //经过点u和e到达e.to能更小
                    d[e.to] = d[u] + e.dist; //更新最小路径
                    pre[e.to] = G[u][i]; //记录路径
                    Q.push(HeapNode(d[e.to], e.to) );
                }
            }//for(i)
        }
    }
};

int main()
{
    Dij dij;
    int n, m; //点和边的数目
    scanf("%d%d", &n, &m);
    dij.init(n);
    int a, b, c;
    for (int i = 0; i < m; ++i){
        scanf("%d%d%d", &a, &b, &c);
        dij.AddEdge(a, b, c);
    }
    dij.dijkstra(0);
    for (int i = 0; i < dij.n; ++i){
        printf("%3d", dij.d[i]);
    }
    printf("\n");
    //pre记录的是路径
    int k = 8;
    while (k){
        printf("%3d", k);
        k = dij.edges[dij.pre[k]].from; //取得前一个结点,
    }
    printf("%3d\n", k);
    return 0;
}

/*
测试数据
9 16
0 1 1
0 2 5
1 2 3
1 3 7
1 4 5
2 4 1
2 5 7
3 4 2
3 6 3
4 5 3
4 6 6
4 7 9
5 7 5
6 7 2
6 8 7
7 8 4

结果: 数组d 0 1 4 7 5 8 10 12 16
0~8路径反序: 8 7 6 3 4 2 1 0
*/
View Code

 

Dijkstra算法 --- 单源最短路

标签:

原文地址:http://www.cnblogs.com/tommychok/p/5043714.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!