狄克斯特拉算法-自带注释

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

 

上一篇:OpenStack iaas-install-swift-controller.sh


下一篇:STM32(三十四)向flash中写64字数据