有权图——最小生成树、最短路径

有权图

有权图的表示

  • 在这里插入图片描述

  • 邻接矩阵表示有权图:

  • 在这里插入图片描述

  • 邻接表表示有权图:

  • 在这里插入图片描述

  • 边的表示:

  • // 边
    template<typename Weight>
    class Edge{
    private:
        int a,b;    // 边的两个端点
        Weight weight;  // 边的权值
    
    public:
        // 构造函数
        Edge(int a, int b, Weight weight){
            this->a = a;
            this->b = b;
            this->weight = weight;
        }
        // 空的构造函数, 所有的成员变量都取默认值
        Edge(){}
    
        ~Edge(){}
    
        int v(){ return a;} // 返回第一个顶点
        int w(){ return b;} // 返回第二个顶点
        Weight wt(){ return weight;}    // 返回权值
    
        // 给定一个顶点, 返回另一个顶点
        int other(int x){
            assert( x == a || x == b );
            return x == a ? b : a;
        }
    
        // 输出边的信息
        friend ostream& operator<<(ostream &os, const Edge &e){
            os<<e.a<<"-"<<e.b<<": "<<e.weight;
            return os;
        }
    
        // 边的大小比较, 是对边的权值的大小比较
        bool operator<(Edge<Weight>& e){
            return weight < e.wt();
        }
        bool operator<=(Edge<Weight>& e){
            return weight <= e.wt();
        }
        bool operator>(Edge<Weight>& e){
            return weight > e.wt();
        }
        bool operator>=(Edge<Weight>& e){
            return weight >= e.wt();
        }
        bool operator==(Edge<Weight>& e){
            return weight == e.wt();
        }
    };
    
  • 邻接矩阵和邻接矩阵代码表示有权图:

  • // 稠密图 - 邻接矩阵
    template <typename Weight>
    class DenseGraph{
    
    private:
        int n, m;       // 节点数和边数
        bool directed;  // 是否为有向图
        vector<vector<Edge<Weight> *>> g;   // 图的具体数据
    
    public:
        // 构造函数
        DenseGraph( int n , bool directed){
            assert( n >= 0 );
            this->n = n;
            this->m = 0;
            this->directed = directed;
            // g初始化为n*n的矩阵, 每一个g[i][j]指向一个边的信息, 初始化为NULL
            g = vector<vector<Edge<Weight> *>>(n, vector<Edge<Weight> *>(n, NULL));
        }
    
        // 析构函数
        ~DenseGraph(){
    
            for( int i = 0 ; i < n ; i ++ )
                for( int j = 0 ; j < n ; j ++ )
                    if( g[i][j] != NULL )
                        delete g[i][j];
        }
    
        int V(){ return n;} // 返回节点个数
        int E(){ return m;} // 返回边的个数
    
        // 向图中添加一个边, 权值为weight
        void addEdge( int v, int w , Weight weight ){
            assert( v >= 0 && v < n );
            assert( w >= 0 && w < n );
    
            // 如果从v到w已经有边, 删除这条边
            if( hasEdge( v , w  ) ){
                delete  g[v][w];
                if( v != w && !directed )
                    delete g[w][v];
                m --;
            }
    
            g[v][w] = new Edge<Weight>(v, w, weight);
            if( v != w && !directed )
                g[w][v] = new Edge<Weight>(w, v, weight);
            m ++;
        }
    
        // 验证图中是否有从v到w的边
        bool hasEdge( int v , int w ){
            assert( v >= 0 && v < n );
            assert( w >= 0 && w < n );
            return g[v][w] != NULL;
        }
        
        // 邻边迭代器, 传入一个图和一个顶点,
        // 迭代在这个图中和这个顶点向连的所有边
        class adjIterator{
        private:
            DenseGraph &G;  // 图G的引用
            int v;
            int index;
    
        public:
            // 构造函数
            adjIterator(DenseGraph &graph, int v): G(graph){
                this->v = v;
                this->index = -1;   // 索引从-1开始, 因为每次遍历都需要调用一次next()
            }
    
            ~adjIterator(){}
    
            // 返回图G中与顶点v相连接的第一个边
            Edge<Weight>* begin(){
                // 索引从-1开始, 因为每次遍历都需要调用一次next()
                index = -1;
                return next();
            }
    
            // 返回图G中与顶点v相连接的下一个边
            Edge<Weight>* next(){
                // 从当前index开始向后搜索, 直到找到一个g[v][index]为true
                for( index += 1 ; index < G.V() ; index ++ )
                    if( G.g[v][index] )
                        return G.g[v][index];
                // 若没有顶点和v相连接, 则返回NULL
                return NULL;
            }
    
            // 查看是否已经迭代完了图G中与顶点v相连接的所有边
            bool end(){
                return index >= G.V();
            }
        };
    };
    
    // 稀疏图 - 邻接表
    template<typename Weight>
    class SparseGraph{
    
    private:
        int n, m;       // 节点数和边数
        bool directed;  // 是否为有向图
        vector<vector<Edge<Weight> *> > g;   // 图的具体数据
    
    public:
        // 构造函数
        SparseGraph( int n , bool directed){
            assert(n >= 0);
            this->n = n;
            this->m = 0;    // 初始化没有任何边
            this->directed = directed;
            // g初始化为n个空的vector, 表示每一个g[i]都为空, 即没有任和边
            g = vector<vector<Edge<Weight> *> >(n, vector<Edge<Weight> *>());
        }
    
        // 析构函数
        ~SparseGraph(){
            for( int i = 0 ; i < n ; i ++ )
                for( int j = 0 ; j < g[i].size() ; j ++ )
                    delete g[i][j];
        }
    
        int V(){ return n;} // 返回节点个数
        int E(){ return m;} // 返回边的个数
    
        // 向图中添加一个边, 权值为weight
        void addEdge( int v, int w , Weight weight){
            assert( v >= 0 && v < n );
            assert( w >= 0 && w < n );
    
            // 注意, 由于在邻接表的情况, 查找是否有重边需要遍历整个链表
            // 我们的程序允许重边的出现
    
            g[v].push_back(new Edge<Weight>(v, w, weight));
            if( v != w && !directed )
                g[w].push_back(new Edge<Weight>(w, v, weight));
            m ++;
        }
    
        // 验证图中是否有从v到w的边
        bool hasEdge( int v , int w ){
            assert( v >= 0 && v < n );
            assert( w >= 0 && w < n );
            for( int i = 0 ; i < g[v].size() ; i ++ )
                if( g[v][i]->other(v) == w )
                    return true;
            return false;
        }
    
        // 邻边迭代器, 传入一个图和一个顶点,
        // 迭代在这个图中和这个顶点向连的所有边
        class adjIterator{
        private:
            SparseGraph &G; // 图G的引用
            int v;
            int index;
    
        public:
            // 构造函数
            adjIterator(SparseGraph &graph, int v): G(graph){
                this->v = v;
                this->index = 0;
            }
    
            ~adjIterator(){}
    
            // 返回图G中与顶点v相连接的第一个边
            Edge<Weight>* begin(){
                index = 0;
                if( G.g[v].size() )
                    return G.g[v][index];
                // 若没有顶点和v相连接, 则返回NULL
                return NULL;
            }
    
            // 返回图G中与顶点v相连接的下一个边
            Edge<Weight>* next(){
                index += 1;
                if( index < G.g[v].size() )
                    return G.g[v][index];
                return NULL;
            }
    
            // 查看是否已经迭代完了图G中与顶点v相连接的所有顶点
            bool end(){
                return index >= G.g[v].size();
            }
        };
    };
    

