次短路问题

文章介绍了如何利用Dijkstra算法求解图中的次短路问题,提供了两种方法:双数组法和换边法。双数组法通过维护最短路和次短路数组,不断更新节点的距离;换边法则通过两次Dijkstra算法分别从起点和终点开始,并检查边是否能形成次短路。
摘要由CSDN通过智能技术生成

次短路问题定义

第二短的路,可以利用Dijkstra算法求解次短路

一、双数组法

**用 dijkstra 算法 **从起点开始 同时维护 最短路数组(dist1[ ])和 次短路 数组 (dist2[ ])

我们曾经学过利用Dijkstra算法求解最短路,但是如果要求解某一个结点的次短路该怎么做呢?实际上,我们仍然可以用Dijkstra算法来求解它。

首先来回顾一下Dijkstra算法的原理:首先把所有结点的最短距离设置为无穷大,然后令d[0]=0。接下来,每次都找到最短路已经确定的经典,更新从它出发的相邻结点的最短距离。以后我们不再考虑最短距离已经确定了的结点。

以上就是Dijkstra算法的主要过程,需要注意的一点是我们不再考虑的是“最短距离已经确定了的结点”,不要错误地理解为是更新过最短距离值的结点。因为有些结点的最短路可能需要多次更新才能最终确定。那么问题来了,如何知道哪些结点的最短距离是确定的呢?这里我们利用了一点贪心的思想,每次都取出当前距离最短的那个结点,认为它的最短路就是已经确定好的。可以证明这样的做法是正确的。这也算为什么优化版本的DIjkstra算法用到了priority_queue的原因。

那么回到主题,如何求解次短路呢?如果我们要求解起点s到终点t的次短路,那么有两种可能的情况:

(1)起点s到某个顶点u的最短路+d(u,t)。

(2)起点s到某个顶点u的次短路+d(u,t)。

因此,对于每个结点,我们记录的不仅仅是最短距离,还有次短距离,接下来用类似于Dijkstra算法不断更新这两个距离即可求出次短路了。

#define N 100000+10
#define INF 100000000
typedef pair<int, int>P;
int n,r;
struct Edge{ int to, cost; };
vector<Edge>G[N];
int dist[N], dist2[N];
void addedge(int u, int v,int w)
{
    G[u].push_back(Edge{ v, w });
    G[v].push_back(Edge{ u, w });
}
void dijkstra()
{
    priority_queue<P, vector<P>, greater<P> >q;
    fill(dist, dist + n, INF);
    fill(dist2, dist2 + n, INF);
    dist[0] = 0;
    q.push(P(0, 0));
    while (!q.empty())
    {
        P u = q.top(); q.pop();
        int v = u.second, d = u.first;
        if (dist2[v] < d)continue;   //取出的比次短距离还大,抛弃
        for (int i = 0; i < G[v].size(); i++)
        {
            Edge&e = G[v][i];
            int d2 = d + e.cost;
            if (dist[e.to]>d2)   // 比最短距离短,最短和次短距离都更新
            {
                swap(dist[e.to], d2);
                q.push(P(dist[e.to], e.to));
            }
            if (dist2[e.to]>d2&&dist[e.to] < d2)  // 只更新次短距离
            {
                dist2[e.to] = d2;
                q.push(P(dist2[e.to], e.to));
            }
        }
    }
    printf("%d\n", dist2[n - 1]);   // 次短路
}

Copy

二、换边法

还是用到dijkstra 算法 分别用两个 dis1[ ] 数组 和 dis2[ ] 数组 分别 维护 从起点 和 从终点开始 的 最短路 ——然后枚举 所有边 , 将边的两个端点 连上 起点和终点 看是不是等于最短路,相等则跳过 , 不相等 则 更新 和 次短路(Inf)取 min

注意:如果是有向边,在反转时需要反向建边。

三、模板题

