zoukankan      html  css  js  c++  java
  • 数据结构-图的最短路径之Djikstra算法(迪杰斯特拉算法)

    一. Djikstra算法定义

    1. 形式:用来解决单源最短路径的问题,即给出图G和起点s,通过算法到达每个顶点的最短距离。
    2. 基本思想: 对图G(V, E)设置集合S, 存放已被访问的顶点,然后每次从集合V-S中选择与起点s的最短距离最小的一个顶点u,访问并加入集合S。之后,令顶点u为中介点, 优化起点和所有的从u能到达的顶点v之间的最短距离。这样的操作执行n(顶点的个数)次。
    3. 伪代码:
    //G为图, 一般设置为全局变量,数组d为源点到达各点的最短路径长度,s为起点
    Djikstra(G, d[], s){
        初始化
        for(循环n次){
            u = 是d[u]最小的还未被访问的顶点的标号
            记u已被访问
            for(从u出发能到达的所有顶点v){
                if(v未被访问&&以U为中介使得s到顶点v的最短距离d[v]更优){
                    优化d[v]
                }
            }
        }
    }
    

    二、具体实现

    1. 邻接矩阵版
    const int MAXV = 1000;//最大顶点数
    const int INF = 10000000000;//设INF为一个很大数
    
    //适用于点数不大的情况
    int n, G[MAXV][MAXV];
    int d[MAXV];
    bool vis[MAXV];
    
    void Dijkstra(int s){
        fill(d, d+MAXV, INF);
        d[s] = 0;
        for(int i = 0; i < n; i++){
            int u = -1. MIN = INF;//u使d[u]最小, MIN存放该最小d[u]
            for(int j = 0; j < n; j++){
                if(vis[j] == false && d[j] < MIN){
                    u = j;
                    MIN = d[j];
                }
            }
            //找不到小于INF的d[u],说明剩下的顶点与s不连通
            if(u == -1) return;
            vis[u] = true;//标记u为已访问
            for(int v = 0; v < n; v++){
                //如果v未访问&&u能够到达v&&以u为中介点可以使d[v]更优
                if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]){
                    d[v] = d[u] + G[u][v];//优化d[v]
                }
            }
        }
    }
    
    2. 邻接表版
    struct node{
        int v, dis;//v为边的目标顶点,dis为边权
    };
    
    vector<node> Adj[MAXV];
    int n;
    int d[MAXn];
    bool vis[MAXV] = {false};
    
    void Dijkstra(int s){
        fill(d, d+MAXV, INF);
        d[s] = 0;
        for(int i = 0; i < n; i++){
            int u = -1, MIN = INF;
            for(int j = 0; j < n; j++){
                if(vis[j] == false && d[j] < MIN){
                    u = j;
                    MIN = d[j];
                }
            }
            if(u == -1) return;
            vis[u] = true;
            //只有这个部分与邻接矩阵自而发不同
            for(int j = 0; j < Adj[u].size(); j++){
                int v = Adj[u][j].v//通过邻接表直接获得u能够到达的v
                if(vis[v] == false && d[u] + Adj[u][j].dis < d[v]){
                    d[v] = d[u] + Adj[u][j].dis;
                }
            }
        }
    }
    
    3. 最短路径的求法(在上面的基础上)
    • 如果题目中给出的是无向图,那么根据邻接表和邻接矩阵自行改变即可,如果是邻接矩阵可以在两边同时加上一样的权值;如果是邻接表那么使用push_back,也是同时交换输入。
    <1>以邻接矩阵为例
    const int MAXV = 1000;//最大顶点数
    const int INF = 10000000000;//设INF为一个很大数
    
    //适用于点数不大的情况
    int n, G[MAXV][MAXV];
    int d[MAXV];
    bool vis[MAXV];
    int pre[MAXV];//表示从起点到顶点v的最短路径上v的前一个顶点(新添加)
    void Dijkstra(int s){
        fill(d, d+MAXV, INF);
        d[s] = 0;
        for(int i = 0; i < n; i++){
            int u = -1. MIN = INF;//u使d[u]最小, MIN存放该最小d[u]
            for(int j = 0; j < n; j++){
                if(vis[j] == false && d[j] < MIN){
                    u = j;
                    MIN = d[j];
                }
            }
            //找不到小于INF的d[u],说明剩下的顶点与s不连通
            if(u == -1) return;
            vis[u] = true;//标记u为已访问
            for(int v = 0; v < n; v++){
                //如果v未访问&&u能够到达v&&以u为中介点可以使d[v]更优
                if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]){
                    d[v] = d[u] + G[u][v];//优化d[v]
                    //就是在这里改变,添加了一条语句
                    pre[v] = u;//新添加
                }
            }
        }
    }
    
    //如何打印,就是递归打印
    void dfs(int s, int v){
        if(v == s){
            printf("%d
    ", s);
            return;
        }
        dfs(s, pre[v]);
        printf("%d
    ", v);
    }
    
    4. 会出现的其他情况,即附加条件
    • 即出现除了第一个条件最短路径外,可能还会有其他的条件限制,如有多条路径
    • 每条边增加一个边权(花费);
    • 每个点增加一个点权(如每个城市可以收到的物资);
    • 直接问有多少条最短路径。
    <1>新增边权。以新增边权花费为例,都是在最后判断出进行修改,其余均不需改动,因为是新增边权,所以在存储上和原来的最短路径是一样的,就是新开辟数组cost[][],然后设立数组c,用于存储最小花费。初始化c[s]=0,其余都初始化为c[u]=INF.
            for(int v = 0; v < n; v++){
                //如果v未访问&&u能够到达v&&以u为中介点可以使d[v]更优
                if(vis[v] == false && G[u][v] != INF){
                    if(d[u] + G[u][v] < d[v]){
                         d[v] = d[u] + G[u][v];//优化d[v]
                         c[v] = c[u] + cost[u][v];
                    }else if(d[u] + G[u][v] == d[v] && c[u] + cost[u][v] < c[v]){
                        c[v] = c[u] + cost[u][v];
                    }
                }
            }
    
    <2>新增点权,以新增的点权代表城市中能收集到的物资为例,用weight[u]表示城市u中物资数目,并增加一个数组w[],令其为起点s到到达顶点u可以收集的最大物资w[u].初始化w[s] = weight[s], 其余w[u] = 0.
            for(int v = 0; v < n; v++){
                //如果v未访问&&u能够到达v&&以u为中介点可以使d[v]更优
                if(vis[v] == false && G[u][v] != INF){
                    if(d[u] + G[u][v] < d[v]){
                         d[v] = d[u] + G[u][v];//优化d[v]
                         w[v] = w[u] + weight[v];
                    }else if(d[u] + G[u][v] == d[v] && w[u] + weight[v] > w[v]){
                        w[v] = w[u] + weight[v];
                    }
                }
            }
    
    <3>求最短路径的条数。只需要增加一个数组num[],初始时num[s] = 1,其余都为num[u] = 0.
            for(int v = 0; v < n; v++){
                //如果v未访问&&u能够到达v&&以u为中介点可以使d[v]更优
                if(vis[v] == false && G[u][v] != INF){
                    if(d[u] + G[u][v] < d[v]){
                         d[v] = d[u] + G[u][v];//优化d[v]
                         num[v] = num[u];
                    }else if(d[u] + G[u][v] == d[v]){
                        num[v] += num[u];
                    }
                }
            }
    
    五、更优的遍历模板(Djikstra+DFS)
    • 只考虑最短路径(距离)的Djikstra算法,然后从这些最短距离中选出一条第二尺度最优的路径出来。
    1. 使用Djikstra算法记录所有最短的路径
    1. 我们需要使用vector,来记录最短路径的前驱结点,因为最短路径的条数可能不止一条。
    2. 通过vector类型的数组就可以通过DFS来获取所有的最短路径。
    3. 首先讲解如何获取这个pre数组,本部分的Djikstra算法只需考虑距离这一因素,不必受第二尺度的干扰,之前的写法中,数组初始化pre[i] = i,表示在初始条件下的前驱为自身,但是在这不需要进行初始化,因为可能会进行更新操作。
    4. 代码:
    vector<int> pre[MAXN];
    void Djikstra(int s){
        fill(d, d+MAXN, INF);
        d[s] = 0;
        for(int i = 0; i < n; i++){
            int u = -1, MIN = INF;
            for(int j = 0; j < n; j++){
                if(vis[j] == false && d[j] < MIN){
                    u = j;
                    MIN = d[j];
                }
            }
            if(u == -1) return;
            vis[u] = true;
            for(int v = 0; v < n; v++){
                if(vis[v] == false && G[u][v] != INF){
                    if(d[u] + G[u][v] < d[v]){
                        d[v] = d[u] + G[u][v];
                        pre[v].clear();//更新清空操作,这就是为啥不用初始化。
                        pre[v].push_back(u);
                    }else if(d[u] + G[u][v] == d[v]){
                        pre[v].push_back(u);
                    }
                }
            }
        }
    }
    
    2. 遍历所有最短路径找出一条使第二尺度最优的路径
    • 与之前不同的是,之前的写法是使用一个递归来获取最短路径,,但是现在可能存在多个前驱结点,所以遍历的过程就是会形成一棵递归树。
    • 当对于这棵树进行遍历的时候,每次到达叶子结点,也就是起点,就会产生一条完整的最短路径。
    • 得到最短路径后,可以进行第二尺度的计算,然后进行跟新,找出最优的最短路径。
    • 接下来考虑如何进行DFS函数的书写:
    1. 最为全局变量的最优值optValue
    2. 记录最优路径的数组path(使用vector)
    3. 临时记录DFS遍历到叶子结点时的路径tempPath(也使用vector记录)
    4. 然后就是递归边界和递归式
    5. 递归边界,如果当前访问的结点是叶子结点(也就是路径的起点st),那么说明到达递归边界,此时tempPath,存放了一天最短路径,求出第二尺度value的值,与optValue进行比较,如果更优那么进行覆盖。
    6. 对于递归式,只需对于pre[v]中的所有前驱结点进行遍历即可。
    7. 递归过程中tempPath是如何生成的,只需在访问结点v时,将v添加到tempPath中即可,然后遍历递归,等pre[v]中所有结点遍历完毕后,把tempPath最后面的v弹出。
    8. 需要注意的是,叶子结点(也就是路径的起点st),没有办法通过上的写法直接加入tempPath,因此在访问到叶子节点的时候临时加入。
    9. 代码:
    int optValue;
    vector<int> pre[MAXN];
    vector<int> path, tempPath;
    void DFS(int v){//当前访问结点v
        //递归边界
        if(st == v){//如果到达叶子结点st(即路径起点)
            tempPath.push_back(v);
            int value;//存放临时路径tempPath的第二尺度的值
            计算路径tempPath上的value
            if(value 优于 optValue){//更新第二尺度最优值与最优路径
                optValue = value;
                path = tempPath;
            }
            tempPath.pop_back();//将刚加入的结点删除
            return;
        }
        //递归式
        tempPath.push_back(v);//将当前访问结点加入临时路径的最后
        for(int i = 0; i < pre[v].size(); i++){
            DFS(pre[v][i]);
        }
        tempPath.pop_back();//遍历完所有前驱结点,将当前结点v删除
    }
    
    • 对于上面部分,我们只需要针对于题目的要求,对计算路径tempPath的value进行修改即可,而这个地方一般会涉及路径边权或者点权的计算,需要注意的是,由于递归的原因,存放在tempPath中的路径结点是逆序的,因此访问结点需要倒着进行,当然如果仅是对于边权或点权进行求和,正着访问也是没有问题的。
    //边权之和
    int value = 0;
    for(int i = tempPath.size()-1; i > 0; i--){
        //当前结点id, 下一个结点idNext
        int id = tempPath[i], idNext = tempPath[i-1];
        value += V[id][idNext];//value增加边id->idNext的边权
    }
    
    //点权之和
    int value = 0;
    for(int i = tempPath.size()-1; i >= 0; i--){
        int id = tempPath[i];
        value += W[id];
    }
    
    作者:睿晞
    身处这个阶段的时候,一定要好好珍惜,这是我们唯一能做的,求学,钻研,为人,处事,交友……无一不是如此。
    劝君莫惜金缕衣,劝君惜取少年时。花开堪折直须折,莫待无花空折枝。
    曾有一个业界大牛说过这样一段话,送给大家:   “华人在计算机视觉领域的研究水平越来越高,这是非常振奋人心的事。我们中国错过了工业革命,错过了电气革命,信息革命也只是跟随状态。但人工智能的革命,我们跟世界上的领先国家是并肩往前跑的。能身处这个时代浪潮之中,做一番伟大的事业,经常激动的夜不能寐。”
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    灰度直方算法 C++
    三国谋士智商前20名
    get the runing time of C++ console program.
    支持向量机 support vector machine
    Typical sentences in SCI papers
    C++调用GDAL库读取并输出tif文件,并计算斑块面积输出景观指数:CSD
    通过管理工具对服务器进行远程管理
    Connected_Component Labelling(联通区域标记算法) C++实现
    Fragstats景观分析研究
    Install GDAL in OpenSUSE 12.3 Linux
  • 原文地址:https://www.cnblogs.com/tsruixi/p/12380430.html
Copyright © 2011-2022 走看看