最小生成树Minimum Span Tree

  • 在这里插入图片描述
生成树
  • 在一个有V个节点的图中,存在V-1条边连接了这V个节点,则说这V-1条边组成了这个图的生成树

  • 生成树满足2个条件:

    • 包含树中的所有节点
    • 任意两节点之间仅有一条边
  • 所有边的权值和最小的生成树称为最小生成树

切分定理Cut Property
  • 在这里插入图片描述

  • 把图中的节点分为两部分,称为一个切分

  • 如果一个边的两个端点,属于切分不同的两边,这个边称为横切边

  • 切分定理:给定任意切分,横切边中权值最小的边必然属于最小生成树

  • 上图中,蓝绿色的边为横切边(4-6的边也是),其中红色的边权值最小,属于最小生成树

Lazy Prim
  • 算法步骤:

    • 1.从起始节点s开始,将他的所有邻边加入到最小堆(优先队列)Q中
    • 2.从Q中出队权值最小的边,通过该边的两个端点判断其是否为横切边
    • 3.若是,则加入到MST中,遍历与该边相连的另一个节点回到步骤1;否则抛弃之,回到步骤2
  • 时间复杂度:O(ElogE),通过优化可以降低到O(ElogV)

  • // 使用Prim算法求图的最小生成树
    template<typename Graph, typename Weight>
    class LazyPrimMST{
    
    private:
        Graph &G;                   // 图的引用
        MinHeap<Edge<Weight>> pq;   // 最小堆, 算法辅助数据结构
        bool *marked;               // 标记数组, 在算法运行过程中标记节点i是否被访问
        vector<Edge<Weight>> mst;   // 最小生成树所包含的所有边
        Weight mstWeight;           // 最小生成树的权值
    
        // 访问节点v
        void visit(int v){
    
            assert( !marked[v] );
            marked[v] = true;
    
            // 将和节点v相连接的所有未访问的边放入最小堆中
            typename Graph::adjIterator adj(G,v);
            for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() )
                if( !marked[e->other(v)] )
                    pq.insert(*e);
        }
    
    public:
        // 构造函数, 使用Prim算法求图的最小生成树
        LazyPrimMST(Graph &graph):G(graph), pq(MinHeap<Edge<Weight>>(graph.E())){
    
            // 算法初始化
            marked = new bool[G.V()];
            for( int i = 0 ; i < G.V() ; i ++ )
                marked[i] = false;
            mst.clear();
    
            // Lazy Prim
            visit(0);
            while( !pq.isEmpty() ){// O(E)
                // 使用最小堆找出已经访问的边中权值最小的边
                Edge<Weight> e = pq.extractMin();// O(logE)
                // 如果这条边的两端都已经访问过了, 则扔掉这条边
                if( marked[e.v()] == marked[e.w()] )
                    continue;
                // 否则, 这条边则应该存在在最小生成树中
                mst.push_back( e );
    
                // 访问和这条边连接的还没有被访问过的节点
                if( !marked[e.v()] )
                    visit( e.v() );// O(E)
                else
                    visit( e.w() );
            }
    
            // 计算最小生成树的权值
            mstWeight = mst[0].wt();
            for( int i = 1 ; i < mst.size() ; i ++ )
                mstWeight += mst[i].wt();
        }
    
        // 析构函数
        ~LazyPrimMST(){
            delete[] marked;
        }
    
        // 返回最小生成树的所有边
        vector<Edge<Weight>> mstEdges(){
            return mst;
        };
    
        // 返回最小生成树的权值
        Weight result(){
            return mstWeight;
        };
    };
    
