最短路
建图
邻接矩阵
这个东西就非常的easy,只需要用一个二维数组 f x , y f_{x,y} fx,y 表示 x x x和 y y y之间的距离
上升到几何层面,就是建立了一个二维的 n ∗ n n*n n∗n的矩阵,每个点表示从下标为它 x x x坐标的点,到下表为它 y y y坐标点之间的距离即可
好的方面在于它存边的过程非常简易,当 n n n的数据范围小的时候用起来会很方便
弊端就在于非常浪费空间,当 n n n的大小上升到 10000 10000 10000以上,就会出现MLE
code
cin>>x>>y>>z;
f[x][y]=z;
邻接表
在建图时最常用的就是通过邻接表,将邻接矩阵中浪费的空间大大减少,需要的空间即为边的数量
邻接表是由链表拓展而来
用一个head数组来表示链表的表头,并用 t o t tot tot来表示当前的是第几条边
通过表头 h e a d [ x ] head[x] head[x],很容易定位到第 x x x类对应的链表,从而访问从点 x x x出发的所有边
当连接一条新的边时,我们可以将这条新边的一端连接到此时出发点的表头正连接的位置,再将出发点的表头连到这条新边另一端上
假设 1 1 1已经与 2 2 2相连,那么接下来将 1 1 1与 3 3 3相连的过程如图所示
通过这样的方式,就可以建立出 n n n条链表来代表每个点所连接的边
存边过程中可以开三个数组来分别代表边权,它所连向的点的下标和这条边的下标。
当然也可以直接三合一存在结构体中,这样的存储方式被称为链式前向星
code
struct Star
{
int nxt,val,to;
}edge[M];
void Lian(int x,int y,int z)
{
tot++;
edge[tot].to=y;
edge[tot].val=z;
edge[tot].nxt=head[x];
head[x]=tot;
}
此代码中有些细节需要注意,当存的图为无向图时,结构体的大小需要开到 2 ∗ M 2*M 2∗M。使用万能头时,next作为关键字,不可以使用(编译能过)
当遍历一个点所连接的所有点时,就需要发挥链表的用处,从出发点的头所连接的第一条边开始遍历,不断遍历当前这条边后的下一条边。
具体实现见下面代码
code
for(int i=head[x];i;i=edge[i].nxt)
{
int y=edge[i].to,z=edge[i].val;//y表示这条边所连接点,z表示这条边的边权
}
依然有一个小细节,在for循环的终止条件中,由于每条边的下标一开始都为0,但在存边过程中,都被赋予了不为 0 0 0的一个下标,所以当遍历到下标为 0 0 0的边时,则表示 x x x连接的所有边都已经遍历完了
但如果 t o t tot tot是从 0 0 0开始来给边赋予下标,那这条下标为 0 0 0的边就没有了意义,我们可以进行一个操作,将所有边的初值赋为 − 1 -1 −1,这样在遍历时就可以把终止条件改为 i ! = 1 i!=1 i!=1,使下标为 0 0 0的边又有了价值
vector
有一种更简单且不是特别浪费空间的存图方法就是使用vector
直接将与 x x x相连的点存到 x x x的vector数组中,直接遍历这个vector数组,就可以遍历它所连接的所有点
code
//存图
vector<node> edge[N];
struct Star
{
int val,to;
};
void add(int x,int y,int z)
{
node a;
a.val=z,a.to=y;
edge[x].push_back(a);
}
//遍历
for(int i=0;i<edge[x].size();i++)
{
node a=edge[x][i];
int y=a.to,z=a.val;//y表示这条边所连接点,z表示这条边的边权
}
单源最短路问题
求出从一个点到达其他所有点的最短路径为多少
洛谷题目链接
我们接下来就针对这道题目进行讲解
Dijkstra
Dijkstra用到的主要是贪心策略。
利用一个dis数组来存储源点到每个顶点的最短距离,源点的最短距离显然为0,初始化其他所有点的dis为正无穷
找出dis数组中最短的点,这个点到源点的距离即为当前的dis值(全局最小值不可能再被其他节点更新),我们将这个点锁定,用vis数组将其标记,不再改变它的值,最后再将这个点向外扩展,这样我们就完成了一个顶点的最短路问题,通过这样的方式执行 n n n次,就可以解决单源最短路问题
但这样的算法复杂度为 O ( n 2 ) O(n^2) O(n2),非常的拉胯,考虑优化
两个 n n n的复杂度分别消耗于对 n n n个节点进行操作,每次操作找出 n n n个节点的最小dis值,后面的这个循环是可以进行优化的
我们可以使用二叉堆来优化这个过程,迅速的查找出最小的dis值,这样就可以在 O ( ( n + m ) l o g n ) O((n+m)logn) O((n+m)logn)的复杂度解决此题,这个复杂度已经是飞快了
Dijkstra却也有弊端,就是无法处理负的边权
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int n,m,s,tot=0;
int head[maxn],vis[maxn];
long long dis[maxn];
//链式前向星存图
struct Star
{
int to,next;
long long val;
}edge[maxn];
void Lian(int x,int y,int z)
{
tot++;
edge[tot].to=y;
edge[tot].next=head[x];
edge[tot].val=z;
head[x]=tot;
}
//堆优化
struct Point
{
int dd,p;
bool operator<(const Point &x) const{
return dd>x.dd;
}
};
void dijkstra()
{
for(int i=1;i<=n;i++) dis[i]=(1<<31)-1;//dis数组赋初值
priority_queue <Point> que;
dis[s]=0;
Point a;
a.p=s;
a.dd=0;
que.push(a);
while(!que.empty())
{
a=que.top();
que.pop();
int x=a.p;
if(vis[x]) continue;
vis[x]=1;//确定这个点的dis值后,就将这个点标记,不再更新它的值
for(int i=head[x];i;i=edge[i].next)
{
int y=edge[i].to;
if(dis[y]>dis[x]+edge[i].val)//如果这条边连接后更加优秀,就更新dis[y]
{
dis[y]=dis[x]+edge[i].val;
a.p=y;
a.dd=dis[y];
que.push(a);
}
}
}
}
int main()
{
cin>>n>>m>>s;
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
Lian(u,v,w);
}
dijkstra();
for(int i=1;i<=n;i++)
{
cout<<dis[i]<<' ';
}
return 0;
}
Bellman-Ford & SPFA
Bellman-Ford算法的流程为
扫描所有边 ( x , y , z ) (x,y,z) (x,y,z),如果 d i s [ y ] > d i s [ x ] + z dis[y]>dis[x]+z dis[y]>dis[x]+z,那么就更新 d i s [ y ] dis[y] dis[y]的值,一直这样搞下去,迟早可以将所有点的最短路都更新完毕
时间复杂度为 O ( n m ) O(nm) O(nm)
显然这是一个很low的算法
如同Dijkstra一样,在它的基础上也可以进行优化,优化后的算法即为SPFA
在我们不断扫描一些边的过程中,有些节点的dis值已经锁死不会再变了,我们却还是一直在扫描与它相连的边,判断能不能将这些边更新,这个操作是冗余的,大大浪费了时间复杂度
所以我们可以拿一个队列优化,当这个节点被更新后,我们再去判断与它相连的边是否能够改变
否则执行操作就没有什么价值
当队列为空,即所有点都确定了最小dis值
需要注意的是,为了防止一个节点重复的进入队列浪费时间复杂度,我们可以拿一个标记数组判断每个点是否在队列中,如果已经处于队列中则没必要再将它放入
#include<bits/stdc++.h>
using namespace std;
int n,m,s;
const int maxn=1e6+10;
int head[maxn],tot=0,dis[maxn],vis[maxn];
//链式前向星存图
struct Star
{
int nxt,to,val;
}edge[maxn];
void Lian(int x,int y,int z)
{
tot++;
edge[tot].nxt=head[x];
edge[tot].to=y;
edge[tot].val=z;
head[x]=tot;
}
void Spfa()
{
for(int i=1;i<=n;i++) dis[i]=1e9;//更新dis初值
queue <int> q;
q.push(s);
vis[s]=1;
dis[s]=0;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=edge[i].nxt)
{
int y=edge[i].to;
if(dis[y]>dis[x]+edge[i].val)
{
dis[y]=dis[x]+edge[i].val;
if(!vis[y])
{
vis[y]=1;//进队列将其标记
q.push(y);
}
}
}
vis[x