迪杰斯特拉算法: 求单源最短路径,即给定图G(V,E)和起点S,求起点S到达其他各个点的最短路径。
算法策略
(1)每次从集合中(图的各个节点) 选择与起点S距离最小且未访问的节点,记为u,访问u,并将其加入已访问的集合。
(2)将结点u作为中介点,访问其能到达的其他结点。如果以u作为中介点,从起点S到达其他结点的距离小于当前记录的起点S到达其他结点的距离,则进行路径长度的优化。(初始化起点S到达其他结点的距离为无穷大,代码中设为一个很大的数)
(3)回到步骤1,继续寻找下一个未访问的且距离最小的节点。
算法的策略类似一种贪心的思想,每次都访问最短的路径,而且如果以这个最短的路径为中介,可以优化起点到其他节点的距离,则将以此路径作为中介,更新起点到其他点的距离。
接下来是代码实现,分为两种类型,根据图的不同实现来区分。一种是邻接表类型,另一种是邻接矩阵类型。
1、使用邻接矩阵实现图,进行Dijkstra算法的实现
#include <vector>
#include <iostream>
using namespace std;
const int MAXV = 1000; // 初始化节点数量
const int INF = 1000000000; // 初始化距离为很大的一个数
int n, G[MAXV][MAXV]; // 图的邻接矩阵实现
int d[MAXV]; // 存储起点到各个点的最短路径长度
bool vis[MAXV] = { false }; // 标记结点是否被访问
void Dijkstra(int s) {
fill(d, d + MAXV, INF); // 初始化距离为 “无穷大”
d[s] = 0; // 起点到自身的距离为0
for (int i = 0; i < n; ++i) {
// 找到路径最短的且还未访问的节点
int u = -1, mid_dis = INF;
for (int j = 0; j < n; ++j) {
if (vis[j] == false && d[j] < mid_dis) {
u = j;
mid_dis = d[j];
}
}
// 如果找不到节点访问,则说明剩下的节点和当前起点不通
if (u == -1) return;
// 标记节点被访问过
vis[u] = true;
// 遍历所有的节点, 优化路径长度
for (int v = 0; v < n; ++v) {
// 如果 v 未被访问 且 u能够到达v 且 以u为中介点可以使d[v]更优
if (vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
}
}
}
}
2、使用邻接表实现图,进行Dijkstra算法的实现
#include <vector>
#include <iostream>
using namespace std;
// 图的节点结构体
struct node {
int v; // 边的目标顶点
int dis; // 两个顶点之间的边权值
};
int n; // 结点个数
int d[MAXV]; // 存储起点到其他节点的最短路径
vector<node> adj[MAXV]; // 图的邻接表实现
bool vis[MAXV] = { false }; // 存储已访问过的结点
void Dijkstra(int s) {
fill(d, d + MAXV, INF);
d[s] = 0; // 起点
for (int i = 0; i < n; ++i) {
// 找到未访问结点中距离最小的结点
int u = -1, min_dis = INF;
for (int j = 0; j < n; ++j) {
if (vis[j] == false && d[j] < min_dis) {
u = j;
min_dis = d[j];
}
}
// 如果没有找到,说明剩余结点和当前起点不连通
if (u == -1) return;
// 找到后,标记结点为已访问
vis[u] = true;
// 遍历当前结点能到达的其他结点
for (int j = 0; j < adj[u].size(); ++j) {
int v = adj[u][j].v;
if (vis[v] == false && d[u] + adj[u][j].dis < d[v]) {
d[v] = d[u] + adj[u][j].dis; // 最短距离优化
}
}
}
}
如果是无向边,则在图的实现的时候,进行双向填充即可:
// 邻接矩阵
G[u][v] = G[v][u] = x; // x = 权值
// 邻接表
adj[u].emplace_back(v);
adj[v].emplace_back(u);
注意:如果图中边权值出现负值,Dijkstra算法就会失效,因为这样会无法保证边的权值随着访问始终在增大,可能访问了很多节点后,权值却为0。这个时候可以使用Bellman-Ford算法和SPFE算法。
如有不足,请指出。
谢谢阅读。
参考《算法笔记》