• Dijkstra算法 --- 单源最短路


    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("
    ");
        //打印0-8的最短路径走过的点 注意是逆序的
        int k = 8;
        while (k){
            printf("%3d", k);
            k = pre[k];
        }
        printf("%3d", k);
        printf("
    ");
        
        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("
    ");
    
        int k = 8;
        while (k){
            printf("%3d", k);
            k = pre[k];
        }
        printf("%3d
    ", 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("
    ");
        //pre记录的是路径
        int k = 8;
        while (k){
            printf("%3d", k);
            k = dij.edges[dij.pre[k]].from; //取得前一个结点,
        }
        printf("%3d
    ", 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
  • 相关阅读:
    程序员是这样区分Null和Undefined
    JavaScript实现
    获取页面中任意一个元素距离body的偏移量
    js检测数据类型的方法你都掌握了几个?
    处理浏览器兼容你最喜欢用哪种方式
    算法竞赛入门经典 第四章 学习笔记 1
    算法竞赛入门经典 程序4-1 组合数
    算法竞赛入门经典 例题3-1 TeX中的引号
    算法竞赛入门经典 第3章 数组和字符串 学习笔记 3
    算法竞赛入门经典 第3章 数组和字符串 学习笔记 2
  • 原文地址:https://www.cnblogs.com/tommychok/p/5043714.html
Copyright © 2020-2023  润新知