Prim
  • Lazy Prim算法存在的问题:

    • 将图中所有的边都加入了堆中,而很多边已经不再是横切边
    • 虽然有很多横切边,但是通常只需考虑最短的横切边,尤其是和一个节点相连的许多横切边中最短的一个
  • 时间复杂度:O(ElogV),对于稠密图改进效果明显

  • 使用辅助数据结构:索引堆。该索引堆中只需存储V个节点,每个节点存储和该节点相连的最短的横切边

  • 算法步骤:

    • 1.从起始节点开始,遍历该节点的邻边,并更新与这些边像连的另一些节点存储的最小的横切边

    • 2.选择其中最短的横切边加入到MST中,并从该边的另一个节点开始回到步骤1

    • 在这里插入图片描述

    • 如上图所示,从节点0开始,遍历其邻边,并更新与之相连的节点(2、4、6、7)在索引堆中存储的最短横切边(分别为0.26、0.38、0.58、0.16);选择其中最短的边0.16加入到MST中,并从节点7开始继续上述步骤。

  • // 使用优化的Prim算法求图的最小生成树
    template<typename Graph, typename Weight>
    class PrimMST{
    
    private:
        Graph &G;                     // 图的引用
        IndexMinHeap<Weight> ipq;     // 最小索引堆, 算法辅助数据结构
        vector<Edge<Weight>*> edgeTo; // 访问的点所对应的边, 算法辅助数据结构(索引堆中只存储了每个节点的最小横切边权值,没有边相连的节点信息)
        bool* marked;                 // 标记数组, 在算法运行过程中标记节点i是否被访问
        vector<Edge<Weight>> mst;     // 最小生成树所包含的所有边
        Weight mstWeight;             // 最小生成树的权值
    
        // 访问节点v
        void visit(int v){
    
            assert( !marked[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);
                // 如果边的另一端点未被访问
                if( !marked[w] ){
                    // 如果从没有考虑过这个端点, 直接将这个端点和与之相连接的边加入索引堆
                    if( !edgeTo[w] ){
                        edgeTo[w] = e;
                        ipq.insert(w, e->wt());
                    }
                    // 如果曾经考虑这个端点, 但现在的边比之前考虑的边更短, 则进行替换
                    else if( e->wt() < edgeTo[w]->wt() ){
                        edgeTo[w] = e;
                        ipq.change(w, e->wt());
                    }
                }
            }
    
        }
    public:
        // 构造函数, 使用Prim算法求图的最小生成树
        PrimMST(Graph &graph):G(graph), ipq(IndexMinHeap<double>(graph.V())){
    
            assert( graph.E() >= 1 );
    
            // 算法初始化
            marked = new bool[G.V()];
            for( int i = 0 ; i < G.V() ; i ++ ){
                marked[i] = false;
                edgeTo.push_back(NULL);
            }
            mst.clear();
    
            // Prim
            visit(0);
            while( !ipq.isEmpty() ){
                // 使用最小索引堆找出已经访问的边中权值最小的边
                // 最小索引堆中存储的是点的索引, 通过点的索引找到相对应的边
                int v = ipq.extractMinIndex();
                assert( edgeTo[v] );
                mst.push_back( *edgeTo[v] );
                visit( v );
            }
    
            mstWeight = mst[0].wt();
            for( int i = 1 ; i < mst.size() ; i ++ )
                mstWeight += mst[i].wt();
        }
    
        ~PrimMST(){
            delete[] marked;
        }
    
        vector<Edge<Weight>> mstEdges(){
            return mst;
        };
    
        Weight result(){
            return mstWeight;
        };
    };
    
