点击查看代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#pragma warning(disable:4996)
/* 针对存在多条最短路径的情况,迪杰斯特拉算法的优化模板:
1、先在迪杰斯特拉算法中记录下所有最短路径(只考虑距离)
2、然后再从这些最短路径中选出一条符合第二标尺的最优路径(如边权和最小、点权和最大)
*/
const int maxn = 1010; //最多1000个结点
const int INF = 0x3fffffff; //无穷大表示两个顶点之间不相连
int n, G[maxn][maxn]; //顶点个数,邻接矩阵
int d[maxn]; //起点与各点之间的最短距离distance
bool vis[maxn] = { false }; //记录顶点是否已被访问,vis[i]=true表示顶点i已被访问
vector<int> pre[maxn]; //记录最短路径,pre[u]存储顶点u在每条最短路径上对应的前驱结点
//1、先在迪杰斯特拉算法中记录下所有最短路径(只考虑距离),存入pre[]中
void Dijkstra(int s) { //将所有最短路径存入pre[]中,起点为s
fill(d, d + maxn, INF); //将d[]全部初始为INF,表示起点与各点之间最短路径为无穷大,即不相连
d[s] = 0; //起点与自己最短距离为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++) { //枚举所有顶点
if (vis[j] == false && d[j] < MIN) { //查找在未访问的顶点中,d[j]最小的那个顶点
u = j; //用u记录j
MIN = d[j]; //MIN记录最短距离d[j]
}
}
if (u == -1) return; //如果u等于-1,表示剩下的顶点都已被访问,或它们和起点s都不相连,可以结束算法
vis[u] = true; //否则设置u已被访问
for (int v = 0; v < n; v++) { //扫描所有顶点,查找顶点u的邻接点
if (vis[v] == false && G[u][v] != INF) { //如果v未被访问,且是u的邻接点
if (d[u] + G[u][v] < d[v]) { //如果以u为中转点能获得更优的最短路径
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]) { //如果以u为中转点能获得相同距离的最短路径
pre[v].push_back(u); //记录v在新的最短路径中的前驱结点是u
}
}
}
}
}
/* 2、使用DFS()遍历所有最短路径,找到在第二标尺条件下的最优路径
因为有多条最短路径时,根据每条最短路径可以画出一棵树,根结点是终点,叶子结点是起点,非叶结点是不同的前驱结点
因此可以使用DFS()遍历每条最短路径,当到达叶子结点时就会产生一条完整的最短路径。计算当前最短路径的点权或边权,如果它比最优值更优,
则记录当前最短路径是最优路径。当所有最短路径遍历结束后,就能找到在第二标尺条件下的最优路径 */
int optvalue; //第二标尺最优值(optimal最优)
int s; //叶子结点编号,即起点编号
int V[maxn][maxn], W[maxn]; //边权,点权
vector<int> optPath, tempPath; //最优路径,临时路径
//因为pre[]存储的是顶点的前驱结点,要从后往前回溯前驱结点,所以在tempPath中存储的最短路径是逆序(终点->起点)
void DFS(int v) { //使用DFS遍历每条最短路径
//当到达叶子结点(路径起点)时,一条最短路径遍历完成,计算它的第二标尺值
if (v == s) {
tempPath.push_back(v); //将起点s加入到临时路径tempPath末尾
int value = 0; //存储tempPath的第二标尺值,如点权和或边权和
//计算tempPath上的值,注意在tempPath中存储的最短路径是逆序(终点->起点),因此要倒着扫描
for (int i = tempPath.size() - 1; i > 0; i--) { //1、如果第二标尺要求最短路径的边权之和最大
int id = tempPath[i], idNext = tempPath[i - 1]; //当前访问顶点id,下一个顶点idNext(注意因为idNext包含了i-1,循环边界不包含0)
value += V[id][idNext]; //累加边id->idNext的边权
}
for (int i = tempPath.size() - 1; i >= 0; i--) { //2、如果第二标尺要求最短路径的点权之和最大
int id = tempPath[i]; //当前访问顶点id
value += W[id]; //累加顶点id的点权
}
if (value > optvalue) { //如果当前最短路径的第二标尺值(边权或点权)比最优值更大
optvalue = value; //更新最优值optvalue
optPath = tempPath; //更新最优路径
}
tempPath.pop_back(); //每条最短路径计算结束后要删除刚加入的顶点v,结束递归回到上一个岔路口
return;
}
tempPath.push_back(v); //将当前访问顶点v加入到tempPath末尾
for (int i = 0; i < pre[v].size(); i++) { //检查在每条最短路径中,v的前驱结点i
DFS(pre[v][i]); //递归回溯v的前驱结点i
}
tempPath.pop_back(); //遍历完顶点v的所有前驱结点后要删除顶点v,回到上一个岔路口
}