C++实现图 - 04 最短路径

数据结构与算法专栏 —— C++实现

写在前面:
今天我们来看看图论中另一个非常重要的问题 —— 最短路径,正如其名就是要再图中找到起点到终点的最短路径,这就需要不断地去比较每条边的权值。
这一讲我们将会具体介绍迪杰斯特拉算法和弗洛伊德算法的实现。

迪杰斯特拉算法

迪杰斯特拉算法是一个单源点的一个最短路径算法,也就是说,我们这个算法会求得从一个顶点到其所有顶点的最短路径。

这个算法需要用到邻接矩阵来存储所有边值,并且需要一个辅助数组来更新最短路径,需要一个路径数组存储最短路径的结点,还需要一个状态数组来判断当前结点是否已经加入最短路径。

这样说可能会有点晕,我们还是先来看图,假设有这样一张图:

在这里插入图片描述

现在,我们要找到结点 0 到结点 8 的最短路径,步骤如下:

辅助数组 D:存储结点 0 到当前结点的最短路径权值。
路径数组 P:存储当前最短路径到该点的父结点。
状态数组 Final :记录当前结点是否已经加入到最短路径当中(0 代表还没有加入,1 代表已经加入)。

第一步:初始化我们上述提到的三个数组,将结点 0 加入数组中,并更新辅助数组即结点 0 连出去的边的权值。

在这里插入图片描述

第二步:找到当前辅助数组中的边权最小值,即结点 0 到结点 1 ,故加入结点 1 ,并且更新结点 1 连出去的边值。
可以发现之前更新过结点 2 的辅助数组,现在又需要更新,因为结点 0 到结点 2 的最短路径不再是 0 -> 2 ,而是 0 -> 1 -> 2 ,即最短路径值从 5 变为 1+3 = 4 ,并且结点 2 的 P 不再是 0 ,它在最短路中要先经过结点 1 ,故 P 改成 1 。

在这里插入图片描述

第三步:更上面一样,找到结点 2 加入最短路,并且更新。

在这里插入图片描述

第四步:找到结点 4 加入最短路,并且更新。

在这里插入图片描述

第五步:找到结点 3 加入最短路,并且更新。

在这里插入图片描述

第六步:找到结点 5 加入最短路,并且更新。

在这里插入图片描述

第七步:找到结点 6 加入最短路,并且更新。

在这里插入图片描述

第八步:找到结点 7 加入最短路,并且更新。

在这里插入图片描述

第九步:到达终点,生成最短路径。

在这里插入图片描述

全部代码

#include <bits/stdc++.h>
using namespace std;
#define MaxVertices 100	//假设包含的最大结点数
#define MaxWeight 99999	//假设两点不邻接的正无穷值

/*
9
16
0 1 1
0 2 5
1 2 3
1 3 7
1 4 5
2 4 1
2 5 7
3 4 2
4 5 3
3 6 3
4 6 6
4 7 9
5 7 5
6 7 2
6 8 7
7 8 4
*/

//定义邻接矩阵
struct AdjMarix {
	int Edge[MaxVertices][MaxVertices] = { 0 };
	int numV;	//当前顶点的个数
	int numE;	//当前边的个数
};

//构建图
void CreatGraph(AdjMarix *G) {
	int vi, vj, w;
	cout << "请输入顶点数量:" << endl;
	cin >> G->numV;
	for (int i = 0; i < G->numV; i++) {
		for (int j = 0; j < G->numV; j++) {
			G->Edge[i][j] = MaxWeight;
		}
	}
	cout << "请输入边的数量:" << endl;
	cin >> G->numE;
	cout << "请输入边的信息:" << endl;
	for (int i = 0; i < G->numE; i++) {
		cin >> vi >> vj >> w;
		G->Edge[vi][vj] = w;
		G->Edge[vj][vi] = w;
	}
}

//迪杰斯特拉算法
void Dijkstra(AdjMarix &M, int v0, int *P, int *D) {
	//初始化
	int Final[MaxVertices] = { 0 };
	for (int i = 0; i < M.numV; i++) {
		P[i] = -1;
		D[i] = M.Edge[v0][i];
	}
	//加入结点V0
	D[v0] = 0;
	Final[v0] = 1;
	
	int k = v0;
	for (int i = 0; i < M.numV; i++) {
		int MIN = MaxWeight;	//找到目前数组中权值最小的一条边
		for (int j = 0; j < M.numV; j++) {
			if (Final[j] == 0 && D[j] < MIN) {
				MIN = D[j];
				k = j;
			}
		}
		Final[k] = 1;	//标记该结点已经加入最短路径中
		for (int w = 0; w < M.numV; w++) {
			if (Final[w] == 0 && (MIN + M.Edge[k][w] < D[w])) {
				D[w] = MIN + M.Edge[k][w];	//更新最小值
				P[w] = k;	//记录路径(说明最短路径中到w的要经过k)
			}
		}
	}
}

int main() {
	AdjMarix AM;
	CreatGraph(&AM);
	int P[MaxVertices], D[MaxVertices];
	Dijkstra(AM, 0, P, D);
	cout << "V0到各个结点的最短路径:" << endl;
	for (int i = 0; i < AM.numV; i++) {
		cout << "V" << i << ":";
		int j = i;
		while (j != -1) {
			cout << "V" << j << "->";
			j = P[j];
		}
		cout << "V0	【最短路径为" << D[i] << "】" << endl;
	}
}

