算法與數據結構之-圖的最短路徑

主要介紹最短路徑問題和鬆弛操做、Dijkstra算法的思想、負權邊和Bellman-Ford算法等算法

最短路徑問題和鬆弛操做

一個節點到另外一個節點最短的路徑,路徑規劃問題。數組

  • 路徑規劃(費用、長度)
  • 工做任務規劃

對於無權圖進行廣度優先遍歷就是求出了一個最短路徑(求出的是一顆最短路徑樹)微信

  • 從起始點到其餘節點路徑最短的樹數據結構

  • 無權圖的最短路徑。函數

  • 有權最短路徑

鬆弛操做

鬆弛操做是指對於每一個頂點v∈V,都設置一個屬性d[v],用來描述從源點s到v的最短路徑上權值的上界,稱爲最短路徑估計(shortest-path estimate)。測試

  • 鬆弛操做,找到更短路徑,鬆弛操做是最短路徑求解的核心。

dijkstra 單源最短路徑算法

侷限性

  • 前提:圖中不能有負權邊
  • 複雜度 O( E log(V) )同最小生成樹

最小索引堆this

  • 從源點能到達的點中最短的路徑。
  • 圖中不能有負權邊

鬆弛操做spa

paste image

  • 通過2到達1和通過2到達3比原來記錄的值小,因此鬆弛更新。

paste image

  • 通過1到4更短

代碼實現

// 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, 0);
        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] );//最小索引堆支持change操做
                        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;
        }
    }
};
複製代碼

main.cpp:3d

// 測試咱們的Dijkstra最短路徑算法
int main() {

    string filename = "testG1.txt";
    int V = 5;

    SparseGraph<int> g = SparseGraph<int>(V, true);
    // Dijkstra最短路徑算法一樣適用於有向圖
    //SparseGraph<int> g = SparseGraph<int>(V, false);
    ReadGraph<SparseGraph<int>, int> readGraph(g, filename);

    cout<<"Test Dijkstra:"<<endl<<endl;
    Dijkstra<SparseGraph<int>, int> dij(g,0);
    for( int i = 0 ; i < V ; i ++ ){
        if(dij.hasPathTo(i)){
            cout<<"Shortest Path to "<<i<<" : "<<dij.shortestPathTo(i)<<endl;
            dij.showPath(i);
        }
        else
            cout<<"No Path to "<<i<<endl;

        cout<<"----------"<<endl;
    }

    return 0;
}
複製代碼

負權邊和Bellman-Ford算法

dijkstra算法不能處理負權邊code

paste image

  • 擁有負權環的不存在最短路徑

    paste image

  • 兩邊造成負權環

paste image

Bellman-Ford 單源最短路徑算法

  • 一個圖不能有負權環
  • bellman-ford能夠判斷圖中是否有負權環
  • 從一點到另一點的最短路徑,最多通過全部的V個頂線,有V-1條邊
  • 不然,存在頂點通過了兩次,既存在負權環

鬆弛操做的核心是咱們找到了一條邊的路徑,咱們看一下有沒有兩條邊的路徑比他權值小。

  • 對一個點的一次鬆弛操做,就是找到通過這個點的另一條路徑,多一條邊,權值更小。
  • 若是一個圖沒有負權環,從一點到另一點的最短路徑,最多通過全部的V個頂線,有V-1條邊
  • 對全部的點進行V-1次鬆弛操做
  • 對全部的點進行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, 0); // 這裏咱們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;
        }
    }
};
複製代碼

main.cpp:

// 測試Bellman-Ford算法
int main() {

    string filename = "testG2.txt";
    //string filename = "testG_negative_circle.txt";
    int V = 5;

    SparseGraph<int> g = SparseGraph<int>(V, true);
    ReadGraph<SparseGraph<int>, int> readGraph(g, filename);

    cout<<"Test Bellman-Ford:"<<endl<<endl;
    BellmanFord<SparseGraph<int>, int> bellmanFord(g,0);
    if( bellmanFord.negativeCycle() )
        cout<<"The graph contain negative cycle!"<<endl;
    else
        for( int i = 1 ; i < V ; i ++ ) {
            if (bellmanFord.hasPathTo(i)) {
                cout << "Shortest Path to " << i << " : " << bellmanFord.shortestPathTo(i) << endl;
                bellmanFord.showPath(i);
            }
            else
                cout << "No Path to " << i << endl;

            cout << "----------" << endl;
        }

    return 0;
}
複製代碼

用於有向圖,由於無向圖中一條負權邊就等價於兩個方向都有,會造成環。

更多和最短路徑相關的問題

單源最短路徑算法 具體實現改進

  • 具體實現,distTo[i] 初始化爲「正無窮」

    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;
                      }
    複製代碼
  • 利用隊列數據結構

  • queue-based bellman-ford算法

單源最短路徑算法對比

paste image

全部對最短路徑算法

  • Floyed算法,處理無負權環的圖
  • O( V^3 )

最長路徑算法

  • 最長路徑問題不能有正權環。
  • 無權圖的最長路徑問題是指數級難度的。
  • 對於有權圖,不能使用Dijkstra求最長路徑問題。
  • 可使用 Bellman-Ford算法。(取負操做)

-------------------------華麗的分割線--------------------

看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。

我的博客番茄技術小棧掘金主頁

想了解更多,歡迎關注個人微信公衆號:番茄技術小棧

番茄技術小棧
相關文章
相關標籤/搜索