一个最短路径算法的C++实现

前文讲到了图论中的最小生成树问题,个人觉得有必要继续讲讲最短路径算法的选路问题。

什么是最短路径?

互联网技术和应用的不断发展,对当今网络通信流量的要求不断增大。流量大、速度快、费用低的传输方式是网络传输的关键。路径最短、代价最低的网络路由能够大大降低通信成本、节约网络资源,提高网络资源的利用率。
最短路径问题是组合优化领域的经典问题之一,它广泛应用于计算机科学、交通工程、通信工程、系统工程、运筹学、信息论、控制理论等众多领域。Dijkstra算法是经典的最短路径算法。

狄克斯特拉算法

Dijkstra算法是经典的最短路径算法,其基本思想是:设置一个集合S存放已经找到最短路径的顶点,S的初始状态只包含源点v,对vi∈V-S,假设从源点v到vi的有向边为最短路径。以后每求得一条最短路径v, …, vk,就将vk加入集合S中,并将路径v, …, vk , vi与原来的假设相比较,取路径长度较小者为最短路径,重复上述过程,直到集合V中全部顶点加入到集合S中。使用该算法找到的是全局最优的最短路径,在网络节点数量大、网络边数较多时,存在内存占用大、时间复杂度高等不足,并且Dijkstra算法不能很好地解决含必经点约束的最短路径问题。

假设有这样一张图,需要求解从A点到其他点的最短路径:


image.png

一个C++算法实现

拿上面的图做为例子,具体实现下面的代码:

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

            // 找到权重最小的待探知顶点
            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;
}

代码基于最小生成树修改而来,增加了有向图和最短路径算法,但是删除了最小生成树的调用部分,如果读者朋友只需要借签算法部分的话,取本篇的代码就可以了。

编译:
g++ -o testdijkstra testdijkstra.cpp -std=c++11

运行结果:

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] 

这是一个最短路径的探索过程,是求解全图中从某个顶点到其他所有顶点的距离问题,整个过程有近及远的发现和探知顶点,直到所有顶点完成探索。
但是狄克斯特拉算法只是求出了单个起点到其他顶点的距离,并不包括某两点间的最短路径,其实基于狄克斯特拉的探索过程是可以分析出两点之间的最短路径的,后面有机会再讲。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容