一個最短路徑算法的C++實現(xiàn)

前文講到了圖論中的最小生成樹問題誉察,個人覺得有必要繼續(xù)講講最短路徑算法的選路問題高诺。

什么是最短路徑?

互聯(lián)網(wǎng)技術(shù)和應(yīng)用的不斷發(fā)展峭梳,對當今網(wǎng)絡(luò)通信流量的要求不斷增大舰绘。流量大、速度快、費用低的傳輸方式是網(wǎng)絡(luò)傳輸?shù)年P(guān)鍵除盏。路徑最短叉橱、代價最低的網(wǎng)絡(luò)路由能夠大大降低通信成本、節(jié)約網(wǎng)絡(luò)資源者蠕,提高網(wǎng)絡(luò)資源的利用率窃祝。
最短路徑問題是組合優(yōu)化領(lǐng)域的經(jīng)典問題之一,它廣泛應(yīng)用于計算機科學踱侣、交通工程粪小、通信工程、系統(tǒng)工程抡句、運籌學探膊、信息論、控制理論等眾多領(lǐng)域待榔。Dijkstra算法是經(jīng)典的最短路徑算法逞壁。

狄克斯特拉算法

Dijkstra算法是經(jīng)典的最短路徑算法,其基本思想是:設(shè)置一個集合S存放已經(jīng)找到最短路徑的頂點锐锣,S的初始狀態(tài)只包含源點v腌闯,對vi∈V-S,假設(shè)從源點v到vi的有向邊為最短路徑雕憔。以后每求得一條最短路徑v, …, vk姿骏,就將vk加入集合S中,并將路徑v, …, vk , vi與原來的假設(shè)相比較斤彼,取路徑長度較小者為最短路徑分瘦,重復上述過程,直到集合V中全部頂點加入到集合S中琉苇。使用該算法找到的是全局最優(yōu)的最短路徑嘲玫,在網(wǎng)絡(luò)節(jié)點數(shù)量大、網(wǎng)絡(luò)邊數(shù)較多時翁潘,存在內(nèi)存占用大趁冈、時間復雜度高等不足歼争,并且Dijkstra算法不能很好地解決含必經(jīng)點約束的最短路徑問題拜马。

假設(shè)有這樣一張圖,需要求解從A點到其他點的最短路徑:


image.png

一個C++算法實現(xiàn)

拿上面的圖做為例子沐绒,具體實現(xiàn)下面的代碼:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include <string>
#include <vector>
#include <set>
#include <map>

// 鄰邊
struct SEdge
{
    std::string strVertexFrom;
    std::string strVertexTo;
    int nEdgeWeight;

    SEdge(const std::string& strFrom = "", const std::string& strTo = "", int nWeight = 0)
    {
        strVertexFrom = strFrom;
        strVertexTo = strTo;
        nEdgeWeight = nWeight;
    }
};

// 鄰接矩陣
class CThroughMatrix
{
    // 頂點集合
    std::set<std::string> m_setVertex;

    // 鄰邊集合
    typedef std::map<std::string, int> MAP_TOVERTEX_WEIGHT_t;
    std::map<std::string, MAP_TOVERTEX_WEIGHT_t> m_mapEdge;

public:
    CThroughMatrix()
    {
    }
    virtual ~CThroughMatrix()
    {
    }

    // 新增頂點名稱
    void addVertex(const std::string& strVertex)
    {
        if (m_setVertex.find(strVertex) == m_setVertex.end())
        {
            m_setVertex.insert(strVertex);

            printf("add vertex:[%s] \n", strVertex.c_str());
        }
    }

