次短路径

次短路径

求次短路的方式

 顾名思义,就是除最短路最外的最短路径,求次短路的方法一般有两种,第一种是从起点跑一遍最短路,终点跑一遍最短路,然后枚举中间点,第二种则是在求最短路时顺便去维护次短路,下面将分别进行详细说明

下面代码都是以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;
}

Guess you like

Origin blog.csdn.net/CUCUC1/article/details/111310818