目录
Dijkstra算法的策略
设置集合S存放已被访问的顶点,然后执行n次下面的两个步骤:
- 每次从未被访问的顶点集合V-S中选择与起点s的最短距离最小的一个顶点(记为u),访问并加入集合S。
- 之后,令顶点u为中介点,优化起点s与所有从u能到达的顶点v之间的最短距离。
Dijkstra算法的具体实现
- 集合S可以用一个bool型数组visit[]来实现,即当visit[i] == true时,表示顶点Vi(0 ≤ i ≤ n - 1)已被访问,当visit[i] == false时,表示顶点Vi未被访问。
- 令int型数组dis[]表示起点s到达顶点Vi的最短距离,初始时除了起点s的dis[s]赋为0,其余顶点都赋为一个很大的数(初学者可以用1000000000,即10^9;稍微懂点二进制编码的话可以使用十六进制0x3fffffff,但不要使用0x7fffffff,因为两个这样的数相加可能会超过int的表示范围)来表示inf,即不可达。
Dijkstra算法的伪代码
Dijkstra(G, dis[], s) {
初始化;
for (循环n次) {
u = 使dis[u]最小的还未被访问的顶点的标号;
记u已被访问;
for (从u出发能到达的所有顶点v)
if (v未被访问 && 以u为中介点使s到达顶点v的最短距离dis[v]更优)
优化dis[v];
}
}
Dijkstra算法的代码模板
全局变量
#define false 0
#define true 1
typedef int bool;
const int MaxV = 1000; // 最大顶点数
const int INF = 1000000000; // 设INF为一个很大的数
邻接矩阵版
int n, G[MaxV][MaxV]; // n为顶点数,MaxV为最大顶点数
int dis[MaxV]; // 起点到达各点的最短路径长度
bool visit[MaxV] = {false}; // 标记数组,visit[i] == true表示已访问. 初值均为false
void Dijkstra(int s) { // s为起点
/*
* 初始化
* 1. 将整个dis数组赋为INF
* 2. 起点s到达自身的距离为0
* */
for (int i = 0; i < MaxV; ++i)
dis[i] = INF;
dis[s] = 0;
/*
* Dijkstra算法
* 1. 设u为dis[u]最小的且还未被访问的顶点的标号;
* 2. 记u已被访问
* 3. 以u为中介点,优化所有u能到达且未被访问过的顶点的路径
* */
for (int i = 0; i < n; ++i) { // 循环n次
int u = -1, MIN = INF; // u使得d[u]最小,MIN存放该最小的dis[u]
for (int j = 0; j < n; ++j) {
// 找到未访问的顶点中dis[u]最小的
if (visit[j] == false && dis[j] < MIN) {
u = j;
MIN = dis[j];
}
}
// 找不到小于INF的dis[u],说明剩下的顶点和起点s不连通
if (u == -1) return;
visit[u] = true; // 标记u为已访问
for (int v = 0; v < n; ++v)
// 如果v未访问 && u能到达v && 以u为中介点可以使dis[v]更优
if (visit[v] == false && G[u][v] != INF && dis[v] > dis[u] + G[u][v])
dis[v] = dis[u] + G[u][v]; // 优化dis[v]
}
}
求最短路径
伪代码部分
- 设置数组pre[],令pre[v]表示从起点s到顶点v的最短路径上v的前一个顶点(即前驱结点)的编号。
- 这样,以u为中介点,当dis[v]可以被优化时,就可以将u赋给pre[v],最终就能把最短路径上每一个顶点的前驱结点记录下来。
if (v未被访问 && 以u为中介点使s到达顶点v的最短距离dis[v]更优){
优化dis[v];
令v的前驱为u;
}
求最短路径代码模板
// 省略
int pre[MaxV]; // pre[v]表示从起点到顶点v的最短路径上v的前一个顶点(新添加)
void Dijkstra(int s) { // s为起点
/*
* 初始化
* 1. 将整个dis数组赋为INF
* 2. 起点s到达自身的距离为0
* 3. 将pre数组中每个元素的前驱设为自身(新添加)
* */
// 省略
// 初始状态设每个点的前驱为自身(新添加)
for (int i = 0; i < n; ++i)
pre[i] = i;
/*
* Dijkstra算法
* 1. 设u为dis[u]最小的且还未被访问的顶点的标号;
* 2. 记u已被访问
* 3. 以u为中介点,优化所有u能到达且未被访问过的顶点的路径
* 4. 记录可优化顶点的前驱为u(新添加)
* */
// 省略
for (int v = 0; v < n; ++v)
// 如果v未访问 && u能到达v && 以u为中介点可以使dis[v]更优
if (visit[v] == false && G[u][v] != INF && dis[v] > dis[u] + G[u][v]) {
dis[v] = dis[u] + G[u][v]; // 优化dis[v]
pre[v] = u; // 记录v的前驱顶点是u(新添加)
}
}
// 递归求最短路径
void DFS(int s, int v) { // s为起点编号,v为当前访问的顶点编号(从终点开始递归)
if (v == s) { // 如果当前已经达到顶点s,则输出起点并返回
printf("%d\n", s);
return;
}
DFS(s, pre[v]); // 递归访问v的前驱结点pre[v]
printf("%d\n", v); // 从起点返回后,输出每一层的顶点号
}
第二标尺
当起点到终点的最短距离最小的路径不止一条时,题目会给出一个第二标尺(第一标尺是距离),要求在所有最短路径中选择第二标尺最优的一条路径。第二标尺常见的是一下三种出题方法或其组合:
- 给每条边增加一个边权,然后在最短路径有多条时,要求路径上的边权之和最大或最小。
- 给每个点增加一个点权,然后在最短路径有多条时,要求路径上的点权之和最大或最小。
- 直接问有多少条最短路径。
对这三种出题方法,都只需要增加一个数组来存放新增的边权或点权或最短路径条数,然后在Dijkstra算法中修改优化dis[v]的那个步骤即可,其他部分不需要改动。
新增边权
- 以新增边权代表花费为例,用cost[u][v]表示u→v的花费(由题目输入)
- 并增加一个数组c[],令起点s到达顶点u的最少花费为c[u],初始化时只有c[s]为0、其余c[u]均为INF。
// 省略
int cost[MaxV][MaxV];
int c[MaxV];
void Dijkstra(int s) {
/*
* 初始化
* */
// 省略
for (int u = 0; u < n; ++u)
c[u] = INF;
c[s] = 0;
/*
* Dijkstra算法
* */
// 省略
for (int v = 0; v < n; ++v) {
// 如果v未访问 && u能到达v
if (visit[v] == false && G[u][v] != INF) {
if (dis[v] > dis[u] + G[u][v]) { // 以u为中介点可以使dis[v]更优
// 优化dis[v]和c[v]
dis[v] = dis[u] + G[u][v];
c[v] = c[u] + cost[u][v];
} else if (dis[v] == dis[u] + G[u][v] && c[v] > c[u] + cost[u][v]) {
c[v] = c[u] + cost[u][v]; // 最短距离相同时优化c[v]
}
}
}
}
新增点权
- 以新增点权代表物资为例,用weight[u]表示城市u中的物资数目(由题目输入)
- 并增加一个数组w[],令起点s到达顶点u可以收集到的最大物资为w[u],初始化时只有w[s]为weight[s]、其余w[u]均为0。
// 省略
int weight[MaxV];
int w[MaxV];
void Dijkstra(int s) {
/*
* 初始化
* */
// 省略
for (int u = 0; u < n; ++u)
w[u] = 0;
w[s] = weight[s];
/*
* Dijkstra算法
* */
// 省略
for (int v = 0; v < n; ++v) {
// 如果v未访问 && u能到达v
if (visit[v] == false && G[u][v] != INF) {
if (dis[v] > dis[u] + G[u][v]) { // 以u为中介点可以使dis[v]更优
// 优化dis[v]和w[v]
dis[v] = dis[u] + G[u][v];
w[v] = w[u] + weight[v];
} else if (dis[v] == dis[u] + G[u][v] && w[v] < w[u] + weight[v]) {
w[v] = w[u] + weight[v]; // 最短距离相同时优化w[v]
}
}
}
}
求最短路径条数
- 只需要增加一个数组num[],令从起点s到达顶点u的最短路径条数为num[u],初始化时只有num[s]为1、其余nun[u]均为0。
- 以u为中介点,当dis[v]可被优化时,s→v的最短路径条数等于s→u的最短路径条数;当s→v(不经过u)的距离和s→u→v(以u为中介点)的距离相同时,s→v的最短路径条数 = s→v的最短路径条数(不经过u)+ s→u的最短路径条数。
// 省略
int num[MaxV];
void Dijkstra(int s) {
/*
* 初始化
* */
// 省略
for (int u = 0; u < n; ++u)
num[u] = 0;
num[s] = 1;
/*
* Dijkstra算法
* */
// 省略
for (int v = 0; v < n; ++v) {
// 如果v未访问 && u能到达v
if (visit[v] == false && G[u][v] != INF) {
if (dis[v] > dis[u] + G[u][v]) { // 以u为中介点可以使dis[v]更优
dis[v] = dis[u] + G[u][v]; // 优化dis[v]
num[v] = num[u]; // num[v]继承num[u]
} else if (dis[v] == dis[u] + G[u][v]) {
num[v] += num[u]; // 最短距离相同时累加num
}
}
}
}