    // 移除指定頂點
    void delVertex(const std::string& strVertex)
    {
        if (m_setVertex.find(strVertex) == m_setVertex.end())
            return;

        // 找該頂點的入度俩莽,刪除鄰邊
        for (auto iter = m_mapEdge.begin(); iter != m_mapEdge.end(); ++iter)
        {
            MAP_TOVERTEX_WEIGHT_t& mapToVertexWeight = iter->second;

            auto iterEdge = mapToVertexWeight.find(strVertex);
            if (iterEdge != mapToVertexWeight.end())
            {
                printf("delete edge:[%s -> %s] weight:[%d] \n", iter->first.c_str(), iterEdge->first.c_str(), iterEdge->second);

                mapToVertexWeight.erase(iterEdge);
                if (mapToVertexWeight.empty())
                {
                    m_mapEdge.erase(iter);
                }
            }
        }
        
        // 找該頂點的出度,刪除鄰邊
        auto iter = m_mapEdge.find(strVertex);
        if (iter != m_mapEdge.end())
        {
            MAP_TOVERTEX_WEIGHT_t& mapToVertexWeight = iter->second;

            for (auto iterEdge = mapToVertexWeight.begin(); iterEdge != mapToVertexWeight.end(); ++iterEdge)
            {
                printf("delete edge:[%s -> %s] weight:[%d] \n", iter->first.c_str(), iterEdge->first.c_str(), iterEdge->second);
            }

            m_mapEdge.erase(iter);
        }

        // 刪除頂點
        m_setVertex.erase(strVertex);
    }

    // 增加鄰邊
    void addEdge(const std::string& strVertex1, const std::string& strVertex2, int nWeight, bool bDirect = false)
    {
        // 檢查頂點是否存在
        if (m_setVertex.find(strVertex1) == m_setVertex.end())
            return;
        if (m_setVertex.find(strVertex2) == m_setVertex.end())
            return;

        // 添加 strVertex1 -> strVertex2
        {
            MAP_TOVERTEX_WEIGHT_t& mapToVertexWeight = m_mapEdge[strVertex1];
            auto iterEdge = mapToVertexWeight.find(strVertex2);
            if (iterEdge != mapToVertexWeight.end())
            {
                iterEdge->second = nWeight;

                printf("update edge:[%s -> %s] weight:[%d] \n", strVertex1.c_str(), strVertex2.c_str(), nWeight);
            }
            else
            {
                 mapToVertexWeight.insert( std::make_pair(strVertex2, nWeight) );

                 printf("add edge:[%s -> %s] weight:[%d] \n", strVertex1.c_str(), strVertex2.c_str(), nWeight);
            }
        }

        // 無向圖添加 strVertex2 -> strVertex1
        if (!bDirect)
        {
            MAP_TOVERTEX_WEIGHT_t& mapToVertexWeight = m_mapEdge[strVertex2];
            auto iterEdge = mapToVertexWeight.find(strVertex1);
            if (iterEdge != mapToVertexWeight.end())
            {
                iterEdge->second = nWeight;

                printf("update edge:[%s -> %s] weight:[%d] \n", strVertex2.c_str(), strVertex1.c_str(), nWeight);
            }
            else
            {
                 mapToVertexWeight.insert( std::make_pair(strVertex1, nWeight) );

                 printf("add edge:[%s -> %s] weight:[%d] \n", strVertex2.c_str(), strVertex1.c_str(), nWeight);
            }
        }
    }

