最短路问题

有边数限制的最短路

bellman_ford算法可处理带有负权边的图

SPFA求最短路

题目描述

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible。

数据保证不存在负权回路。

输入

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x, y, z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

数据范围

1≤n, m≤10^5,

图中涉及边长绝对值均不超过 10000。

输入样例

3 3
1 2 5
2 3 -3
1 3 4

输出

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 impossible。

输出样例

2

代码实现

spfa算法由bellman_ford算法优化而来,Bellman_ford算法里最后return-1的判断条件写的是dist[n]>0x3f3f3f3f/2;而spfa算法写的是dist[n]==0x3f3f3f3f;其原因在于Bellman_ford算法会遍历所有的边,因此不管是不是和源点连通的边它都会得到更新;但是SPFA算法不一样,它相当于采用了BFS,因此遍历到的结点都是与源点连通的,因此如果你要求的n和源点不连通,它不会得到更新,还是保持的0x3f3f3f3f。

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=100010;

int n,m;
int h[N],w[N],e[N],ne[N],idx;//存储图的邻接表
int dist[N];
bool st[N];//存储该节点是否已经在队列中,避免重复入队

void add(int x,int y,int z)
{//h是头指针数组,e存储边的终点,ne存储下一条边的索引
    e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;
}
int spfa()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;

    queue<int> q;
    q.push(1);
    st[1]=true;
    while(q.size())
    {
        int t=q.front();
        q.pop();
        st[t]=false;//已出队,后续有可能再次入队

        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+w[i])
            {
                dist[j]=dist[t]+w[i];
                if(!st[j])
                {
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return dist[n];
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    while(m--)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
    }

    int t=spfa();
    if(t==0x3f3f3f3f)puts("impossible");
    else printf("%d\n",t);
    return 0;
}

st数组存储的是该节点是否已经在队列中,避免重复入队,但是已经出队后的节点有可能再次入队,st数组含义区别于Dijkstra算法。与Bellman_ford算法不同,由于没有限制循环的次数,所以spfa(Shortest Path Faster Algorithm)算法无法处理负权环的情况。

Floyd求最短路

题目描述

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数。

再给定 k 个询问,每个询问包含两个整数 x 和 y,表示查询从点 x 到点 y 的最短距离,如果路径不存在,则输出 impossible。

数据保证图中不存在负权回路。

数据范围

1≤n≤400,

1≤k≤n^2,

1≤m≤20000,

图中涉及边长绝对值均不超过 10000。

输入

第一行包含三个整数 n, m, k。

接下来 m 行,每行包含三个整数 x, y, z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

接下来 k 行,每行包含两个整数 x, y,表示询问点 x 到点 y 的最短距离。

输出

共 k 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 impossible。

输入样例 1 

3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3

输出样例

impossible

1

 代码实现

#include<iostream>
#include<cstdio>

using namespace std;
const int N=400,Inf=1e9,low=-330000000;
int n,m,k;
int dist[N][N];

void floyd()
{
    for(int i=1;i<=n;i++)
        for(int x=1;x<=n;x++)
            for(int y=1;y<=n;y++)
                dist[x][y]=min(dist[x][y],dist[x][i]+dist[i][y]);
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);

    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(i==j)dist[i][j]=0;
            else dist[i][j]=Inf;

    while(m--)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        dist[x][y]=min(dist[x][y],z);
    }
    floyd();
    while(k--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        int t=dist[x][y];
        if(t > Inf/2 || t < low)puts("impossible");
        else printf("%d\n",t);
    }
    return 0;
}

这里使用了low取巧来处理超大负权边,实际上为了增加代码的健壮性,应该使用long long来处理。

A*算法求第K短路

描述

给定一张N个点(编号 1, 2…N),M条边的有向图,求从起点S到终点T的第K短路的长度,路径允许重复经过点或边。

注意:每条最短路中至少要包含一条边。

输入

第一行包含两个整数NM

接下来M行,每行包含三个整数A,BL,表示点A与点B之间存在有向边,且边长为L

最后一行包含三个整数S,TK,分别表示起点S,终点T和第K短路。

输出

输出格式输出占一行,包含一个整数,表示第K短路的长度,如果第K短路不存在,则输出-1

数据范围1≤S, T≤N≤10000≤M≤10^41≤K≤10001≤L≤100

输入样例 1 

2 2
1 2 5
2 1 4
1 2 2

输出样例 

14

思路:

A*算法介绍

设计一个估价函数f,算出每个状态到目标状态的估计值。设从当前状态state到目标状态的估计值为 f[state] ,在实际搜索中求出的最小代价为 g[state] 。仍然维护一个堆,不断从堆中取出 “当前代价+未来估价” 最小的状态进行扩展。

A*算法条件:
估计距离<=真实距离
d[state] + f[state] = 起点到state的真实距离 + state到终点的估计距离=估计距离
                                                                       ^
d[state] + g[state] = 起点到state的真实距离 + state到终点的真实距离=真实距离

代码实现

通过Dijkstra算法求出每个节点到终点T的最短距离作为估价函数值(此处估价函数值等于实际值);再跑一个优先队列BFS,堆中维护一个三元组( dis(S,v)+f[v] , distance, v ),允许每个点最多入队K次,当终点第K次出队时,对应的距离distance就是第K短路的长度。

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>

#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
typedef pair<int,PII> PIII;

const int N=1005,M=200005;

int n,m,S,T,K;
int h[N],rh[N],e[M],w[M],ne[M],idx;
int dist[N],cnt[N];//cnt存储每个顶点被访问的次数
bool st[N];

void Add(int h[],int a,int b,int l)
{
    e[idx]=b,w[idx]=l,ne[idx]=h[a],h[a]=idx++;
}
void Dijkstra()
{//利用Dijkstra算法计算各点到终点的距离
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({0,T});//终点
    memset(dist,0x3f,sizeof dist);
    dist[T]=0;

    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();

        int ver=t.y;
        if(st[ver])continue;
        st[ver]=true;

        for(int i=rh[ver]; ~i; i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[ver]+w[i])
            {
                dist[j]=dist[ver]+w[i];
                heap.push({dist[j],j});
            }
        }
    }
}
int astar()
{
    priority_queue<PIII,vector<PIII>,greater<PIII>> heap;
    heap.push({dist[S],{0,S}});//起点

    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();

        int ver = t.y.y, distance = t.y.x;
        cnt[ver]++;
        if(cnt[T] == K)return distance;

        for(int i=h[ver];~i;i=ne[i])
        {
            int j=e[i];
            if(cnt[j]<K)
                heap.push({distance+w[i]+dist[j],{distance+w[i],j}});
        }
    }
    return -1;
}
int main()
{
    memset(h,-1,sizeof h);
    memset(rh,-1,sizeof rh);
    scanf("%d%d",&n,&m);

    for(int i=0;i<m;i++)
    {
        int a,b,l;
        scanf("%d%d%d",&a,&b,&l);
        Add(h,a,b,l);
        Add(rh,b,a,l);
    }
    scanf("%d%d%d",&S,&T,&K);
    if(S==T)K++;//起点==终点时

    Dijkstra();
    printf("%d\n",astar());
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值