在这里插入图片描述

弗洛伊德算法

这个算法相对于上一个就十分暴力了,直接三层循环,遍历所有情况,每次都去试图找到一个中间结点,判断是否能使结点 i 到结点 j 经过结点 k 能缩短路径长度。这里我就直接展示代码啦,因为实质算法三层循环就搞定了。

#include <bits/stdc++.h>
using namespace std;
#define MaxVertices 100	//假设包含的最大结点数
#define MaxWeight 99999	//假设两点不邻接的正无穷值

/*
9
16
0 1 1
0 2 5
1 2 3
1 3 7
1 4 5
2 4 1
2 5 7
3 4 2
4 5 3
3 6 3
4 6 6
4 7 9
5 7 5
6 7 2
6 8 7
7 8 4
*/

//定义邻接矩阵
struct AdjMarix {
	int Edge[MaxVertices][MaxVertices] = { 0 };
	int numV;	//当前顶点的个数
	int numE;	//当前边的个数
};

//创建图
void CreatGraph(AdjMarix *G) {
	int vi, vj, w;
	cout << "请输入顶点数量:" << endl;
	cin >> G->numV;
	for (int i = 0; i < G->numV; i++) {
		for (int j = 0; j < G->numV; j++) {
			if (i == j) {
				G->Edge[i][j] == 0;
			} else {
				G->Edge[i][j] = MaxWeight;
			}
		}
	}
	cout << "请输入边的数量:" << endl;
	cin >> G->numE;
	cout << "请输入边的信息:" << endl;
	for (int i = 0; i < G->numE; i++) {
		cin >> vi >> vj >> w;
		G->Edge[vi][vj] = w;
		G->Edge[vj][vi] = w;
	}
}

//弗洛伊德算法
void Floyd(AdjMarix &M, int P[][MaxVertices], int D[][MaxVertices]) {
	for (int i = 0; i < M.numV; i++) {
		for (int j = 0; j < M.numV; j++) {
			P[i][j] = j;
			D[i][j] = M.Edge[i][j];
		}
	}
	//遍历所有结点所有边并更新,找到最短路径
	for (int i = 0; i < M.numV; i++) {
		for (int j = 0; j < M.numV; j++) {
			for (int k = 0; k < M.numV; k++) {
				int temp = (D[j][i] == MaxWeight || D[i][k] == MaxWeight) ? MaxWeight : D[j][i] + D[i][k];
				if (temp < D[j][k]) {
					D[j][k] = temp;
					P[j][k] = P[j][i];
				}
			}
		}
	}
	cout << "最小路径:" << endl;
	for (int i = 0; i < M.numV; i++) {
		for (int j = 0; j < M.numV; j++) {
			cout << P[i][j] << " ";
		}
		cout << endl;
	}
	cout << endl;
	cout << "最小路径值:" << endl;
	for (int i = 0; i < M.numV; i++) {
		for (int j = 0; j < M.numV; j++) {
			printf("%2d ", D[i][j]);
		}
		cout << endl;
	}
}

int main() {
	AdjMarix AM;
	CreatGraph(&AM);
	int P[MaxVertices][MaxVertices], D[MaxVertices][MaxVertices];
	Floyd(AM, P, D);
}

在这里插入图片描述

如果大家有什么问题的话,欢迎在下方评论区进行讨论哦~

【上一讲】图 - 03 最小生成树
【下一讲】图 - 05 拓扑排序

  • 3
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的C代码实现bfs迷宫问题最短路径: ``` #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #define MAX_ROW 10 #define MAX_COL 10 typedef struct { int row, col, parent; } Node; Node queue[MAX_ROW * MAX_COL]; int head = 0, tail = 0; char maze[MAX_ROW][MAX_COL+1] = { "#S######.#", "......#..#", ".#.##.##.#", ".#........", "##.##.####", "....#....#", ".######.#.", "........#.", ".#######.#", "....#...G#" }; const int dr[] = {-1, 0, 1, 0}; const int dc[] = {0, 1, 0, -1}; void enqueue(Node n) { queue[tail++] = n; } Node dequeue() { return queue[head++]; } bool is_empty() { return head == tail; } void print_path(Node n) { if (n.parent == -1) { printf("(%d, %d)\n", n.row, n.col); } else { print_path(queue[n.parent]); printf("(%d, %d)\n", n.row, n.col); } } void bfs() { Node start = {0, 1, -1}; enqueue(start); maze[start.row][start.col] = '#'; while (!is_empty()) { Node current = dequeue(); if (maze[current.row][current.col] == 'G') { print_path(current); return; } for (int i = 0; i < 4; i++) { int nr = current.row + dr[i]; int nc = current.col + dc[i]; if (nr < 0 || nc < 0 || nr >= MAX_ROW || nc >= MAX_COL) { continue; } if (maze[nr][nc] == '#' || maze[nr][nc] == 'S') { continue; } Node next = {nr, nc, head-1}; enqueue(next); maze[nr][nc] = '#'; } } } int main() { bfs(); return 0; } ``` 这个代码使用了一个队列来实现bfs。在每次循环中,它从队列中取出一个节点,检查它是否是终点(G),如果是,就打印路径并结束程序。否则,它会检查节点周围的四个邻居,如果邻居是可通行的,就将它们加入队列中。如果队列为空,就说明没有从起点到达终点的路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值