    // 刪除鄰邊
    void delEdge(const std::string& strVertex1, const std::string& strVertex2, bool bDirect = false)
    {
        // 檢查頂點是否存在
        if (m_setVertex.find(strVertex1) == m_setVertex.end())
            return;
        if (m_setVertex.find(strVertex2) == m_setVertex.end())
            return;

        // 刪除 strVertex1 -> strVertex2
        {
            auto iter = m_mapEdge.find(strVertex1);
            if (iter != m_mapEdge.end())
            {
                MAP_TOVERTEX_WEIGHT_t& mapToVertexWeight = iter->second;

                auto iterEdge = mapToVertexWeight.find(strVertex2);
                if (iterEdge != mapToVertexWeight.end())
                {
                    mapToVertexWeight.erase(iterEdge);
                    if (mapToVertexWeight.empty())
                    {
                        m_mapEdge.erase(strVertex1);
                    }

                    printf("delete edge:[%s -> %s] \n", strVertex1.c_str(), strVertex2.c_str());
                }
            }
        }

        // 無向圖刪除 strVertex2 -> strVertex1
        if (!bDirect)
        {
            auto iter = m_mapEdge.find(strVertex2);
            if (iter != m_mapEdge.end())
            {
                MAP_TOVERTEX_WEIGHT_t& mapToVertexWeight = iter->second;

                auto iterEdge = mapToVertexWeight.find(strVertex1);
                if (iterEdge != mapToVertexWeight.end())
                {
                    mapToVertexWeight.erase(iterEdge);
                    if (mapToVertexWeight.empty())
                    {
                        m_mapEdge.erase(strVertex1);
                    }

                    printf("delete edge:[%s -> %s] \n", strVertex2.c_str(), strVertex1.c_str());
                }
            }
        }
    }

    
    // 使用普里姆算法計算最小生成樹
    bool calcMinWeightTreeByPrim(std::vector<SEdge>& vecEdge) const
    {
        if (m_setVertex.empty())
        {
            printf("no vertex! \n");
            return false;
        }

        std::set<std::string> setVertexLeft, setVertexRight = m_setVertex;

        // 初使化左右頂點集合
        // 左頂點集合為已探知乔遮,右頂點集合為待探知
        const std::string& strVertex = *setVertexRight.begin();
        setVertexLeft.insert(strVertex);
        setVertexRight.erase(strVertex);

        while (!setVertexRight.empty())
        {
            // 尋找從左邊頂點到右邊頂點的最小鄰邊

            std::string strFrom = "", strTo = "";
            int nMinWeight = -1;

            for (auto iterLeft = setVertexLeft.begin(); iterLeft != setVertexLeft.end(); ++iterLeft)
            {
                const std::string& strLeft = (*iterLeft);

                auto iter = m_mapEdge.find(strLeft);
                if (iter == m_mapEdge.end())
                {
                    printf("vertex:[%s] no edge! \n", strLeft.c_str());
                    return false;
                }

                const MAP_TOVERTEX_WEIGHT_t& mapToVertexWeight = iter->second;

                for (auto iterEdge = mapToVertexWeight.begin(); iterEdge != mapToVertexWeight.end(); ++iterEdge)
                {
                    const std::string& strRight = iterEdge->first;

                    // 過濾已探知的頂點
                    if (setVertexRight.find(strRight) == setVertexRight.end())
                        continue;

                    if (nMinWeight < 0 || iterEdge->second < nMinWeight)
                    {
                        strFrom = strLeft;
                        strTo = strRight;
                        nMinWeight = iterEdge->second;
                    }
                }
            }

            // 找到權(quán)重最小的待探知頂點
            if (strTo != "")
            {
                SEdge stEdge(strFrom, strTo, nMinWeight);
                vecEdge.push_back(stEdge);

                // 將頂點從右邊移動到左邊
                setVertexLeft.insert(strTo);
                setVertexRight.erase(strTo);
            }
        }

        return true;
    }

