次短路径
求次短路的方式
顾名思义,就是除最短路最外的最短路径,求次短路的方法一般有两种,第一种是从起点跑一遍最短路,终点跑一遍最短路,然后枚举中间点,第二种则是在求最短路时顺便去维护次短路,下面将分别进行详细说明
下面代码都是以poj3255作为例题传送门
1、起点终点各跑一遍最短路,然后枚举中间点
先说一个结论,节点x到节点y的次短路可以看作
min( mindis(x,k1) + length(k1,k2) + mindisn(k2,y) ) && != dis(x,y)
这里的miindis为x到各点的最短路径,mindisn为y到各点的最短路径,也就是说,我枚举每一条边,然后加上起点到这个边的一点的最短路径 + 终点到这个边另一点的最短路径,找到最小且不等于最短路的路径即为次短路径,但是注意,这种做法仅限于无向图,因为我们默认终点到各个点的最短路也是各个点到终点的最短路径,而且这种方式还是比较消耗时间的,毕竟要跑两遍最短路
code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;
typedef long long ll;
const int inf = 0x7fffffff;//用于比较
const int INF = 0x3f3f3f3f;//用于计算
const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
const int dir8[8][2] = {{-1,0},{1,0},{0,-1},{0,1},{-1,-1},{-1,1},{1,-1},{1,1}};
const int dir4[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
struct node
{
int v,w,next;
}edge[maxn << 2];
int head[maxn],cnt,n,m;
int dis[maxn],dis2[maxn],disn[maxn];
void addedge(int a,int b,int val)
{
cnt++;
edge[cnt].v = b;
edge[cnt].w = val;
edge[cnt].next = head[a];
head[a] = cnt;
}
void Dijkstra(int start,int di[])
{
int vis[maxn];
for(int i = 1; i <= n; i++) di[i] = INF,vis[i] = 0;
priority_queue < pair<int,int>, vector< pair<int,int> >,greater< pair<int,int> > > q;
di[start] = 0;
q.push(make_pair(0,start));
while(!q.empty()) {
int w = q.top().first;//dis
int u = q.top().second;//point
q.pop();
vis[u] = 1;
for(int i = head[u]; i; i = edge[i].next) {
int to = edge[i].v;
if(!vis[to] && di[to] > di[u] + edge[i].w) {
di[to] = di[u] + edge[i].w;
q.push(make_pair(di[to],to));
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1; i <= m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
addedge(v,u,w);
}
//第一种在求最短路的时候动态维护次短路
//dijkstra(1);
//cout << dis2[n] << endl;
Dijkstra(1,dis);
Dijkstra(n,disn);
int temp = inf;
for(int i = 1; i <= n; i++) {
for(int j = head[i]; j; j = edge[j].next) {
int to = edge[j].v;
int w = dis[i] + edge[j].w + disn[to];
//如果等于最短路就跳过
if(w == dis[n]) continue;
temp = min(temp,w);
}
}
printf("%d\n",temp);
return 0;
}
2、边更新最短路边记录次短路
这个方法目前我认为无向图和有向图通用的,并且时间复杂度要比上一个稍低一些,但是感觉有些不好理解
下面我先把核心代码放一下,然后根据核心代码去进行解释
dis表示最短路径,dis2表示次短路径
void dijkstra(int start)
{
for(int i = 1; i <= n; i++) dis[i] = dis2[i] = INF;
priority_queue < pair<int,int>, vector< pair<int,int> >,greater< pair<int,int> > > q;
dis[start] = 0;
q.push(make_pair(0,start));
while(!q.empty()) {
int w = q.top().first;//dis
int u = q.top().second;//point
q.pop();
//如果这个点的dis已经大于该点的次短路了,说明也大于该点的最短路,所以没有更新的必要的
//因为已经用之前更短的最短路更新过了
if(w > dis2[u]) continue;
for(int i = head[u]; i; i = edge[i].next) {
int v = edge[i].v;
int cost = w + edge[i].w;//通过当前点为中转到相连点的花费
//经过中转后最短路小于原来的最短路
if(dis[v] > cost) {
swap(dis[v],cost);
q.push(make_pair(dis[v],v));
}
//更新次短路
if(dis2[v] > cost && cost > dis[v]) {
swap(dis2[v],cost);
//压入队列,之所以次短路要压入队列是因为后面更新需要。
//例子:dis[2] = 10, dis2[2] = 20 有一条边 2 到 6 的边权值为 5
//如果不把 dis2 入队,那么之后的算法中 dis[6] = 15, dis2[6] = INF
//只有当队列里有 20 这个值,才能 20+5 得出 25,然后更新 dis2[6] = 25
q.push(make_pair(dis2[v],v));
}
}
}
}
我们利用更新最短路时,如果最短路需要更新,那么次短路就是换下来的最短路径(这次更新之前的),如果不能替换最短路,也就是当前cost > dis,那么就和次短路比较,选择较小的作为次短路
我们会发现,相比最短路,次短路中缺少了vis标记数组,我个人的理解是因为我们压入优先队列的有次短路径和最短路径,故我们不知道压出队列的究竟是最短路径还是次短路径,所以我们不能盲目标记,我们只知道出队列的这个元素可能会对次短路和最短路径产生优化,所以不能进行标记,这里可以参考spfa的思想,以至于这样会导致复杂度要比最短路的dij算法要高一些,所以采取了一定的剪枝优化也就是if(w > dis2[u]) continue;
下面我们来说一下这一行q.push(make_pair(dis2[v],v));
加入这一行的原因在于,如果某点入度只为1,也就是只有一个点与它相连,如果次短路不如队列,那么由于该点的入度为1,它只会被最短路更新一次,这时候我们会发现,这个点的次短路是不会更新的,因为它在计算最短路的时候是没有替换过程的,入度为1,最短路直接就是与它相连的点最短路加上该边的,是不会有替换过程的,所以单单将最短路压入队列是不可行的,需要将次短路也压入队列.
code
/* * *
* 次短路
* poj3255
* * */
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;
typedef long long ll;
const int inf = 0x7fffffff;//用于比较
const int INF = 0x3f3f3f3f;//用于计算
const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
const int dir8[8][2] = {{-1,0},{1,0},{0,-1},{0,1},{-1,-1},{-1,1},{1,-1},{1,1}};
const int dir4[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
struct node
{
int v,w,next;
}edge[maxn << 2];
int head[maxn],cnt,n,m;
int dis[maxn],dis2[maxn],disn[maxn];
void addedge(int a,int b,int val)
{
cnt++;
edge[cnt].v = b;
edge[cnt].w = val;
edge[cnt].next = head[a];
head[a] = cnt;
}
void Dijkstra(int start,int di[])
{
int vis[maxn];
for(int i = 1; i <= n; i++) di[i] = INF,vis[i] = 0;
priority_queue < pair<int,int>, vector< pair<int,int> >,greater< pair<int,int> > > q;
di[start] = 0;
q.push(make_pair(0,start));
while(!q.empty()) {
int w = q.top().first;//dis
int u = q.top().second;//point
q.pop();
vis[u] = 1;
for(int i = head[u]; i; i = edge[i].next) {
int to = edge[i].v;
if(!vis[to] && di[to] > di[u] + edge[i].w) {
di[to] = di[u] + edge[i].w;
q.push(make_pair(di[to],to));
}
}
}
}
void dijkstra(int start)
{
for(int i = 1; i <= n; i++) dis[i] = dis2[i] = INF;
priority_queue < pair<int,int>, vector< pair<int,int> >,greater< pair<int,int> > > q;
dis[start] = 0;
q.push(make_pair(0,start));
while(!q.empty()) {
int w = q.top().first;//dis
int u = q.top().second;//point
q.pop();
//如果这个点的dis已经大于该点的次短路了,说明也大于该点的最短路,所以没有更新的必要的
//因为已经用之前更短的最短路更新过了
if(w > dis2[u]) continue;
for(int i = head[u]; i; i = edge[i].next) {
int v = edge[i].v;
int cost = w + edge[i].w;//通过当前点为中转到相连点的花费
//经过中转后最短路小于原来的最短路
if(dis[v] > cost) {
swap(dis[v],cost);
q.push(make_pair(dis[v],v));
}
//更新次短路
if(dis2[v] > cost && cost > dis[v]) {
swap(dis2[v],cost);
//压入队列,之所以次短路要压入队列是因为后面更新需要。
//例子:dis[2] = 10, dis2[2] = 20 有一条边 2 到 6 的边权值为 5
//如果不把 dis2 入队,那么之后的算法中 dis[6] = 15, dis2[6] = INF
//只有当队列里有 20 这个值,才能 20+5 得出 25,然后更新 dis2[6] = 25
q.push(make_pair(dis2[v],v));
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1; i <= m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
addedge(v,u,w);
}
//第一种在求最短路的时候动态维护次短路
dijkstra(1);
cout << dis2[n] << endl;
return 0;
}