狄克斯特拉算法-自带注释
Posted 卡尔曼和玻尔兹曼谁曼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了狄克斯特拉算法-自带注释相关的知识,希望对你有一定的参考价值。
#include <cstdio>
#include <iostream>
#include <list>
#include <stack>
#include <tuple>
#include <vector>
#include <cassert>
using std::list;
using std::tuple;
using std::vector;
using std::stack;
using std::printf;
using Matrix = vector<vector<uint32_t>>; // 图的邻接矩阵(使用嵌套的vector表示)
using ENode = tuple<uint32_t, uint32_t, uint32_t>; // 图中的每个结点信息(顶点编号,当前顶点到起始点的最短距离,最短路径中当前结点的上一个结点)
using SNodes = vector<ENode>; // 已计算出最短路径的顶点集合S(类似一个动态数组)
using UNodes = list<ENode>; // 未进行遍历的顶点集合U(使用list结点删除操作)
/// <summary>
/// 从未遍历的U结点结合中找到下一个离起始结点距离最近的结点
/// </summary>
/// <param name="nodes">未遍历的结点集合U</param>
/// <returns>下一个离起始结点距离最近的结点</returns>
ENode searchNearest(const UNodes& nodes) {
uint32_t minDistance = UINT32_MAX;
ENode nearest;
for (const auto& node : nodes) {
if (std::get<1>(node) <= minDistance) {
minDistance = std::get<1>(node);
nearest = node;
}
}
return nearest;
}
/// <summary>
/// Dijkstra最短路径算法实现
/// </summary>
/// <param name="graph">图的邻接矩阵</param>
/// <param name="startNodeIndex">起始结点编码</param>
/// <returns>返回集合中是按每个结点到起始点最短距离的排列</returns>
SNodes dijkstra(const Matrix& graph, uint32_t startNodeIndex) {
const uint32_t numOfNodes = graph.size(); // 图中结点的个数
SNodes visitedNodes; // 已经计算出最短路径的结点的集合S
UNodes unvisitedNoes; // 未计算出最短路径的结点的集合
// 对S和U集合进行初始化,起始结点到其实结点的距离为0,其他结点不直达起始结点的距离为无穷大
// 最短路径中当前结点的上一个结点初始化为起始结点,之后会逐步修正
for (size_t i = 0; i < numOfNodes; ++i) {
if (i == startNodeIndex) visitedNodes.emplace_back(i, 0, startNodeIndex);
else unvisitedNoes.emplace_back(i, graph[startNodeIndex][i], startNodeIndex);
}
while (!unvisitedNoes.empty()) {
// 从U中找出距离到起始点距离最短的结点,加入集合S,同时从U中删除该结点
auto nextNode = searchNearest(unvisitedNoes);
visitedNodes.push_back(nextNode);
unvisitedNoes.erase(std::find(unvisitedNoes.begin(), unvisitedNoes.end(), nextNode));
// 更新集合U中各个结点到起始点的最短距离以及最短路径中的上一个顶点
for (auto& node : unvisitedNoes) {
// 更新规则是起始点到当前最短距离结点(nextNode)的距离加上当前结点到U集合中各个结点的距离小于原来U集合中各个结点到起始点的距离
// 更新最短距离的同时还需要更新最短路径中上一个结点为nextNode
if (graph[std::get<0>(nextNode)][std::get<0>(nextNode)] != UINT32_MAX) {
auto newDistance = graph[std::get<0>(nextNode)][std::get<0>(nextNode)] + std::get<1>(nextNode);
if (newDistance < std::get<1>(node)) {
std::get<1>(node) = newDistance;
std::get<2>(node) = std::get<0>(nextNode);
}
}
}
}
return visitedNodes;
}
void printPathInfo(const SNodes& paths) {
stack<uint32_t> tracks; // 从尾部出发,使用stack将每个结点的最短路径中的前一个结点入栈,然后出站的顺序就是最短路径顺序
// 第一个结点是起始点,从第二个元素开始输出
for (auto it = ++paths.begin(); it != paths.end(); ++it) {
// 输出起点开始信息
printf("%c -> %c:\\t Length: %d\\t Paths: %c",
char(std::get<0>(paths[0]) + 65),
char(std::get<0>(*it) + 65),
std::get<1>(*it),
char(std::get<0>(paths[0]) + 65));
auto pointer = *it;
// 如果当前指针pointer指向的结点的前驱结点不是起始点,要以此寻找前驱结点直到起始点
while (std::get<2>(pointer) != std::get<0>(paths[0])) {
tracks.push(std::get<0>(pointer));
// Lambda表达式,使用find_if函数把当前结点的前驱结点从paths中找出并继续寻找该结点的前驱结点直到起始点
auto condition = [pointer](ENode x) { return std::get<0>(x) == std::get<2>(pointer); };
pointer = *std::find_if(paths.begin(), paths.end(), condition);
}
tracks.push(std::get<0>(pointer));
// 以出站顺序进行打印输出
while (!tracks.empty()) {
printf(" -> %c", char(tracks.top() + 65));
tracks.pop();
}
printf("\\n");
}
}
int main() {
Matrix graph = {
{0, 12, UINT32_MAX, UINT32_MAX, UINT32_MAX, 16, 14},
{12, 0, 10, UINT32_MAX, UINT32_MAX, 7, UINT32_MAX},
{UINT32_MAX, 10, 0, 3, 5, 6, UINT32_MAX},
{UINT32_MAX, UINT32_MAX, 3, 0, 4, UINT32_MAX, UINT32_MAX},
{UINT32_MAX, UINT32_MAX, 5, 4, 0, 2, 8},
{16, 7, 6, UINT32_MAX, 2, 9, 9},
{14, UINT32_MAX, UINT32_MAX, UINT32_MAX, 8, 9, 0}
}; // 图对应的邻接矩阵
for (size_t i = 0; i < graph.size(); ++i) {
for (size_t j = 0; j < i; ++j) {
assert(graph[i][j] == graph[j][i]);
}
}
auto results = dijkstra(graph, uint32_t('D' - 65)); // 选取结点D为起始点(大写字母A的ASCII编码为65)
printPathInfo(results);
return 0;
}
以上是关于狄克斯特拉算法-自带注释的主要内容,如果未能解决你的问题,请参考以下文章