    // 使用狄克斯特拉算法計算從指定點到其他點的最小距離
    bool calcMinDistanceByDijkstra(const std::string& strVertexSrc, std::vector<std::pair<std::string, int> >& vecDistance) const
    {
        if (m_setVertex.find(strVertexSrc) == m_setVertex.end())
        {
            printf("no vertex! \n");
            return false;
        }

        // 初使化距離表
        std::map<std::string, int> mapDistance;
        for (auto iter = m_setVertex.begin(); iter != m_setVertex.end(); ++iter)
        {
            mapDistance[*iter] = -1;
        }
 
        std::set<std::string> setVertexRight = m_setVertex;

        // 初使化選擇起始頂點
        printf("choose vertex:[%s] \n", strVertexSrc.c_str());
        vecDistance.push_back(std::make_pair(strVertexSrc, 0));

        // 標明起始頂點為已探知
        setVertexRight.erase(strVertexSrc);

        mapDistance[strVertexSrc] = 0;
        printf("update to:[%s] distance:[0] \n", strVertexSrc.c_str());

        // 更新與起始頂點直連頂點的距離
        {
            auto iter = m_mapEdge.find(strVertexSrc);
            if (iter == m_mapEdge.end())
            {
                printf("source vertex:[%s] no edge! \n", strVertexSrc.c_str());
                return false;
            }

            const MAP_TOVERTEX_WEIGHT_t& mapToVertexWeight = iter->second;
            for (auto iterEdge = mapToVertexWeight.begin(); iterEdge != mapToVertexWeight.end(); ++iterEdge)
            {
                mapDistance[iterEdge->first] = iterEdge->second;

                printf("update to:[%s] distance:[%d] \n", iterEdge->first.c_str(), iterEdge->second);
            }
        }

        while (!setVertexRight.empty())
        {
            // 從已知探明的頂點中取取距離最小的頂點
            std::string strChoose = "";
            int nMinDistance = -1;
            for (auto iter = mapDistance.begin(); iter != mapDistance.end(); ++iter)
            {
                // 必須為距離已探明
                if (iter->second < 0)
                    continue;

                // 必須為頂點未探明
                if (setVertexRight.find(iter->first) == setVertexRight.end())
                    continue;

                if (nMinDistance < 0 || iter->second < nMinDistance)
                {
                    strChoose = iter->first;
                    nMinDistance = iter->second;
                }
            }

            // 找到最小距離頂點
            if (strChoose != "")
            {
                printf("choose nearest vertex:[%s] distance:[%d] \n", strChoose.c_str(), mapDistance[strChoose]);

                vecDistance.push_back(std::make_pair(strChoose, mapDistance[strChoose]));

                // 標記該頂點為已探知
                setVertexRight.erase(strChoose);

                // 更新通過strChoose頂點可達的距離
                auto iter = m_mapEdge.find(strChoose);
                if (iter == m_mapEdge.end())
                {
                    printf("vertex:[%s] no edge! \n", strChoose.c_str());
                    continue;
                }

                const MAP_TOVERTEX_WEIGHT_t& mapToVertexWeight = iter->second;

                for (auto iterEdge = mapToVertexWeight.begin(); iterEdge != mapToVertexWeight.end(); ++iterEdge)
                {
                    const std::string& strRight = iterEdge->first;
                    int nWeight = iterEdge->second;

                    // 過濾已探知的頂點
                    if (setVertexRight.find(strRight) == setVertexRight.end())
                        continue;

                    // 更新相鄰頂點的距離
                    if (mapDistance[strRight] < 0 || mapDistance[strChoose] + nWeight < mapDistance[strRight])
                    {
                        mapDistance[strRight] = mapDistance[strChoose] + nWeight;

                        printf("update to:[%s] distance:[%d] \n", strRight.c_str(), mapDistance[strRight]);
                    }
                }
            }
        }

        return true;
        
    }
};

int main(int argc, char* argv[])
{
    CThroughMatrix throughMatrix;    
    
    // 加入頂點
    throughMatrix.addVertex("A");
    throughMatrix.addVertex("B");
    throughMatrix.addVertex("C");
    throughMatrix.addVertex("D");
    throughMatrix.addVertex("E");
    throughMatrix.addVertex("F");
    throughMatrix.addVertex("G");

    // 加入各有向邊和距離

    throughMatrix.addEdge("A", "B", 4, true);
    throughMatrix.addEdge("A", "C", 6, true);
    throughMatrix.addEdge("A", "D", 6, true);
   
    throughMatrix.addEdge("B", "C", 1, true);
    throughMatrix.addEdge("B", "E", 7, true);
   
    throughMatrix.addEdge("C", "E", 6, true);
    throughMatrix.addEdge("C", "F", 4, true);

    throughMatrix.addEdge("D", "C", 2, true);
    throughMatrix.addEdge("D", "F", 5, true);

    throughMatrix.addEdge("E", "G", 6, true);

    throughMatrix.addEdge("F", "E", 1, true);
    throughMatrix.addEdge("F", "G", 8, true);

    // 計算最短路徑
    std::vector<std::pair<std::string, int> > vecDistance;
    throughMatrix.calcMinDistanceByDijkstra("A", vecDistance);

    for (auto iter = vecDistance.begin(); iter != vecDistance.end(); ++iter)
    {
        printf("vertex:[%s] distance:[%d] \n", iter->first.c_str(), iter->second);
    }

    return 0;
}

