Dijkstra算法及其C++实现

简介: 如果从图中某一顶点(称为源点)到达另一顶点(称为终点)的路径可能不止一条,如何找到一条路径使得沿此路径上各边上的权值总和达到最小。

Dijkstra算法及其C++实现

什么是最短路径问题

如果从图中某一顶点(称为源点)到达另一顶点(称为终点)的路径可能不止一条,如何找到一条路径使得沿此路径上各边上的权值总和达到最小。

单源最短路径问题是指对于给定的图$G=(V, E)$,求源点$v_0$到其它顶点$v_t$的最短路径。

Dijkstra算法

Dijkstra算法用于计算一个节点到其他节点的最短路径。Dijkstra是一种按路径长度递增的顺序逐步产生最短路径的方法,是一种贪婪算法。

Dijkstra算法的核心思想是首先求出长度最短的一条最短路径,再参照它求出长度次短的一条最短路径,依次类推,直到从源点$v_0$到其它各顶点的最短路径全部求出为止。

具体来说:图中所有顶点分成两组,第一组是已确定最短路径的顶点,初始只包含一个源点,记为集合$S$;第二组是尚未确定最短路径的顶点,记为集合$U$。

按最短路径长度递增的顺序逐个把$U$中的顶点加到$S$中去,同时动态更新$U$集合中源点到各个顶点的最短距离,直至所有顶点都包括到$S$中。