Kruskal
  • 算法思想:选择图G的所有边E中权值最小的边,判断其是否能在MST中形成环(注意这里并不是判断其是否为横切边),若是,舍弃之并选择下一条权值最小的边;否则,将其加入到MST中,直到所有的节点都在MST中为止

  • 在这里插入图片描述

  • 如上图所示,0-2这条边虽然不是横切边,但是其并不会使得MST形成环,因此需加入到MST中

  • 在这里插入图片描述

  • 而1-3这条边若加入到MST中的话,则会在MST中形成环,因此舍弃之

  • 若要判断是否能形成环,则需要并查集这种数据结构。每当将一条边加入到MST中时,都要对两端的端点进行一次union操作,这样当对下一条边进行判断的时候,就可以通过判断这条边两端的端点是否已经联通,进而可知是否能形成环

  • 时间复杂度:O(ElogE)

  • // Kruskal算法
    template <typename Graph, typename Weight>
    class KruskalMST{
    
    private:
        vector<Edge<Weight>> mst;   // 最小生成树所包含的所有边
        Weight mstWeight;           // 最小生成树的权值
    
    public:
        // 构造函数, 使用Kruskal算法计算graph的最小生成树
        KruskalMST(Graph &graph){
    
            // 将图中的所有边存放到一个最小堆中
            MinHeap<Edge<Weight>> pq( graph.E() );
            for( int i = 0 ; i < graph.V() ; i ++ ){
                typename Graph::adjIterator adj(graph,i);
                for( Edge<Weight> *e = adj.begin() ; !adj.end() ; e = adj.next() )
                    if( e->v() < e->w() )
                        pq.insert(*e);
            }
    
            // 创建一个并查集, 来查看已经访问的节点的联通情况
            UnionFind uf = UnionFind(graph.V());
            while( !pq.isEmpty() && mst.size() < graph.V() - 1 ){
    
                // 从最小堆中依次从小到大取出所有的边
                Edge<Weight> e = pq.extractMin();
                // 如果该边的两个端点是联通的, 说明加入这条边将产生环, 扔掉这条边
                if( uf.isConnected( e.v() , e.w() ) )
                    continue;
    
                // 否则, 将这条边添加进最小生成树, 同时标记边的两个端点联通
                mst.push_back( e );
                uf.unionElements( e.v() , e.w() );
            }
    
            mstWeight = mst[0].wt();
            for( int i = 1 ; i < mst.size() ; i ++ )
                mstWeight += mst[i].wt();
        }
    
        ~KruskalMST(){ }
    
        // 返回最小生成树的所有边
        vector<Edge<Weight>> mstEdges(){
            return mst;
        };
    
        // 返回最小生成树的权值
        Weight result(){
            return mstWeight;
        };
    };
    