代碼基于最小生成樹修改而來扮超,增加了有向圖和最短路徑算法,但是刪除了最小生成樹的調(diào)用部分,如果讀者朋友只需要借簽算法部分的話出刷,取本篇的代碼就可以了璧疗。

編譯:
g++ -o testdijkstra testdijkstra.cpp -std=c++11

運行結(jié)果:

add vertex:[A] 
add vertex:[B] 
add vertex:[C] 
add vertex:[D] 
add vertex:[E] 
add vertex:[F] 
add vertex:[G] 
add edge:[A -> B] weight:[4] 
add edge:[A -> C] weight:[6] 
add edge:[A -> D] weight:[6] 
add edge:[B -> C] weight:[1] 
add edge:[B -> E] weight:[7] 
add edge:[C -> E] weight:[6] 
add edge:[C -> F] weight:[4] 
add edge:[D -> C] weight:[2] 
add edge:[D -> F] weight:[5] 
add edge:[E -> G] weight:[6] 
add edge:[F -> E] weight:[1] 
add edge:[F -> G] weight:[8] 
choose vertex:[A] 
update to:[A] distance:[0] 
update to:[B] distance:[4] 
update to:[C] distance:[6] 
update to:[D] distance:[6] 
choose nearest vertex:[B] distance:[4] 
update to:[C] distance:[5] 
update to:[E] distance:[11] 
choose nearest vertex:[C] distance:[5] 
update to:[F] distance:[9] 
choose nearest vertex:[D] distance:[6] 
choose nearest vertex:[F] distance:[9] 
update to:[E] distance:[10] 
update to:[G] distance:[17] 
choose nearest vertex:[E] distance:[10] 
update to:[G] distance:[16] 
choose nearest vertex:[G] distance:[16] 
vertex:[G] no edge! 
vertex:[A] distance:[0] 
vertex:[B] distance:[4] 
vertex:[C] distance:[5] 
vertex:[D] distance:[6] 
vertex:[F] distance:[9] 
vertex:[E] distance:[10] 
vertex:[G] distance:[16] 

這是一個最短路徑的探索過程,是求解全圖中從某個頂點到其他所有頂點的距離問題馁龟,整個過程有近及遠的發(fā)現(xiàn)和探知頂點崩侠,直到所有頂點完成探索。
但是狄克斯特拉算法只是求出了單個起點到其他頂點的距離坷檩,并不包括某兩點間的最短路徑却音,其實基于狄克斯特拉的探索過程是可以分析出兩點之間的最短路徑的,后面有機會再講矢炼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末系瓢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子句灌,更是在濱河造成了極大的恐慌夷陋,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胰锌,死亡現(xiàn)場離奇詭異肌稻,居然都是意外死亡,警方通過查閱死者的電腦和手機匕荸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門爹谭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人榛搔,你說我怎么就攤上這事诺凡。” “怎么了践惑?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵腹泌,是天一觀的道長。 經(jīng)常有香客問我尔觉,道長凉袱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任侦铜,我火速辦了婚禮专甩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钉稍。我一直安慰自己涤躲,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布贡未。 她就那樣靜靜地躺著种樱,像睡著了一般蒙袍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嫩挤,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天害幅,我揣著相機與錄音,去河邊找鬼岂昭。 笑死矫限,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的佩抹。 我是一名探鬼主播叼风,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼棍苹!你這毒婦竟也來了无宿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤枢里,失蹤者是張志新(化名)和其女友劉穎孽鸡,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栏豺,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡彬碱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奥洼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巷疼。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖灵奖,靈堂內(nèi)的尸體忽然破棺而出嚼沿,到底是詐尸還是另有隱情,我是刑警寧澤瓷患,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布骡尽,位于F島的核電站,受9級特大地震影響擅编,放射性物質(zhì)發(fā)生泄漏攀细。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一爱态、第九天 我趴在偏房一處隱蔽的房頂上張望谭贪。 院中可真熱鬧,春花似錦肢藐、人聲如沸故河。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鱼的。三九已至,卻和暖如春痘煤,著一層夾襖步出監(jiān)牢的瞬間凑阶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工衷快, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宙橱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓蘸拔,卻偏偏與公主長得像师郑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子调窍,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容