方法一、双数组法

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <stack>
#include <vector>
using namespace std;
typedef long long LL;
const int Maxn = 1e5 + 7;
const int Inf = 1e9 + 7;
int N , M;
int dis1[Maxn] , dis2[Maxn];
struct node{
    int v , w;
    friend bool operator < (node a , node b){
        return a.w > b.w;
    }
};
vector <node> G[Maxn];
void Dijkstra(){
    priority_queue <node> que;
    fill(dis1 , dis1+N+1 , Inf);
    fill(dis2 , dis2+N+1 , Inf);
    int start = 1;
    dis1[start] = 0;
    que.push((node){start , 0});
    node q;
    int v , w;
    while(!que.empty()){
        q = que.top();  que.pop();
        v = q.v , w = q.w;
        if(dis2[v] < w)    continue;
        int to_v , to_w;
        for(int i = 0 ; i < G[v].size() ; i++){
            to_v = G[v][i].v , to_w = G[v][i].w + w;
            if(dis1[to_v] > to_w){
                que.push((node){to_v , to_w});
                swap(dis1[to_v] , to_w);
            }
            if(dis2[to_v] > to_w && dis1[to_w] < to_w){
                dis2[to_v] = to_w;
                que.push((node){to_v , to_w});
            }
        }
    }
 
}
 
int main()
{
    while(~scanf(" %d %d",&N,&M)){
        for(int i = 1 ; i <= M ; i++){
            int u , v , w;  scanf(" %d %d %d",&u,&v,&w);
            G[u].push_back((node){v,w});
            G[v].push_back((node){u,w});
        }
        Dijkstra();
        printf("%d\n",dis2[N]);
    }
}

Copy

方法二、换边法

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <stack>
#include <vector>
using namespace std;
typedef long long LL;
const int Maxn = 1e5 + 7;
const int Inf = 1e9 + 7;
int N , M , cnt , ans;
int start = 1 , End = N;
int dis1[Maxn] , dis2[Maxn];
bool vis[Maxn];
struct node{
    int v , w;
    friend bool operator < (node a , node b){
        return a.w > b.w;
    }
};
struct edge{
    int x , y , w;
}A[Maxn << 1];
vector <node> G[Maxn];
 
void GetDis(int op){
    priority_queue <node> que;
    if(!op) que.push((node){start , 0});
    else    que.push((node){End , 0});
    int v , w;
    node q;
    while(!que.empty()){
        q = que.top();  que.pop();
        v = q.v , w = q.w;
        if(vis[v])  continue;
        vis[v] = true;
        int to_v , to_w;
        for(int i = 0 ; i < G[v].size() ; i++){
            to_v = G[v][i].v , to_w = G[v][i].w + w;
            if(!op && dis1[to_v] > to_w){
                dis1[to_v] = to_w;
                que.push((node){to_v , to_w});
            } else if(op && dis2[to_v] > to_w){
                dis2[to_v] = to_w;
                que.push((node){to_v , to_w});
            }
        }
    }
}
 
 
void Dijkstra(){
    fill(dis1 , dis1+N+1 , Inf);
    fill(dis2 , dis2+N+1 , Inf);
    start = 1 , End = N;
    dis1[start] = dis2[End] = 0;
    fill(vis , vis+N+1 , false);
    GetDis(0);
    fill(vis , vis+N+1 , false);
    GetDis(1);
}
void FindCdl(){
    int flag = dis1[End];
    int x , y , w;
    ans = Inf;
    for(int i = 1 ; i <= cnt ; i++){
        x = A[i].x , y = A[i].y , w = A[i].w;
        int temp = dis1[x] + dis2[y] + w;
        if(temp == flag)    continue;
        else ans = min(ans , temp);
    }
}
 
int main()
{
    while(~scanf(" %d %d",&N,&M)){
        cnt = 0;
        for(int i = 1 ; i <= M ; i++){
            int u , v , w;  scanf(" %d %d %d",&u,&v,&w);
            G[u].push_back((node){v,w});
            G[v].push_back((node){u,w});
            A[++cnt].x = u , A[cnt].y = v , A[cnt].w = w;
            A[++cnt].x = v , A[cnt].y = u , A[cnt].w = w;
        }
        Dijkstra();
        FindCdl();
        printf("%d\n",ans);
    }
}

Copy

四、练习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10247D

我会继续努力,信息技术会越好

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值