赞
踩
const int MAXN = 1000;
const int INF = 0x3fffffff;int n, G[MAXN][MAXN]; // n 为顶点数,MAXN 为最大顶点数
int d[MAXN]; //起点到达各点的最短路径长度
bool vis[MAXN] = { false }; //标记数组,标记顶点是否被访问过void dijkstra(int s) { // s 为起点
fill(d, d + MAXN, INF); // 初始化 d 数组为 INF
d[s] = 0; //起点 s 到达自身的距离为 0
for (int i = 0; i < n; i++) { //循环 n 次
int u = -1, MIN = INF; // u 使 d[u] 最小,MIN 存放该最小的 d[u]
for (int j = 0; j < n; j++) //找到未访问的顶点中 d[] 最小的
if (!vis[j] && d[j] < MIN) { u = j; MIN = d[j]; }
if (u == -1) return; //找不到小于 INF 的 d[u],说明剩下的顶点和起点 s 不连通
vis[u] = true; //标记 u 为已访问
for (int v = 0; v < n; u++)
//如果 v 未访问,且 u->v 连通,且以 u 为中介点可使 d[v] 更优
if (!vis[u] && G[u][v] != INF && d[u] + G[u][v] < d[v])
d[v] = d[u] + G[u][v]; //优化 d[v]
}
}
const int MAXN = 1000;
const int INF = 0x3fffffff;struct node { int v, dis; };
vector <node> adj[MAXN]; // adj[u] 存放从顶点 u 出发可以到达的所有顶点
int n; // n 为顶点数
int d[MAXN]; //起点到达各点的最短路径长度
bool vis[MAXN] = { false }; //标记数组,标记顶点是否被访问过void dijkstra(int s) { // s 为起点
fill(d, d + MAXN, INF); // 初始化 d 数组为 INF
d[s] = 0; //起点 s 到达自身的距离为 0
for (int i = 0; i < n; i++) { //循环 n 次
int u = -1, MIN = INF; // u 使 d[u] 最小,MIN 存放该最小的 d[u]
for (int j = 0; j < n; j++) //找到未访问的顶点中 d[] 最小的
if (!vis[j] && d[j] < MIN) { u = j; MIN = d[j]; }
if (u == -1) return; //找不到小于 INF 的 d[u],说明剩下的顶点和起点 s 不连通
vis[u] = true; //标记 u 为已访问
for (int j = 0; j < adj[u].size(); j++) {
int v = adj[u][j].v; //通过邻接表直接获得 u 能到达的顶点 v
//如果 v 未访问,且以 u 为中介点可使 d[v] 更优
if (!vis[v] && d[u] + adj[u][j].dis < d[v])
d[v] = d[u] + adj[u][j].dis; //优化 d[v]
}
}
}
一般情况下,如果有两条及以上可以达到最短距离的路径,题目会给出一个第二标尺(第一标尺是距离),要求在所有最短路径中选择第二标尺最优的一条路径。而第二标尺常见的是这三种情况或其组合:第一种,给每条边再增加一个边权(比如花费),然后要求在最短路径有多条时要求路径上的花费之和最小(如果边权是其他含义,也可以是最大);第二种,给每个点增加一个点权(例如每个城市能收集到的物资),然后在最短路径有多条时要求路径上的点权之和最大(如果点权是其他含义的话也可以是最小);第三种,直接问有多少条最短路径。对于这三种出题方法,都只需要增加一个数组来存放新增的边权或点权或最短路径条数,然后在优化 d[v] 的那个步骤中修改增加的数组即可。
这里给出一种通用的模板化的解决上述三种情况的最短路径算法:Dijkstra + DFS。具体是先在 Dijkstra 算法中记录下所有最短路径(只考虑距离),然后从这些最短路径中选出一条第二标尺最优的路径。完整的参考代码如下。
const int MAXN = 1000;
const int INF = 0x3fffffff;第一步,使用 Dijkstra 算法记录所有的最短路径
int n, G[MAXN][MAXN];
int d[MAXN];
bool vis[MAXN] = { false };
vector <int> pre[MAXN];void dijkstra(int s) {
fill(d, d + MAXN, INF);
d[s] = 0;
for (int i = 0; i < n; i++) {
int u = -1, MIN = INF;
for (int j = 0; j < n; j++)
if (!vis[j] && d[j] < MIN) { u = j; MIN = d[j]; }
if (u == -1) return;
vis[u] = true;
for (int v = 0; v < n; v++)
if (!vis[v] && G[u][v] != INF) {
if (d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v]; //优化 d[v]
pre[v].clear(); //清空 pre[v]
pre[v].push_back(u); //令 v 的前驱为 u
}
else if (d[u] + G[u][v] == d[v]) {
pre[v].push_back(u); //令 v 的前驱为 u
}
}
}
}第二步,遍历所有最短路径,找出一条使第二标尺最优的路径
int opt_value; //第二标尺最优值
int st; //路径的起点
vector <int> path, temp_path; //最优路径,临时路径void DFS(int v) {
if (v == st) { //如果到达了路径起点
temp_path.push_back(v); //将起点加入临时路径 temp_path 的最后面
int value = 0; //存放临时路径 temp_path 的第二标尺的值
计算路径 temp_path 上的 value 值
if (value 优于 opt_value) {
opt_value = value; //更新第二标尺最优值
path = temp_path; //更新最优路径
}
temp_path.pop_back(); //将刚加入的结点删除
return;
}
//递归式
temp_path.push_back(v); //将当前访问结点加入临时路径 temp_path 的最后面
for (int i = 0; i < pre[v].size(); i++)
DFS(pre[v][i]); //结点 v 的前驱结点 pre[v][i],递归
temp_path.pop_back(); //遍历完所有前驱结点,将当前结点 v 删除
}
struct node { int v, dis; }; // v 为邻接边的目标顶点, dis 为邻接边的边权
int n; // n 为顶点数,MAXN 为最大顶点数
vector <node> adj[MAXN]; //图的邻接表
int d[MAXN]; //起点到达各点的最短路径长度bool bellman(int s) { // s 为源点
fill(d, d + MAXN, INF);
d[s] = 0; //起点 s 到达自身的距离为 0
//以下为求解数组 d 的部分
for (int i = 0; i < n - 1; i++) { //执行 n-1 轮操作,n 为顶点数
for (int u = 0; u < n; u++) { //每轮操作都遍历所有边
for (int j = 0; j < adj[u].size(); j++) {
int v = adj[u][j].v; //邻接边的顶点
int dis = adj[u][j].dis; //邻接边的边权
if (d[u] + dis < d[v]) //以 u 为中介点可以使 d[v] 更小
d[v] = d[u] + dis; //松弛操作
}
}
}
//以下为判断负环的代码
for (int u = 0; u < n; u++) { //对每条边进行判断
for (int j = 0; j < adj[u].size(); j++) {
int v = adj[u][j].v; //邻接边的顶点
int dis = adj[u][j].dis; //邻接边的边权
if (d[u] + dis < d[v]) //如果仍可以被松弛
return false; //说明图中存在从源点可达的负环
}
}
return true; //数组 d 的所有值已经达到最优
}
struct node { int v, dis; }; // v 为邻接边的目标顶点, dis 为邻接边的边权
int n; // n 为顶点数,MAXN 为最大顶点数
vector <node> adj[MAXN]; //图的邻接表
int d[MAXN], num[MAXN] = { 0 }; //d 记录起点到达各点的最短路径长度,num 记录顶点的入队次数
bool inq[MAXN] = { false }; //顶点是否在队列中bool SPFA(int s) {
fill(d, d + MAXN, INF);
queue <int> q;
q.push(s); //源点入队
inq[s] = true; //源点已入队
num[s]++; //源点入队次数加 1
d[s] = 0; //源点的 d 值为 0
while (q.size()) {
int u = q.front(); //队首顶点编号为 u
q.pop(); //出队
inq[u] = false; //设置 u 不在队列中
for (int j = 0; j < adj[u].size(); j++) { //遍历 u 的所有邻接边 v
int v = adj[u][j].v;
int dis = adj[u][j].dis;
if (d[u] + dis < d[v]) { //松弛操作
d[v] = d[u] + dis;
if (!inq[v]) { //如果 v 不在队列中
q.push(v); // v 入队
inq[v] = true; //设置 v 为在队列中
num[v]++; // v 的入队次数加 1
if (num[v] > n - 1) return false; //有可达负环
}
}
}
}
return true; //无可达负环
}
int n, m; // n 为顶点数,m 为边数
int dis[MAXN][MAXN]; // dis[i][j] 表示顶点 i 和顶点 j 的最短距离void floyd() {
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (dis[i][k] != INF && dis[k][j] != INF && dis[i][k] + dis[k][j] < dis[i][j])
dis[i][j] = dis[i][k] + dis[k][j]; //找到更短的路径
}
算法名称 | 适用条件 | 时间复杂度 | 其他 |
Dijkstra 算法 | 非负权值的单源最短路径 | | 时间复杂度可优化至 对于邻接矩阵和邻接表均比较适合, |
Bellman-Ford 算法 | 图中不存在从源点可达的负环 | | 一般使用邻接表来存储图 如果使用邻接矩阵存储图,则和 Floyd 算法一致。 可以用来判断是否存在从源点可达的负环。 |
SPFA 算法 | 图中不存在从源点可达的负环 | | 是对 Bellman-Ford 算法的优化。 可以用来判断是否存在从源点可达的负环。 |
Floyd 算法 | 全源最短路径 顶点数在200以内,其他无限制 | | 一般使用邻接矩阵来存储图 |
[1] 胡凡,曾磊.算法笔记[M].机械工业出版社,2016:465.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。