写在前面的话:写写复习下它,太久没怎么写这类题了
文章部分内容出自《算法竞赛进阶指南》
单源最短路径
这种问题就是说给一张有向图,以某一个节点(一般为1号节点),记录下其他每一个点
到达这个1号节点的最短路径的长度。
常用算法:Dijkstra,Bellman-Ford,SPFA(本质上是Bellman-Ford)
Dijkstra算法
基本思路:
它是基于贪心算法的一种方案,只能用在所有边长度非负的图。因为若z为负,全局最小值不可能被其他点
更新了。其中,第一步选出来的x已经满足--dist[x]是起点到x点的最短路径。本质上,我们在不断寻找全局
最小值在进行标记和拓展,最后就得到起点1到每个节点的最短路。
过程:
1.初始化dist[i]数组,即原点到i点的距离,dist[1]=0,其他点初始化到原点距离为+∞;
2.找出一个未被标记的且dist[x]最小的点x,再标记节点x;
3.扫描x的所有出边,若dist[y]>dist[x]+z,就更新dist[y] = dist[x]+z;
4.重复2--3,直到所有点被标记为止
当然我们可以发现,在找全局最小值的时候,代码可以继续优化,即找这个最小值可以用二叉堆去找,
复杂度从O($n^{2}$) 进化成 O($m log n$)。
#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
const long MAXN=1000010;
long n,m;
long head[MAXN],Next[MAXN],ver[MAXN],edge[MAXN],tot=0;
priority_queue< pair<long,long> > q;
inline void add(long x,long y,long z){
ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
}
bool v[MAXN];
long dist[MAXN];
inline void dijkstra(){
for(long i=1;i<=n;i++) dist[i]=999999;
memset(v,0,sizeof v);
dist[1]=0;
q.push(make_pair(0,1));
while(q.size()){
long x=q.top().second;
q.pop();
if(v[x]) continue;
v[x]=1;
for(long i=head[x];i;i=Next[i])
{
long y=ver[i],z=edge[i];
if(dist[y]>dist[x]+z){
dist[y]=dist[x]+z;
q.push(make_pair(-dist[y],y));
}
}
}
}
int main(){
scanf("%ld%ld",&n,&m);
long x,y,z;
for(long i=1;i<=m;i++){
scanf("%ld%ld%ld",&x,&y,&z);
add(x,y,z);
}
dijkstra();
for(long i=1;i<=n;i++)
printf("%ld
",dist[i]);
}
Bellman-Ford算法
基本思路
它是基于迭代的思想去考虑,即使所有边(x,y,z)满足三角形不等式:d[y]<=d[x]+z
如下图:
过程:
1.扫描所有边,若dist[y]>dist[x]+z,就使dist[y]=dist[x]+z;
2.重复上述步骤直到没有更新发生为止。
复杂度高达O(nm),但它优于dijkstra的一点,是它的边权可以为负值。
SPFA算法
基本思路:
spfa在国际上通称”队列优化的Bellman-Ford算法“,它用队列保存了待扩展的节点,每一次的入队
相当于完成一次更新,使得节点慢慢收敛,即松弛,稀疏图效率高。
因为对于一个从x到y的边,节点x还没有松弛过,那y就没必要松弛,所以我们用队列记录松弛过的点,
避免了bellman-ford中的冗余松弛操作。
过程:
1.建立一个队列,初始入队节点1;
2.取出队头,并扫描所有出边,若dist[y]>dist[x]+z,则更新dist[y]=dist[x]+z,并判断y是否在队列中,若
不在,则将y入队;
3.重复上述步骤直至队列为空。
用简略语言概述就是,我们有一个松弛点x,尝试x能到达的点y,若y可以被松弛,就更新dist[y],在
这个条件下,若y还没入队,那就把这个松驰过的点入队,直到队列为空。
#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
const long MAXN=1000010;
long n,m;
long head[MAXN],Next[MAXN],ver[MAXN],edge[MAXN],tot=0;
queue <long> q;
inline void add(long x,long y,long z){
ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
}
bool v[MAXN];
long dist[MAXN];
inline void spfa(){
for(long i=1;i<=n;i++) dist[i]=999999;
memset(v,0,sizeof v);
dist[1]=0;
q.push(1);
v[1]=1;
while(q.size()){
long x=q.front();
q.pop();
v[x]=0;
for(long i=head[x];i;i=Next[i])
{
long y=ver[i],z=edge[i];
if(dist[y]>dist[x]+z){
dist[y]=dist[x]+z;
if(v[y]==0) q.push(y),v[y]=1;
}
}
}
}
int main(){
scanf("%ld%ld",&n,&m);
long x,y,z;
for(long i=1;i<=m;i++){
scanf("%ld%ld%ld",&x,&y,&z);
add(x,y,z);
}
spfa();
for(long i=1;i<=n;i++)
printf("%ld
",dist[i]);
}
特别地,当我们要去判定负环的时候,我们可以用一个数组c[i]表示1到i的最短路径上点的个数,每一次松弛x-y时,
我们就用c[y]=c[x]+1,当c[y]>n时,即存在负环。
注:
其实对比dijkstra和spfa,我们可以发现,dijkstra针对每一个最小距离点进行扩展,
而spfa则是通过迭代思想将所有边进行扩展,自然spfa的复杂度会高于dijkstra,但是spfa同时也具有
可以更加容易加入各种其他算法和对负权的边,负环的处理。
温馨提示,面对一个不存在负权边的图最好用dijkstra,因为spfa易被恶意卡掉。
任意两点间的最短路径
Floyd算法
基本思路:
它是一种基于动态规划的算法,我们可以用D[k,i,j]来表示i经过编号不超过k的节点后到达j的最短路,所以
我们可以将其划分为两个子问题,一是congi经过编号不超过k-1的节点到j,或者从i经过k节点到达j。
则 D[k,i,j]=min(D[k-1,i,j],D[k-1,i,k]+D[k-1,k,j])
所以k是阶段,应该放在最外层
我们也可以用D保存邻接矩阵,省略k这一维,即:
D[i,j]=min(D[i,j],D[i,k]+D[k,j])
也可以理解为从i到j,看看目前的情况好,还是再经过一个中间点k更好。
过程:
这就不再赘述了,太过简单......
#include<iostream>
#include<cmath>
using namespace std;
const long MAXN=10010;
long dist[MAXN][MAXN];
long n,m;
int main(){
cin>>n>>m;
for(long i=1;i<=n;i++)
for(long j=1;j<=n;j++)
dist[i][j]=999999;
for(long i=1;i<=n;i++)
d[i][i]=0;
long x,y,z;
for(long i=1;i<=m;i++){
cin>>x>>y>>z;
if(dist[x][y]>z) dist[x][y]=z;
}
for(long k=1;k<=n;k++)
for(long i=1;i<=n;i++)
for(long j=1;j<=n;j++)
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
for(long i=1;i<=n;i++){
for(long j=1;j<=n;j++)
cout<<dist[i][j]<<" ";
cout<<endl;
}
}
范围,数值最大值可以自行更改
传递闭包
在交际网络中存在若干元素和若干对二元关系,关系具有传递性,请推导出尽量多的元素间的关系----传递闭包
易得,我们可以用floyd解决此类问题
#include<iostream>
#include<cmath>
using namespace std;
const long MAXN=10010;
bool dist[MAXN][MAXN];
long n,m;
int main(){
cin>>n>>m;
for(long i=1;i<=n;i++)
dist[i][i]=1;
long x,y,z;
for(long i=1;i<=m;i++){
cin>>x>>y;
dist[x][y]=dist[y][x]=1;
}
for(long k=1;k<=n;k++)
for(long i=1;i<=n;i++)
for(long j=1;j<=n;j++)
dist[i][j]|=dist[i][k]&dist[k][j];
}