图论 | 最短路径——dijkstra算法、Bellman-Ford单源最短路径算法_青春猪头少年_的博客-程序员宅基地

dijkstra单源最短路径算法

前提:图中不能有负权边
因为存在负权环的话就不存在最短路径

复杂度 O(ElogV)

// Dijkstra算法求最短路径
template<typename Graph, typename Weight>
class Dijkstra{

private:
    Graph &G;                   // 图的引用
    int s;                      // 起始点
    Weight *distTo;             // distTo[i]存储从起始点s到i的最短路径长度
    bool *marked;               // 标记数组, 在算法运行过程中标记节点i是否被访问
    vector<Edge<Weight>*> from; // from[i]记录最短路径中, 到达i点的边是哪一条
                                // 可以用来恢复整个最短路径

public:
    // 构造函数, 使用Dijkstra算法求最短路径       核心代码
    Dijkstra(Graph &graph, int s):G(graph){

        // 算法初始化
        assert( s >= 0 && s < G.V() );
        this->s = s;
        distTo = new Weight[G.V()];
        marked = new bool[G.V()];
        for( int i = 0 ; i < G.V() ; i ++ ){
            distTo[i] = Weight();
            marked[i] = false;
            from.push_back(NULL);
        }

        // 使用索引堆记录当前找到的到达每个顶点的最短距离
        IndexMinHeap<Weight> ipq(G.V());

        // 对于其实点s进行初始化
        distTo[s] = Weight();
        from[s] = new Edge<Weight>(s, s, Weight());
        ipq.insert(s, distTo[s] );
        marked[s] = true;
        while( !ipq.isEmpty() ){
            int v = ipq.extractMinIndex();

            // distTo[v]就是s到v的最短距离
            marked[v] = true;

            // 对v的所有相邻节点进行更新
            typename Graph::adjIterator adj(G, v);
            for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ){
                int w = e->other(v);
                // 如果从s点到w点的最短路径还没有找到
                if( !marked[w] ){
                    // 如果w点以前没有访问过,
                    // 或者访问过, 但是通过当前的v点到w点距离更短, 则进行更新
                    if( from[w] == NULL || distTo[v] + e->wt() < distTo[w] ){
                        distTo[w] = distTo[v] + e->wt();
                        from[w] = e;
                        if( ipq.contain(w) )
                            ipq.change(w, distTo[w] );
                        else
                            ipq.insert(w, distTo[w] );
                    }
                }
            }
        }
    }

    // 析构函数
    ~Dijkstra(){
        delete[] distTo;
        delete[] marked;
        delete from[0];
    }

    // 返回从s点到w点的最短路径长度
    Weight shortestPathTo( int w ){
        assert( w >= 0 && w < G.V() );
        assert( hasPathTo(w) );
        return distTo[w];
    }

    // 判断从s点到w点是否联通
    bool hasPathTo( int w ){
        assert( w >= 0 && w < G.V() );
        return marked[w];
    }

    // 寻找从s到w的最短路径, 将整个路径经过的边存放在vec中
    void shortestPath( int w, vector<Edge<Weight>> &vec ){

        assert( w >= 0 && w < G.V() );
        assert( hasPathTo(w) );

        // 通过from数组逆向查找到从s到w的路径, 存放到栈中
        stack<Edge<Weight>*> s;
        Edge<Weight> *e = from[w];
        while( e->v() != this->s ){
            s.push(e);
            e = from[e->v()];
        }
        s.push(e);

        // 从栈中依次取出元素, 获得顺序的从s到w的路径
        while( !s.empty() ){
            e = s.top();
            vec.push_back( *e );
            s.pop();
        }
    }

    // 打印出从s点到w点的路径
    void showPath(int w){

        assert( w >= 0 && w < G.V() );
        assert( hasPathTo(w) );

        vector<Edge<Weight>> vec;
        shortestPath(w, vec);
        for( int i = 0 ; i < vec.size() ; i ++ ){
            cout<<vec[i].v()<<" -> ";
            if( i == vec.size()-1 )
                cout<<vec[i].w()<<endl;
        }
    }
};

Bellman-Ford单源最短路径算法

前提:不能有负权环

Bellman-Ford可以判断图中是否有负权环

复杂度O(EV)

如果一个图没有负权环,从一点到另外一点的最短路径,最多经过所有的V个顶点,有V-1条边
否则存在顶点经过了两次,即存在负权环

// 使用BellmanFord算法求最短路径
template <typename Graph, typename Weight>
class BellmanFord{

private:
    Graph &G;                   // 图的引用
    int s;                      // 起始点
    Weight* distTo;             // distTo[i]存储从起始点s到i的最短路径长度
    vector<Edge<Weight>*> from; // from[i]记录最短路径中, 到达i点的边是哪一条
                                // 可以用来恢复整个最短路径
    bool hasNegativeCycle;      // 标记图中是否有负权环

