一在求最短路前我们来看看图的存储
1.邻接矩阵的建立:
int n,g[N][N];//n个点,对于g[x][y]表示起点x到终点y的边权(有向边)
g[x][y]=g[y][x]=z;//无向边的处理
g[x][y]=min(g[x][y],z);//重边的处理(最短路只保留最短的一条边即可)
2.邻接表的建立:
int head[N],ne[M],ver[M],edge[M],idx;
//邻接表的建立
void add(x,y,z)
{
idx++; ver[idx]=y,edge[idx]=z,ne[idx]=head[x],head[x]=idx;
}
//无向边的处理:
add(x,y,z),add(y,x,z)
x表示起点,y表示终点,z表示这条边的边权,N表示最大范围的点数,M表示最大边数,
add操作同数组模拟单链表类似,ver终点集合,head起点集合,ne指向下一个标号,edge表示边,它们通过同一个idx统一,
朴素Dijkstra算法(邻接矩阵存)
适用情况:求s号点到图中其他所有点的最短距离 O(n^2)
int d[N];//表示从某一点到当前距离是多少
bool v[N];//判断每个点的最短路是否已经确定了
算法步骤:
1.初始化将dist置为无穷大,v默认false,起点到自己的距离默认为0
2.进行n次循环,在每一次循环中都要确定s到一个还没有确定最短距离点的最短距离(那么n次循环就能确定n个点的最短距离)
即,先找到x<--找到不在st中的距离最近的点,再用这个点更新到其他点的距离
d[y]=min(d[y],d[x]+a[x][y]);//表示s到y的之前确定的距离和s到x再到y的距离的更小
现在我们来看看题目:
给定一个n个点m条边的有向图,图中可能存在重边和自环,所有边权均为正值,求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出-1
输入:
第一行:n(1~500)和m(1~10000)
接下来m行 x,y,z:点x和点y间存在一条有向边,边长为z
输出:
一个整数,表示1号点到n号点的最短距离
若路径不存在,则输出-1
边长均不超过10000
3 3
1 2 2
2 3 1
1 3 4
3
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[505][505];
bool v[505];
int d[505];
int n,m;
int dijikstra()
{
memset(d,0x3f,sizeof d);
d[1]=0;
for(int cnt=1;cnt<=n;cnt++){
int t=1<<30,x;
for(int i=1;i<=n;i++)
if(!v[i]&&t>d[i]) t=d[i],x=i;
v[x]=true;
for(int y=1;y<=n;y++)
d[y]=min(d[y],d[x]+a[x][y]);
}
if(d[n]==0x3f3f3f3f) return -1;
else return d[n];
}
int main()
{
cin>>n>>m;
int x,y,z;
memset(a,0x3f,sizeof a);
for(int i=1;i<=m;i++){
cin>>x>>y>>z;
a[x][y]=min(a[x][y],z);
}
for(int i=1;i<=n;i++)
a[i][i]=0;
int t= dijikstra();
cout<<t<<endl;
return 0;
}
堆优化版Dijkstra算法(邻接表存)
适用情况:求s号点到图中其他所有点的最短距离 O(mlogn)
算法思路
1.借用priority_queue和pair,小根堆存储,first表示s号点到y的距离,second表示y,按照最小距离排序
2.将(0,s)入队,每次离源点最近的点都在堆顶,并用v数组来记录是否访问,(将朴素算法中寻找最小直接转化为取堆顶)
3.弹出堆顶,更新以堆顶的点到其他所有点的最短距离
现在我们来看看题目:
P4779 【模板】单源最短路径(标准版) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
给定一个 nn 个点,mm 条有向边的带非负权图,请你计算从 ss 出发,到每个点的距离。
数据保证你能从 ss 出发到任意点。
输入格式
第一行为三个正整数 n, m, sn,m,s。 第二行起 mm 行,每行三个非负整数 u_i, v_i, w_iui,vi,wi,表示从 u_iui 到 v_ivi 有一条权值为 w_iwi 的有向边。
输出格式
输出一行 nn 个空格分隔的非负整数,表示 ss 到每个点的距离。
输入输出样例
输入 #1复制
4 6 1 1 2 2 2 3 2 2 4 1 1 3 5 3 4 3 1 4 4
输出 #1复制
0 2 4 3
说明/提示
样例解释请参考 数据随机的模板题。
1 \leq n \leq 10^51≤n≤105;
1 \leq m \leq 2\times 10^51≤m≤2×105;
s = 1s=1;
1 \leq u_i, v_i\leq n1≤ui,vi≤n;
0 \leq w_i \leq 10 ^ 90≤wi≤109,
0 \leq \sum w_i \leq 10 ^ 90≤∑wi≤109。
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
priority_queue<pair<int,int> > q;
const int N=100020,M=200020;
int heap[N],edge[M],ne[M],ver[M],idx;//这里的m要足够大
int d[N];
bool v[N];
int n,m,s;
void add(int x,int y,int z)
{
idx++;
edge[idx]=z;
ver[idx]=y;
ne[idx]=heap[x];
heap[x]=idx;
}
void dijkstra()
{
//memset(d,0x7f,sizeof d);
for(int i=1;i<=n;i++){
d[i]=2147483647;
}
d[s]=0;
q.push({0,s});
while(!q.empty()){
int x=q.top().second;
q.pop();
if(v[x]) continue;
v[x]=true;
for(int i=heap[x];i;i=ne[i]){
int y=ver[i],z=edge[i];
if(d[y]>d[x]+z){
d[y]=d[x]+z;
q.push({-d[y],y});//因为默认大根堆,我们取负就可以将最小的保证再堆顶
}
}
}
}
int main()
{
cin>>n>>m>>s;
for(int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
dijkstra();
for(int i=1;i<=n;i++){
cout<<d[i]<<" ";
}
return 0;
}
Bellman_ford算法
适用情况:有边数限制的最短路径,有负权回路
算法思路
1.n次迭代
2.每一次循环所有边,比较并更新起点到其他所有点的最小距离,x,y,z表示从x到y的边权为z
d[y]=min(d[y],d[x]+z)松弛操作
循环n次后对于所有的边都满足d[y]<=d[x]+z;
这里如果边数限制为k次,则k次迭代代表了从起点不超过k条边到每个点的最短距离
对于一条从x到y长度为z的边,最终的最短路一定满足d[y]<=d[x]+z;否则d[y]可以被更新为d[x]+z;,这也叫做松弛操作。
循环n次,对每条边进行松弛操作,便可以得到每个点的最短路。
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
d[v[i]] = min(d[v[i]], d[u[i]] + w[i]);
}
}
特别的,如果第n轮更新(即i = n - 1i=n−1时)发生了松弛操作,那么说明某个点的最短路上存在n条边,这是不可能的,一定有负环的存在。
现在我们来看看题目:
n个点m条边的有向图,可能有重边和自环,边权可能为负数
求出1号点到n号点的最多经过k条边的最短距离,如果走不到则暑促
impossible
注意:图中可能存在负权回路
输入:n,m,k
接下来m行x,y,z
输出:
表示从一号点到n号点最多经过k条边的最短距离
不存在则输出impossible
1<n,k<500
1<=m<=10000
样例:
3 3 1
1 2 1
2 3 1
1 3 3
3
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=510,M=10010;
int n,m,k;
int d[N],b[N];
struct edge{
int x,y,z;
}edges[N];
int bellman_ford()
{
memset(d,0x3f,sizeof d);
d[1]=0;
for(int i=0;i<k;i++){
memcpy(b,d,sizeof d);//备份,防止出现串连
for(int j=0;j<m;j++){
int x=edges[j].x,y=edges[j].y,z=edges[j].z;
d[y]=min(d[y],b[x]+z);
}
}
if(d[n]>=0x3f3f3f3f/2) return -1;//正无穷的更新
else return d[n];
}
int main()
{
cin>>n>>m>>k;
for(int i=0;i<m;i++){
int x,y,z;
cin>>x>>y>>z;
edges[i]={x,y,z};
}
int t=bellman_ford();
if(t==-1) puts("impossible");
else cout<<t<<endl;
return 0;
}
队列优化版Bellman_ford算法即spfa算法(邻接表存)
适用情况:(无负环,有负权)(网格图要尽量避免)
求s号点到图中其他所有点的最短距离 判负环,O(nm)
对d[y]=min(d[y],d[x]+z)这步骤的优化,并不是每一步d[y]都更新,只有d[x]变小才可以
算法思路
1.利用队列,先把起点存入,(队列存放的是距离变小)
2.当队列不空,取出对头,用取出的对头更新其所有出边,如果更新成功则成功的进队,只有改变了后面的才会变小,所有没有更新的不进队
现在我们来看看题目:
n个点m条边的有向图,可能有重边和自环,边权可能为负数
求出1号点到n号点的最短距离,如果走不到则输出
impossible
注意:图中不存在负权回路
输入:n,m,
接下来m行x,y,z
输出:
表示从一号点到n号点最多经过k条边的最短距离
不存在则输出impossible
1<n,m<100000
样例:
3 3
1 2 5
2 3 -3
1 3 4
2
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=100010;
int n,m;
int d[N];
bool b[N];
int h[N],ve[N],e[N],ne[N],idx;
void add(int x,int y,int z)
{
idx++;ve[idx]=y,e[idx]=z,ne[idx]=h[x],h[x]=idx;
}
int spfa()
{
memset (d,0x3f,sizeof d);
d[1]=0;
queue<int> q;
q.push(1);
b[1]=true;
while(q.size()){
int t=q.front();
q.pop();
b[t]=false;
for(int x=h[t];x;x=ne[x]){
int y=ve[x];
if(d[y]>d[t]+e[x])
{
d[y]=d[t]+e[x];
if(!b[y]){
q.push(y);
b[y]=true;
}
}
}
}
if(d[n]==0x3f3f3f3f) return -1;
else return d[n];
}
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
int t=spfa();
if(t==-1) puts("impossible");
else cout<<t<<endl;
return 0;
}
注:如果要判负环,则不用初始化距离,且把所有点放进队列
d[x]=d[t]+w[i];
cnt[x]=cnt[x]+1;
if(cnt[x]>=n)//即表示从1到x经过了至少n条边,即经过了n+1个点,即存在负环
return true;
Floyd算法(多源最短路)零阶矩阵
算法思路 三重循环
d[x][y]表示从第x到y的最短路
基于动态规划 状态表示:d[k,x,y]//表示从x点出发经过1-k的中间点到达y的最短距离
d[k,x,y]=d[x-1,x,k]+d[k-1,k,j];//从x到k在到j
for(int k=1;i<=n;k++)
for(int x=1;x<=n;x++)
for(int y=1;y<=n;y++)
d[x][y]=min(d[x][j],d[x][k]+d[k][y];