Dijkstra算法及其C++实现
时间:2022-07-23
本文章向大家介绍Dijkstra算法及其C++实现,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
Dijkstra算法及其C++实现
什么是最短路径问题
如果从图中某一顶点(称为源点)到达另一顶点(称为终点)的路径可能不止一条,如何找到一条路径使得沿此路径上各边上的权值总和达到最小。
单源最短路径问题是指对于给定的图
,求源点
到其它顶点
的最短路径。
Dijkstra算法
Dijkstra算法用于计算一个节点到其他节点的最短路径。Dijkstra是一种按路径长度递增的顺序逐步产生最短路径的方法,是一种贪婪算法。
Dijkstra算法的核心思想是首先求出长度最短的一条最短路径,再参照它求出长度次短的一条最短路径,依次类推,直到从源点
到其它各顶点的最短路径全部求出为止。
具体来说:图中所有顶点分成两组,第一组是已确定最短路径的顶点,初始只包含一个源点,记为集合
;第二组是尚未确定最短路径的顶点,记为集合
。
按最短路径长度递增的顺序逐个把
中的顶点加到
中去,同时动态更新
集合中源点到各个顶点的最短距离,直至所有顶点都包括到
中。
实现思路
- 初始时,
集合只包含起点
;
集合包含除
外的其他顶点
,且
中顶点的距离为起点
到该顶点的距离。(
中顶点
的距离为
的长度,如果
和
不相邻,则
的最短距离为
)
- 从
中选出距离最短的顶点
,并将顶点
加入到
中;同时,从
中移除顶点
。
- 更新
中各个顶点
到起点
的距离以及最短路径中当前顶点的前驱顶点。之所以更新
中顶点的距离以及前驱顶点是由于上一步中确定了
是求出最短路径的顶点,从而可以利用
来更新
中其它顶点
的距离,因为存在
的距离可能大于
距离的情况,从而也需要更新其前驱顶点
- 重复步骤(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: %dt 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
- 《资讯》霍金:人工智能的威胁就像核武器,世界将发生10大变化!
- [原创]WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿
- 厉害了,连美图CEO都开始热捧区块链了!
- Silverlight制作逐帧动画
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(58)-DAL层重构
- 糟糕了!这次新版微信,要干死所有小游戏了!
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(59)-BLL层重构
- 除了奇奇怪怪的机器人们,2017年人工智能还干了哪些“蠢事”?
- 区块链搬砖的坑及有效鉴别方法
- 英伟达回应禁令:研究人员放心用不更新驱动就没影响
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(88)-Excel导入和导出-主从表结构导出
- WCF中的Binding模型之六(完结篇):从绑定元素认识系统预定义绑定
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(57)-插件---ueditor使用
- 何为正则表达式?要他有何用?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Java多线程相关知识点扩展实例分析
- 巧用 Nsenter 调取宿主机工具调试容器内程序
- 如何在 Kubernetes 集群中集成 Kata
- Pytest实战
- Native 与 Weex 交互通用解决方案
- 20个MySQL运维案例,请查收!
- Unity3D中使用Joystick Pack实现摇杆控制
- Unity3D使用Timeline实现过场动画
- Oracle中ascii为0的陷阱
- VBA解析VBAProject 05——提取模块代码
- VBA解析VBAProject 06——清除VBA工程密码
- VBA解析VBAProject 07——隐藏模块
- python测试开发django-83.Dockerfile部署django项目
- python测试开发django-82.线上部署设置DEBUG=FALSE
- BCEL ClassLoader去哪了