今天也是为了cc,努力奋斗的一天ヾ(≧▽≦*)o
1. 引言
在引入了所谓的“第二标尺”的Dijkstra算法中,数组pre
总是保持着最优路径,而显然需要在执行Dijkstra算法的过程中使用严谨的思路来确定何时更新每个节点v
的前驱结点pre[v]
,实在是容易出错。
事实上,最简单的方法是:先在Dijkstra算法中记录下所有最短路径(只考虑距离),然后从这些最短路径中选出一条第二标尺最优的路径(因为在给定一条路径的情况下,针对这条路径的信息都可以通过边权和点权很容易计算出来!)
2. 实现
(1)使用Dijkstra算法记录所有最短路径
由于此时要记录所有最短路径,因此每个节点就会存在多个前驱节点,这样原先pre
数组只能记录一个前驱结点的方法不再适用。为了适应多个前驱的情况,不妨把pre
数组定义为vector
类型vector<int> pre[MAXV]
,这样对每个节点v
来说,pre[v]
就是一个变长数组vector
,里面存放着节点v
的所有可能产生最短路径的前驱节点。
(注:对需要查询某个顶点u是否在顶点v的前驱中的题目,也可以把pre数组设置为set<int>
数组,此时使用pre[v].count(u)
来查询会比较方便)
接下来就是考虑更新d[v]
的过程中pre
数组的变化:
- 如果
d[u] + G[u][v] < d[v]
,说明以u
为中介点可以使d[v]
更优,此时需要令v
的前驱节点为u
。并且即便原来的pre[v]
中已经存放了若干节点,此处也应该清空,然后再添加u
。if(d[u] + G[u][v] < d[v]){ d[v] = d[u] + G[u][v]; pre[v].clear(); pre[v].push_back(u); }
- 如果
d[u]+G[u][v] == d[v]
,说明以u
为中介点可以找到一条相同距离的路径,因此v
的前驱节点需要在原先的基础上添加上u
节点(而不必清空pre[v]
),代码如下:if(d[u] + G[u][v] == d[v]){ pre[v].push_back(u); }
下面是使用Dijkstra求单源最短路径的通用模板:
vector<int> pre[MAXV];
void Dijkstra(int s){ //s为起点
fill(d,d+MAXV,INF);
d[s] = 0;
for(int i=0;i < n;i++){
int u = -1;
int MIN = INF; //找到最小的d[u]
for(int j=0;j<n;j++){
if(vis[j] == false && d[j] < MIN){
u = j;
MIN = d[j];
}
}
if(u == -1){
return;
}
vis[u] = true;
for(int v = 0;v < n;v++){
if(vis[u] == false && 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
}
}
}
}
}
(2)遍历所有最短路径,找出一条使第二标尺最优的路径
考虑如何写DFS的递归函数。
数据结构
首先,根据分析,必须要有的是:
- 作为全局变量的第二标尺最优值
optValue
; - 记录最优路径的数组
path
(使用vector
来存储) - 临时记录DFS遍历到叶子节点时的路径
tempPath
(也使用vector
存储)
递归边界
对于递归边界而言,如果当前访问的是叶子节点(其实就是路径的起点,思考为什么是路径的起点是递归边界?因为pre数组的原因),那么说明到达了递归边界,此时tempPath
存放了一条路径。这时要做的就是对这条路径求出第二标尺的值value
,并与optValue
比较,如果更优,则更新optValue
并把tempPath
覆盖path
。
递归式
如果当前访问的节点是v
,那么只需要遍历pre[v]
中的所有节点并进行递归即可。
int optvalue; //第二标尺最优值
vector<int> pre[MAXV]; //存放节点的前驱节点
vector<int> path,tempPath; //最优路径,临时路径
void DFS(int v){ //v为当前访问结点
//递归边界
if(v == st){ //如果到达了叶子节点st(即路径的起点)
tempPath.push_back(v); //将起点st加入临时路径tempPath的最后面
int value; //存放临时路径tempPath的第二标尺值
计算路径tempPath上的value值;
if(value优于optvalue){
optvalue = value; //更新第二标尺最优值与最优路径
path = tempPath;
}
tempPath.pop_back() ; //将刚加入的结点删除
return;
}
//递归式
tempPath.push_back(v); //将当前访问结点加入临时路径tempPath的最后面
for(int i = 0;i< pre[v].size();i++){
DFS(pre[v][i]); //结点v的前驱结点pre[v][i],递归
}
tempPath.pop_back(); //遍历完所有前驱节点,将当前结点v删除
}