圖論 | 最短路徑——dijkstra算法、Bellman-Ford單源最短路徑算法

dijkstra單源最短路徑算法

前提:圖中不能有負權(quán)邊
因為存在負權(quán)環(huán)的話就不存在最短路徑

復(fù)雜度 O(ElogV)

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

private:
    Graph &G;                   // 圖的引用
    int s;                      // 起始點
    Weight *distTo;             // distTo[i]存儲從起始點s到i的最短路徑長度
    bool *marked;               // 標記數(shù)組, 在算法運行過程中標記節(jié)點i是否被訪問
    vector<Edge<Weight>*> from; // from[i]記錄最短路徑中, 到達i點的邊是哪一條
                                // 可以用來恢復(fù)整個最短路徑

public:
    // 構(gòu)造函數(shù), 使用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的所有相鄰節(jié)點進行更新
            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] );
                    }
                }
            }
        }
    }

    // 析構(gòu)函數(shù)
    ~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點是否聯(lián)通
    bool hasPathTo( int w ){
        assert( w >= 0 && w < G.V() );
        return marked[w];
    }

    // 尋找從s到w的最短路徑, 將整個路徑經(jīng)過的邊存放在vec中
    void shortestPath( int w, vector<Edge<Weight>> &vec ){

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

        // 通過from數(shù)組逆向查找到從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單源最短路徑算法

前提:不能有負權(quán)環(huán)

Bellman-Ford可以判斷圖中是否有負權(quán)環(huán)

復(fù)雜度O(EV)

如果一個圖沒有負權(quán)環(huán),從一點到另外一點的最短路徑,最多經(jīng)過所有的V個頂點,有V-1條邊
否則存在頂點經(jīng)過了兩次,即存在負權(quán)環(huán)

// 使用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點的邊是哪一條
                                // 可以用來恢復(fù)整個最短路徑
    bool hasNegativeCycle;      // 標記圖中是否有負權(quán)環(huán)

    // 判斷圖中是否有負權(quán)環(huán)
    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:
    // 構(gòu)造函數(shù), 使用BellmanFord算法求最短路徑
    BellmanFord(Graph &graph, int s):G(graph){

        this->s = s;
        distTo = new Weight[G.V()];
        // 初始化所有的節(jié)點s都不可達, 由from數(shù)組來表示
        for( int i = 0 ; i < G.V() ; i ++ )
            from.push_back(NULL);

        // 設(shè)置distTo[s] = 0, 并且讓from[s]不為NULL, 表示初始s節(jié)點可達且距離為0
        distTo[s] = Weight();
        from[s] = new Edge<Weight>(s, s, Weight()); // 這里我們from[s]的內(nèi)容是new出來的, 注意要在析構(gòu)函數(shù)里delete掉

        // Bellman-Ford的過程
        // 進行V-1次循環(huán), 每一次循環(huán)求出從起點到其余所有點, 最多使用pass步可到達的最短距離
        for( int pass = 1 ; pass < G.V() ; pass ++ ){

            // 每次循環(huán)中對所有的邊進行一遍松弛操作
            // 遍歷所有邊的方式是先遍歷所有的頂點, 然后遍歷和所有頂點相鄰的所有邊
            for( int i = 0 ; i < G.V() ; i ++ ){
                // 使用我們實現(xiàn)的鄰邊迭代器遍歷和所有頂點相鄰的所有邊
                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();
    }

    // 析構(gòu)函數(shù)
    ~BellmanFord(){

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

    // 返回圖中是否有負權(quán)環(huán)
    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點是否聯(lián)通
    bool hasPathTo( int w ){
        assert( w >= 0 && w < G.V() );
        return from[w] != NULL;
    }

    // 尋找從s到w的最短路徑, 將整個路徑經(jīng)過的邊存放在vec中
    void shortestPath( int w, vector<Edge<Weight>> &vec ){

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

        // 通過from數(shù)組逆向查找到從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算法的一種隊列優(yōu)化,減少了不必要的冗余計算。

另外Floyed算法可以求出無負權(quán)環(huán)的最短路徑
復(fù)雜度O(V^3)

關(guān)于最長路徑:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容