    // 判断图中是否有负权环
    bool detectNegativeCycle(){

        for( int i = 0 ; i < G.V() ; i ++ ){
            typename Graph::adjIterator adj(G,i);
            for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() )
                if( from[e->v()] && distTo[e->v()] + e->wt() < distTo[e->w()] )
                    return true;
        }

        return false;
    }

public:
    // 构造函数, 使用BellmanFord算法求最短路径
    BellmanFord(Graph &graph, int s):G(graph){

        this->s = s;
        distTo = new Weight[G.V()];
        // 初始化所有的节点s都不可达, 由from数组来表示
        for( int i = 0 ; i < G.V() ; i ++ )
            from.push_back(NULL);

        // 设置distTo[s] = 0, 并且让from[s]不为NULL, 表示初始s节点可达且距离为0
        distTo[s] = Weight();
        from[s] = new Edge<Weight>(s, s, Weight()); // 这里我们from[s]的内容是new出来的, 注意要在析构函数里delete掉

        // Bellman-Ford的过程
        // 进行V-1次循环, 每一次循环求出从起点到其余所有点, 最多使用pass步可到达的最短距离
        for( int pass = 1 ; pass < G.V() ; pass ++ ){

            // 每次循环中对所有的边进行一遍松弛操作
            // 遍历所有边的方式是先遍历所有的顶点, 然后遍历和所有顶点相邻的所有边
            for( int i = 0 ; i < G.V() ; i ++ ){
                // 使用我们实现的邻边迭代器遍历和所有顶点相邻的所有边
                typename Graph::adjIterator adj(G,i);
                for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() )
                    // 对于每一个边首先判断e->v()可达
                    // 之后看如果e->w()以前没有到达过, 显然我们可以更新distTo[e->w()]
                    // 或者e->w()以前虽然到达过, 但是通过这个e我们可以获得一个更短的距离, 即可以进行一次松弛操作, 我们也可以更新distTo[e->w()]
                    if( from[e->v()] && (!from[e->w()] || distTo[e->v()] + e->wt() < distTo[e->w()]) ){
                        distTo[e->w()] = distTo[e->v()] + e->wt();
                        from[e->w()] = e;
                    }
            }
        }

        hasNegativeCycle = detectNegativeCycle();
    }

    // 析构函数
    ~BellmanFord(){

        delete[] distTo;
        delete from[s];
    }

    // 返回图中是否有负权环
    bool negativeCycle(){
        return hasNegativeCycle;
    }

    // 返回从s点到w点的最短路径长度
    Weight shortestPathTo( int w ){
        assert( w >= 0 && w < G.V() );
        assert( !hasNegativeCycle );
        assert( hasPathTo(w) );
        return distTo[w];
    }

    // 判断从s点到w点是否联通
    bool hasPathTo( int w ){
        assert( w >= 0 && w < G.V() );
        return from[w] != NULL;
    }

    // 寻找从s到w的最短路径, 将整个路径经过的边存放在vec中
    void shortestPath( int w, vector<Edge<Weight>> &vec ){

        assert( w >= 0 && w < G.V() );
        assert( !hasNegativeCycle );
        assert( hasPathTo(w) );

        // 通过from数组逆向查找到从s到w的路径, 存放到栈中
        stack<Edge<Weight>*> s;
        Edge<Weight> *e = from[w];
        while( e->v() != this->s ){
            s.push(e);
            e = from[e->v()];
        }
        s.push(e);

        // 从栈中依次取出元素, 获得顺序的从s到w的路径
        while( !s.empty() ){
            e = s.top();
            vec.push_back( *e );
            s.pop();
        }
    }

    // 打印出从s点到w点的路径
    void showPath(int w){

        assert( w >= 0 && w < G.V() );
        assert( !hasNegativeCycle );
        assert( hasPathTo(w) );

        vector<Edge<Weight>> vec;
        shortestPath(w, vec);
        for( int i = 0 ; i < vec.size() ; i ++ ){
            cout<<vec[i].v()<<" -> ";
            if( i == vec.size()-1 )
                cout<<vec[i].w()<<endl;
        }
    }
};

SPFA(Shortest Path Faster Algorithm)是Bellman-Ford算法的一种队列优化,减少了不必要的冗余计算。

20988794-c0fbe2b9a50e66d9.png

另外Floyed算法可以求出无负权环的最短路径
复杂度O(V^3)

关于最长路径:

20988794-881e9093bb819841.png
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41017788/article/details/105266019

智能推荐

移动端三种适配方案_weixin_33698043的博客-程序员宅基地

1.meta-viewport适配背景viewport:中文的意思就是“视口”,通常就是浏览器用于显示页面的区域,但是在移动端,viewport一般都要大于浏览器的可视区域,保证PC页面在移动页面上面的可视性,这是因为考虑到移动设备的分辨率相对于桌面电脑来说都比较小,所以为了能在移动设备上正常显示那些传统的为桌面浏览器设计的网站,移动设备上的浏览器都会把自己默认的viewport设为980px...

