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

dijkstra單源最短路徑算法

前提:圖中不能有負(fù)權(quán)邊
因?yàn)榇嬖谪?fù)權(quán)環(huán)的話(huà)就不存在最短路徑

復(fù)雜度 O(ElogV)

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

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

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);
        }

        // 使用索引堆記錄當(dāng)前找到的到達(dá)每個(gè)頂點(diǎn)的最短距離
        IndexMinHeap<Weight> ipq(G.V());

        // 對(duì)于其實(shí)點(diǎn)s進(jìn)行初始化
        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;

            // 對(duì)v的所有相鄰節(jié)點(diǎn)進(jìn)行更新
            typename Graph::adjIterator adj(G, v);
            for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ){
                int w = e->other(v);
                // 如果從s點(diǎn)到w點(diǎn)的最短路徑還沒(méi)有找到
                if( !marked[w] ){
                    // 如果w點(diǎn)以前沒(méi)有訪(fǎng)問(wèn)過(guò),
                    // 或者訪(fǎng)問(wèn)過(guò), 但是通過(guò)當(dāng)前的v點(diǎn)到w點(diǎn)距離更短, 則進(jìn)行更新
                    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點(diǎn)到w點(diǎn)的最短路徑長(zhǎng)度
    Weight shortestPathTo( int w ){
        assert( w >= 0 && w < G.V() );
        assert( hasPathTo(w) );
        return distTo[w];
    }

    // 判斷從s點(diǎn)到w點(diǎn)是否聯(lián)通
    bool hasPathTo( int w ){
        assert( w >= 0 && w < G.V() );
        return marked[w];
    }

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

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

        // 通過(guò)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點(diǎn)到w點(diǎn)的路徑
    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單源最短路徑算法

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

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

復(fù)雜度O(EV)

如果一個(gè)圖沒(méi)有負(fù)權(quán)環(huán)描睦,從一點(diǎn)到另外一點(diǎn)的最短路徑宿接,最多經(jīng)過(guò)所有的V個(gè)頂點(diǎn)宫峦,有V-1條邊
否則存在頂點(diǎn)經(jīng)過(guò)了兩次德绿,即存在負(fù)權(quán)環(huán)

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

private:
    Graph &G;                   // 圖的引用
    int s;                      // 起始點(diǎn)
    Weight* distTo;             // distTo[i]存儲(chǔ)從起始點(diǎn)s到i的最短路徑長(zhǎng)度
    vector<Edge<Weight>*> from; // from[i]記錄最短路徑中, 到達(dá)i點(diǎn)的邊是哪一條
                                // 可以用來(lái)恢復(fù)整個(gè)最短路徑
    bool hasNegativeCycle;      // 標(biāo)記圖中是否有負(fù)權(quán)環(huán)

    // 判斷圖中是否有負(fù)權(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é)點(diǎn)s都不可達(dá), 由from數(shù)組來(lái)表示
        for( int i = 0 ; i < G.V() ; i ++ )
            from.push_back(NULL);

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

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

            // 每次循環(huán)中對(duì)所有的邊進(jìn)行一遍松弛操作
            // 遍歷所有邊的方式是先遍歷所有的頂點(diǎn), 然后遍歷和所有頂點(diǎn)相鄰的所有邊
            for( int i = 0 ; i < G.V() ; i ++ ){
                // 使用我們實(shí)現(xiàn)的鄰邊迭代器遍歷和所有頂點(diǎn)相鄰的所有邊
                typename Graph::adjIterator adj(G,i);
                for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() )
                    // 對(duì)于每一個(gè)邊首先判斷e->v()可達(dá)
                    // 之后看如果e->w()以前沒(méi)有到達(dá)過(guò)荷荤, 顯然我們可以更新distTo[e->w()]
                    // 或者e->w()以前雖然到達(dá)過(guò), 但是通過(guò)這個(gè)e我們可以獲得一個(gè)更短的距離, 即可以進(jìn)行一次松弛操作, 我們也可以更新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];
    }

    // 返回圖中是否有負(fù)權(quán)環(huán)
    bool negativeCycle(){
        return hasNegativeCycle;
    }

    // 返回從s點(diǎn)到w點(diǎn)的最短路徑長(zhǎng)度
    Weight shortestPathTo( int w ){
        assert( w >= 0 && w < G.V() );
        assert( !hasNegativeCycle );
        assert( hasPathTo(w) );
        return distTo[w];
    }

    // 判斷從s點(diǎn)到w點(diǎn)是否聯(lián)通
    bool hasPathTo( int w ){
        assert( w >= 0 && w < G.V() );
        return from[w] != NULL;
    }

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

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

        // 通過(guò)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點(diǎn)到w點(diǎn)的路徑
    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算法的一種隊(duì)列優(yōu)化,減少了不必要的冗余計(jì)算移稳。

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蕴纳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子个粱,更是在濱河造成了極大的恐慌古毛,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件都许,死亡現(xiàn)場(chǎng)離奇詭異稻薇,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)胶征,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)塞椎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人睛低,你說(shuō)我怎么就攤上這事案狠。” “怎么了钱雷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵骂铁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我罩抗,道長(zhǎng)拉庵,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任澄暮,我火速辦了婚禮名段,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泣懊。我一直安慰自己伸辟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布馍刮。 她就那樣靜靜地躺著信夫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上静稻,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天警没,我揣著相機(jī)與錄音,去河邊找鬼振湾。 笑死杀迹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的押搪。 我是一名探鬼主播树酪,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼大州!你這毒婦竟也來(lái)了续语?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤厦画,失蹤者是張志新(化名)和其女友劉穎疮茄,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體根暑,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡力试,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了购裙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懂版。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖躏率,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情民鼓,我是刑警寧澤薇芝,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站丰嘉,受9級(jí)特大地震影響夯到,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饮亏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一耍贾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧路幸,春花似錦荐开、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春能扒,著一層夾襖步出監(jiān)牢的瞬間佣渴,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工初斑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辛润,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓见秤,卻偏偏與公主長(zhǎng)得像砂竖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秦叛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353