有边数限制的最短路
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
短路的长度,路径允许重复经过点或边。
注意:每条最短路中至少要包含一条边。
输入
第一行包含两个整数N
和M
。
接下来M
行,每行包含三个整数A
,B
和L
,表示点A
与点B
之间存在有向边,且边长为L
。
最后一行包含三个整数S
,T
和K
,分别表示起点S
,终点T
和第K
短路。
输出
输出格式输出占一行,包含一个整数,表示第K
短路的长度,如果第K
短路不存在,则输出-1
。
数据范围1≤S, T≤N≤1000
0≤M≤10^4
1≤K≤1000
1≤L≤100
输入样例 1
2 2
1 2 5
2 1 4
1 2 2
输出样例
14
思路:
设计一个估价函数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;
}