最短路径

  • 所生成的路径称为单源最短路径,形成了一棵最短路径树
松弛操作Relaxation
  • 在这里插入图片描述

  • 查看从0先到其他节点(比如2)再到1的距离是否比从0直接到1的距离要短

  • 松弛操作是最短路径求解的核心

Dijkstra算法
  • 前提:图中不能有负权边

  • 时间复杂度:O(ElogV)

  • 借助最小索引堆数据结构

  • 算法步骤:

    • 每次循环从未求出最短路劲的节点中,找出距离起点最短路径的节点Oi
    • 判断从原点出发经过Oi到达另一个节点Oj的路径长度,是否小于之前到达Oj的路径长度,若小于则更新Oj对应的最短路径(松弛操作),否则继续遍历Oi所能到达的下一个节点
  • // 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();
            }
        }
    };
    
Bellman-Ford算法
  • Dijkstra算法无法处理存在负权边的图的最短路径问题

  • 当图中存在负权环的时候,则不存在最短路径

  • 在这里插入图片描述

  • 如上图,0->2->0代价为-1,1->2->1代价为-3,0->1->2->0的代价为-2,这些都是负权环。若要找0到任何一个节点的最短路径,则可以在这个环中不断循环使得路径的代价逐渐减小,直到负无穷。

  • Bellman-Ford算法的前提是图中不能有负权环,但可以判断图中是否有负权环;通常处理有向图

  • 时间复杂度为O(EV)

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

  • 主要步骤:

    • 对所有的点进行V-1轮松弛操作,在第n轮松弛操作后,得到的是从原点最多经过n条边到达其他顶点的最短距离;此时,正常情况下能够找到从原点到其余顶点的最短路径
    • 最后对所有的点进行第V轮的松弛操作,若还能找到最短路径,则说明图中有负权环
  • 可以使用队列进行优化

  • // 使用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;
            }
        }
    };
    
最短路径算法总结
  • image-20210406154901017
  • 所有对的最短路径算法:Floyed算法,处理无负权环的图,O(V^3)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值