数据库笛卡尔积运算_weixin_33716557的博客-程序员宅基地

笛卡尔积老是忘记怎么运算,R*S 即R的每一行和S的每一行连接。转载于:https://www.cnblogs.com/starxingyun/p/3893092.html

《Android 源码设计模式解析与实战》——第2章,第2.1节单例模式介绍_weixin_33929309的博客-程序员宅基地

本节书摘来自异步社区《Android 源码设计模式解析与实战》一书中的第2章,第2.1节单例模式介绍,作者 何红辉 , 关爱民,更多章节内容可以访问云栖社区“异步社区”公众号查看第2章 应用最广的模式——单例模式Android 源码设计模式解析与实战2.1 单例模式介绍单例模式是应用最广的模式之一,也可能是很多初级工程师唯一会使用的设计模式。在应用...

Mac 终端下 实现 安装 ipa 包到 iPhone 真机_技术小黑屋_的博客-程序员宅基地

最近处理 Flutter 的开发工作,开始尝试使用 iOS 作为日常的真机调试工作。对于一个原技术栈为 Android的人来说,发现 iOS 有很多不太方便的地方。比如如何在 Mac 电脑...

cacti安装与配置_cacti 安装 apache 配置_ygmdream的博客-程序员宅基地

一、准备所需要的软件包Apache     http://www.apache.org/Mysql      http://www.mysql.com/Php        http://www.php.netRrdtool    http://oss.oetiker.ch/rrdtool/Net-snmp   http://www.net-snmp.org

Promise返回值问题(处理异步请求返回值问题)_promise 返回数据混乱_Spearmint_的博客-程序员宅基地

因为项目中我们的数据请求全都是异步的在开发中遇到的问题:本意将数据请求封装成一个方法,并return数据请求结果给变量如:let getData = () =&gt;{ //service.getList 为数据请求方法名 service.getList().then(function(rs){ return rs })}var da...

随便推点

关于Android程序的java.lang.RuntimeException: Unable to instantiate activity ComponentInfo错误_weixin_34018202的博客-程序员宅基地

今天写Android程序,遇到了java.lang.RuntimeException: Unable to instantiate activity ComponentInfo这个错误,原来是我的AndroidManifest.xml文件配置有误。以下是我本来的配置文件,我改过包名的。原来错误是在android:name=".LogoActivity",因为我的LogoActivity.java是...

事件分发机制面试题,Android组件化架构实践,隔壁都馋哭了_android 组件化模块间 事件分发_Java老猴子的博客-程序员宅基地

开头在Android开发当中,相信大家对第三方库的重要性是无需多说的,尤其是三方库源码更是重中之重,而EventBus源码就属于其中的一个重点。EventBus是安卓(Java中也可以用)开发中非常流行的一个第三方库,是一种发布/订阅事件的总线.想必每个入了门的Android开发者都多少对EventBus有过了解,EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递。EventBus使用简单,并将事件发布和订阅充分解耦,从而使代码更简洁。一直以来

float和double 精度分析_mfc float与double_giantmfc123的博客-程序员宅基地

结论1.)范围float和double的范围是由指数的位数来决定的。float的指数位有8位,而double的指数位有11位,分布如下:float:1bit(符号位) 8bits(指数位) 23bits(尾数位)double:1bit(符号位) 11bits(指数位) 52bits(尾数位)于是,float的指数范围为-127+128,而double的指数范围为-1023+10...

实战node静态文件服务器_AirZH??的博客-程序员宅基地

支持功能:读取静态文件访问目录可以自动寻找下面的index.html文件, 如果没有index.html则列出文件列表MIME类型支持缓存支持/控制支持gzip压缩Range支持,断点续传全局命令执行子进程运行 1. 创建服务读取静态文件首先引入http模块,创建一个服务器,并监听配置端口: const http = require('http'); const ...

Spring WebFlux + React搭建后台管理系统(7): 实现excel文件上传下载_spring webflux 上传excel_泛泛之素的博客-程序员宅基地

后台管理系统的excel导出功能,以及使用excel进行批量导入都是必不可少的功能,本篇主要介绍内容如下:java后端 excel的读写excel类型判断以及通过反射适配class后端接收upload服务逻辑实现后端download服务逻辑前端upload组建使用前端download配置先上效果图:1. 读取Excel文件1.1 添加依赖通过使用poi进行excel文件的解析:implementation 'org.apache.poi:poi:4.0.1'implementa

HttpClient的cookie_cjz_huateng的博客-程序员宅基地

package com.huawe;import java.io.IOException;import java.util.List;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.client.ClientProtocolException

推荐文章

热门文章

相关标签