实现思路

  1. 初始时,$S$集合只包含起点$v_0$;$U$集合包含除$v_0$外的其他顶点$v_t$,且$U$中顶点的距离为起点$v_0$到该顶点的距离。($U$中顶点$v_t$的距离为$(v_0, v_t)$的长度,如果$v_0$和$v_t$不相邻,则$v_t$的最短距离为$\infty$)
  2. 从$U$中选出距离最短的顶点$v_{t'}$,并将顶点$v_{t'}$加入到$S$中;同时,从$U$中移除顶点$v_{t'}$。
  3. 更新$U$中各个顶点$v_t$到起点$v_0$的距离以及最短路径中当前顶点的前驱顶点。之所以更新$U$中顶点的距离以及前驱顶点是由于上一步中确定了$v_{t'}$是求出最短路径的顶点,从而可以利用$v_{t'}$来更新$U$中其它顶点$v_t$的距离,因为存在$(v_0, v_t)$的距离可能大于$(v_0, v_{t'}) + (v_{t'}, v_t)$距离的情况,从而也需要更新其前驱顶点
  4. 重复步骤(2)和(3),直到遍历完所有顶点

代码实现

使用了部分C++11特性,注释丰富,读起来应该不会太困难!

#include <cstdio>
#include <iostream>
#include <vector>
#include <list>
#include <stack>

using namespace std;
using Matrix = vector<vector<uint>>;                // 连接矩阵(使用嵌套的vector表示)
using SNodes = vector<tuple<uint, uint, uint>>;     // 已计算出最短路径的顶点集合S(类似一个动态数组)
using UNodes = list<tuple<uint, uint, uint>>;       // 未进行遍历的顶点集合U(使用list主要是方便元素删除操作)
using ENode = tuple<uint, uint, uint>;              // 每个节点包含(顶点编号,当前顶点到起始点最短距离,最短路径中当前顶点的上一个顶点)信息


/***
 * 从未遍历的U顶点集合中找到下一个离起始顶点距离最短的顶点
 * @param unvisitedNodes 未遍历的U顶点集合
 * 每个元素是(顶点编号,当前顶点到起始点最短距离,最短路径中当前顶点的上一个顶点)的tuple
 * @return 下一个离起始顶点距离最短的顶点
 */
ENode searchNearest(const UNodes &unvisitedNodes) {
    uint minDistance = UINT_MAX;
    ENode nearest;
    for (const auto &node: unvisitedNodes) {
        if (get<1>(node) <= minDistance) {
            minDistance = get<1>(node);
            nearest = node;
        }
    }
    return nearest;
}


/***
 * 迪克斯特拉算法的实现
 * @param graph 连接矩阵(使用嵌套的vector表示)
 * @param startNodeIndex 起始点编码(从0开始)
 * @return 返回一个vector,每个元素是到起始顶点的距离排列的包含(顶点编号,当前顶点到起始点最短距离,最短路径中当前顶点的上一个顶点)的tuple
 */
SNodes dijkstra(const Matrix &graph, uint startNodeIndex) {
    const uint numOfNodes = graph.size();   // 图中顶点的个数
    // S是已计算出最短路径的顶点的集合(顶点编号,当前顶点到起始点最短距离,最短路径中当前顶点的上一个顶点)
    SNodes visitedNodes;
    // U是未计算出最短路径的顶点的集合(其中的key为顶点编号,value为到起始顶点最短距离和最短路径中上一个节点编号组成的pair)
    UNodes unvisitedNodes;

    // 对S和U集合进行初始化,起始顶点的距离为0,其他顶点的距离为无穷大
    // 最短路径中当前顶点的上一个顶点初始化为起始顶点,后面会逐步进行修正
    for (auto i = 0; i < numOfNodes; ++i) {
        if (i == startNodeIndex) visitedNodes.emplace_back(i, 0, startNodeIndex);
        else unvisitedNodes.emplace_back(i, graph[startNodeIndex][i], startNodeIndex);
    }

    while (!unvisitedNodes.empty()) {
        // 从U中找到距离起始顶点距离最短的顶点,加入S,同时从U中删除
        auto nextNode = searchNearest(unvisitedNodes);
        unvisitedNodes.erase(find(unvisitedNodes.begin(), unvisitedNodes.end(), nextNode));
        visitedNodes.emplace_back(nextNode);
        // 更新U集合中各个顶点的最短距离以及最短路径中的上一个顶点
        for (auto &node: unvisitedNodes) {
            // 更新的判断依据就是起始顶点到当前顶点(nextNode)距离加上当前顶点到U集合中顶点的距离小于原来起始顶点到U集合中顶点的距离
            // 更新最短距离的时候同时需要更新最短路径中的上一个顶点为nextNode
            if (graph[get<0>(nextNode)][get<0>(node)] != UINT_MAX &&
                graph[get<0>(nextNode)][get<0>(node)] + get<1>(nextNode) < get<1>(node)) {
                get<1>(node) = graph[get<0>(nextNode)][get<0>(node)] + get<1>(nextNode);
                get<2>(node) = get<0>(nextNode);
            }
        }
    }

    return visitedNodes;
}


/***
 * 对使用迪克斯特拉算法求解的最短路径进行打印输出
 * @param paths vector表示的最短路径集合
 * 每个元素是到起始顶点的距离排列的包含(顶点编号,当前顶点到起始点最短距离,最短路径中当前顶点的上一个顶点)的tuple
 */
void print(const SNodes &paths) {
    stack<int> tracks;  //从尾部出发,使用stack将每个顶点的最短路径中的前一个顶点入栈,然后出栈的顺序就是最短路径顺序
    // 第一个元素是起始点,从第二个元素进行打印输出
    for (auto it = ++paths.begin(); it != paths.end(); ++it) {
        // 打印头部信息
        printf("%c -> %c:\t Length: %d\t Paths: %c",
               char(get<0>(paths[0]) + 65),
               char(get<0>(*it) + 65),
               get<1>(*it),
               char(get<0>(paths[0]) + 65));
        auto pointer = *it;
        // 如果当前指针pointer指向的节点有中途节点(判断的条件是最短路径中的前一个节点不是起始点)
        while (get<2>(pointer) != get<0>(paths[0])) {
            tracks.push(get<0>(pointer));
            // Lambda表达式,使用find_if函数把当前顶点的前一个顶点从paths中找出来继续进行循环直到前一个节点就是起始点
            auto condition = [pointer](tuple<uint, uint, uint> x) { return get<0>(x) == get<2>(pointer); };
            pointer = *find_if(paths.begin(), paths.end(), condition);
        }
        tracks.push(get<0>(pointer));

        // 以出栈的顺序进行打印输出
        while (!tracks.empty()) {
            printf(" -> %c", char(tracks.top() + 65));
            tracks.pop();
        }
        printf("\n");
    }
}

int main() {
    Matrix graph = {
            {0,        12,       UINT_MAX, UINT_MAX, UINT_MAX, 16, 14},
            {12,       0,        10,       UINT_MAX, UINT_MAX, 7, UINT_MAX},
            {UINT_MAX, 10,       0, 3,               5,        6, UINT_MAX},
            {UINT_MAX, UINT_MAX, 3, 0,               4, UINT_MAX, UINT_MAX},
            {UINT_MAX, UINT_MAX, 5, 4,               0,        2,  8},
            {16,       7,        6,        UINT_MAX, 2,        9,  9},
            {14,       UINT_MAX, UINT_MAX, UINT_MAX, 8,        9,  0}
    };  // 图对应的连接矩阵
    auto results = dijkstra(graph, uint('D' - 65));          // 选取顶点C(大写字母A的ASCII编码是65)
    print(results);     // 打印输出结果
    return 0;
}

运行结果:

D -> C:     Length: 3     Paths: D -> C
D -> E:     Length: 4     Paths: D -> E
D -> F:     Length: 6     Paths: D -> E -> F
D -> G:     Length: 12     Paths: D -> E -> G
D -> B:     Length: 13     Paths: D -> C -> B
D -> A:     Length: 22     Paths: D -> E -> F -> A
目录
相关文章
|
9月前
|
存储 运维 监控
基于 C# 语言的 Dijkstra 算法在局域网内监控软件件中的优化与实现研究
本文针对局域网监控系统中传统Dijkstra算法的性能瓶颈,提出了一种基于优先队列和邻接表优化的改进方案。通过重构数据结构与计算流程,将时间复杂度从O(V²)降至O((V+E)logV),显著提升大规模网络环境下的计算效率与资源利用率。实验表明,优化后算法在包含1000节点、5000链路的网络中,计算时间缩短37.2%,内存占用减少21.5%。该算法适用于网络拓扑发现、异常流量检测、故障定位及负载均衡优化等场景,为智能化局域网监控提供了有效支持。
238 5
|
10月前
|
存储 监控 算法
基于 C++ 哈希表算法实现局域网监控电脑屏幕的数据加速机制研究
企业网络安全与办公管理需求日益复杂的学术语境下,局域网监控电脑屏幕作为保障信息安全、规范员工操作的重要手段,已然成为网络安全领域的关键研究对象。其作用类似网络空间中的 “电子眼”,实时捕获每台电脑屏幕上的操作动态。然而,面对海量监控数据,实现高效数据存储与快速检索,已成为提升监控系统性能的核心挑战。本文聚焦于 C++ 语言中的哈希表算法,深入探究其如何成为局域网监控电脑屏幕数据处理的 “加速引擎”,并通过详尽的代码示例,展现其强大功能与应用价值。
212 2
|
6月前
|
机器学习/深度学习 编解码 算法
【机器人路径规划】基于迪杰斯特拉算法(Dijkstra)的机器人路径规划(Python代码实现)
【机器人路径规划】基于迪杰斯特拉算法(Dijkstra)的机器人路径规划(Python代码实现)
535 4
|
7月前
|
算法 机器人 定位技术
基于机器视觉和Dijkstra算法的平面建筑群地图路线规划matlab仿真
本程序基于机器视觉与Dijkstra算法,实现平面建筑群地图的路径规划。通过MATLAB 2022A读取地图图像,识别障碍物并进行路径搜索,支持鼠标选择起点与终点,最终显示最优路径及长度,适用于智能导航与机器人路径规划场景。
|
10月前
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
252 17
|
9月前
|
存储 机器学习/深度学习 算法
基于 C++ 的局域网访问控制列表(ACL)实现及局域网限制上网软件算法研究
本文探讨局域网限制上网软件中访问控制列表(ACL)的应用,分析其通过规则匹配管理网络资源访问的核心机制。基于C++实现ACL算法原型,展示其灵活性与安全性。文中强调ACL在企业与教育场景下的重要作用,并提出性能优化及结合机器学习等未来研究方向。
240 4
|
8月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
221 0
|
9月前
|
机器学习/深度学习 存储 算法
基于 C++ 布隆过滤器算法的局域网上网行为控制:URL 访问过滤的高效实现研究
本文探讨了一种基于布隆过滤器的局域网上网行为控制方法,旨在解决传统黑白名单机制在处理海量URL数据时存储与查询效率低的问题。通过C++实现URL访问过滤功能,实验表明该方法可将内存占用降至传统方案的八分之一,查询速度提升约40%,假阳性率可控。研究为优化企业网络管理提供了新思路,并提出结合机器学习、改进哈希函数及分布式协同等未来优化方向。
270 0
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
11月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
440 12