第一种方式:用pre[ ]数组表示从起点s到顶点v的最短路径上v的前一个顶点的编号。
如果d[u] + G[u][v] < d[v]
,说明以u作为中介点可以使d[v]更优,此时需要令v的前驱节点为u,并且即便原先的pre[v]存放了若干个节点都应该清空,然后再添加u,因为正是因为这个中介点才使得到达v这个节点的路径变短,所有之前到达v的所有节点都需要清除,然后将u节点添加进去。
如果d[u] + G[u][v] == d[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);
}
else if(d[u] + G[u][v] == d[v])
{
pre[v].push_back(u);
}
如图,我们想要输出从V1到V4的最短路径。
首先要从pre[4]得到V3,再从pre[3]得到V2,一直继续。
输出最优路径:
void DFS(int s, int v) //s为起点,v为当前结点(从终点递归)
{
//递归边界
if(s == v) //如果已经到达起点s,就输出起点并返回
{
printf("%d ", s);
return;
}
//递归边界
DFS(s, pre[v]); //递归访问v的前驱
printf("%d ", v); //从最深处return回来后,输出每一层的顶点号
}
const int maxn = 1000;
const int INF = 100000;
int G[maxn][maxn], d[maxn];
int pre[maxn]; //表示最短路径中v的前一个顶点
bool vis[maxn] = {false};
int n;
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] == 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[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v])
{
d[v] = d[u] + G[u][v];
pre[v] = u; //记录v的前驱是u
}
}
}
}
void DFS(int s, int v) //s为起点,v为当前结点(从终点递归)
{
//递归边界
if(s == v) //如果已经到达起点s,就输出起点并返回
{
printf("%d ", s);
return;
}
//递归边界
DFS(s, pre[v]); //递归访问v的前驱
printf("%d ", v); //从最深处return回来后,输出每一层的顶点号
}
第二种方式:Dijkstra+DFS,先用Dijkstra求出所有路径,然后从这些路径中选出一条第二标尺最优的路径。
思路:用一个vector<int> pre[maxn]
记录前驱。pre[v]是一个多维数组,如果v的前驱是1,2,3,那么pre[v][0]~pre[v][3]就是1,2,3。
int optValue; //第二标尺最优值
vector<int> pre[maxn]; //前驱
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(); //遍历完所有前驱结点,将当前结点删除。
}
输出:需要注意的是,存放在tempPath中的路径结点是逆序的,因此需要倒着访问(计算边权和时可以正序)。以计算最短路径的边权之和为例。
//边权之和
int vaulue = 0
for(int i = tempPath.size() - 1; i >= 0; i--) //倒着访问结点
{
//当前结点id,下一个结点idNext
int id = tempPath[i], idNext = tempPath[i - 1];
value += V[id][idNext]; //value增加边id->idNext的边权
}
//点权之和
int value = 0;
for(int i = tempPath.size() - 1; i >= 0; i--)
{
int id = tempPath[i]; //当前结点
value += W[id]; //value增加点id的点权
}