最短路径问题

1. 最短路径问题

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

根据有向网或无向网中各边权值的取值情形及问题求解的需要,最短路径问题分为以下三种情形,分别用不同的算法求解: 

  • 求单源最短路径(边的权值非负) - Dijkstra 算法,所谓单源最短路径就是固定一个顶点为源点,求源点到其他每个顶点的最短路径;
  • 求单源最短路径(边的权值允许为负值, 但不存在负权值回路) - Bellman-Ford 算法
  • Bellman-Ford 算法的改进 - SPFA 算法
  • 求所有顶点之间的最短路径(边的权值允许为负值,但不存在负权值回路) - Floyd算法 

2. Dijkstra 算法 

2.1 算法思想 

给定一个带权有向图(即有向网) G 和源点 v0,求 v0 G 中其他每个顶点的最短路径。限定各边上的权值大于或等于 0 

例如,在图 4.1(a)中,假设源点为顶点 0,则源点到其他顶点的最短距离分别为:

  • 顶点 0 到顶点 1 的最短路径距离是: 20,其路径为 v0v2v1
  • 顶点 0 到顶点 2 的最短路径距离是: 5,其路径为 v0v2
  • 顶点 0 到顶点 3 的最短路径距离是: 22,其路径为 v0v2v5v3
  • 顶点 0 到顶点 4 的最短路径距离是: 28,其路径为 v0v2v1v4
  • 顶点 0 到顶点 5 的最短路径距离是: 12,其路径为 v0v2v5

这些最短路径距离及对应的最短路径是怎么求出来的? 

为求得这些最短路径, Dijkstra 提出按路径长度的递增次序,逐步产生最短路径的算法。首先求出长度最短的一条最短路径,再参照它求出长度次短的一条最短路径,依次类推,直到从源点v0 到其它各顶点的最短路径全部求出为止。 

Dijkstra 算法里, 为了求源点 v0 到其他各顶点 vi 的最短路径及其长度, 需要设置 3 个数组: 

  • dist[n]dist[i]表示当前找到的从源点 v0 到终点 vi 的最短路径的长度,初始时, dist[i]Edge[v0][i],即邻接矩阵的第 v0  
  • S[n]S[i]0 表示顶点 vi 还未加入到集合 S 中, S[i]1 表示 vi 已经加入到集合 S 中。初始时, S[v0]1,其余为 0,表示最初集合 S 中只有顶点 v0 
  • path[n]path[i]表示 v0 vi 的最短路径上顶点 vi 的前一个顶点序号。采用“倒向追踪”的方法,可以确定 v0 到顶点 vi 的最短路径上的每个顶点。 

Dijkstra 算法里,重复做以下 3 步工作:

  • 在数组 dist[n]里查找 S[i] != 1,并且 dist[i]最小的顶点 u
  • S[u]改为 1,表示顶点 u 已经加入进来了;
  • 修改 T 集合中每个顶点 vk dist path 数组元素值: 当 S[k] != 1, 且顶点 u 到顶点 vk有边(Edge[u][k]<MAX),且 dist[u] + Edge[u][k] < dist[k],则修改 dist[k]dist[u] + Edge[u][k],修改 path[k]u 

利用 Dijkstra 算法求上图(a)中顶点 0 到其他各顶点的最短路径长度,并输出对应的最短路径 :

分析:

  • Dijkstra 算法的 3 个数组: Sdistpath 初始状态如下图(a)所示(源点为顶点 0):
  • S[0]1,其余为 0表示初始时, S 集合中只有源点 v0,其他顶点都是在集合 T
  • dist 数组各元素的初始值就是邻接矩阵的第 v0 行对应的元素值;
  • path 数组中, path[2]path[3]0表示当前求得的顶点 0 到顶点 2 的最短路径上,前一个顶点是顶点 0顶点 0 到顶点 3 的最短路径上,前一个顶点也是 0;其余元素值均为-1,表示顶点 0 到其余顶点没有直接路径。 

Dijkstra 算法执行过程中,以上 3 个数组各元素值的变化如图(b)(f)所示。以图(b)为例加以解释。首先在 dist 数组的各元素 dist[t]中,选择一个满足 S[t]0、且 dist[t]取最小值,求得的最小值为 5,用粗体、斜体标明,对应顶点 v2然后将 S[2]修改成 1,用粗体、下划线标明,表示将顶点 v2 加入到集合 S 中;最后修改 dist 数组和 path 数组中某些元素的值: 

  • 修改条件是: S[k]0,顶点 v2 到顶点 vk 有直接路径,且 dist[2] + Edge[2][k] < dist[k] 
  • 修改方法是:将 dist[k]修改成 dist[2] + Edge[2][k],将 path[k]修改成 2。所有修改过的值用粗体、下划线标明。 

当顶点 0 到其他各顶点的最短路径长度求解完毕后, 如何根据 path 数组求解顶点 0 到其他各顶点 vk 的最短路径?方法是:path[k]开始,采用“倒向追踪”方法,一直找到源点 v0

  • k 4, 则因为 path[4] = 1, 说明最短路径上顶点 4 的前一个顶点是顶点 1path[1] = 2,说明顶点 1 的前一个顶点是顶点 2path[2] = 0,说明顶点 2 的前一个顶点是顶点 0,这就是源点; 所以从源点 v0 到终点 v4 的最短路径为: v0v2v1v4, 最短路径长度为 dist[4] = 28 

2.2 算法代码

#include <iostream>


#define INF 10000
#define MAXL 21


using namespace std;

int n, m;

int Edge[MAXL][MAXL];

int S[MAXL];
int path[MAXL];
int dist[MAXL];


void Dijkstra(int v0) {
	for (int i = 0; i < n; i++) { // 初始化三个数组
		S[i] = 0;
		dist[i] = Edge[v0][i];
		if (i != v0 && dist[i] < INF) {
			path[i] = v0;
		}
		else {
			path[i] = -1;
		}
	}

	S[0] = 1;
	dist[v0] = 0;

	for (int i = 0; i < n - 1; i++) { // 从顶点 v0 确定 n-1 条最短路径
		int Min = INF;
		int u = v0;
		for (int j = 0; j < n; j++) { // 选择当前集合 T 中具有最短路径的顶点 u
			if (!S[j] && dist[j] < Min) {
				u = j;
				Min = dist[j];
			}
		}

		S[u] = 1; // 将顶点 u 加入到集合 S,表示它的最短路径已求得

		for (int k = 0; k < n; k++) { // 修改 T 集合中顶点的 dist 和 path 数组元素值
			if (!S[k] && Edge[u][k] < INF && Edge[u][k] + dist[u] < dist[k]) {
				dist[k] = Edge[u][k] + dist[u];
				path[k] = u;
			}
		}
	}
}

int main() {
	int u, v, w;

	memset(Edge, 0, sizeof(Edge));

	cin >> n >> m;

	for (int i = 0; i < m; i++) {
		cin >> u >> v >> w;
		Edge[u][v] = w; // 有向图
	}

	for (int i = 0; i < n; i++) { // 初始化Edge矩阵
		for (int j = 0; j < n; j++) {
			if (i == j) {
				Edge[i][j] = 0;
			}
			else if (Edge[i][j] == 0) {
				Edge[i][j] = INF;
			}
		}
	}

	Dijkstra(0);

	for (int i = 1; i < n; i++) {
		cout << dist[i] << endl;
	}

	return 0;
}

 

posted @   小熊酱  阅读(128)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
点击右上角即可分享
微信分享提示