关于最短路
[前言&胡扯]
\(By:Soroak\)
说到最短路问题,我们无非就是用三种算法进行处理:
①: \(Floyd\) 。②: \(Dijkstra\) 。③: \(Spfa\)
对于最短路算法,学长之前给我们讲过,可是当时我因为不想听课,上课走神等原因,成功的没有学扎实,现在来重新 复习 重新学习一下
[Floyd]
- 定义: \(Floyd\) 算法,是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。
- 时间&空间复杂度:
\(Floyd\) 算法的时间复杂度为 \(O(N^3)\) ,空间复杂度为 \(O(N^2)\) 。 - \(Floyd\) 可以说是最暴力的一个算法了,用 \(Floyd\) 的时候,往往用邻接矩阵来存储。
Q:为什么用邻接矩阵来存而不是邻接表来存呢??
A:没有那个必要啊,Floyd的时间复杂度是 \(O(N^3)\) , \(O(N^3)\) 能跑的范围,邻接矩阵完全可以存过来,不过你要是实在想写邻接表来存,我也不拦你用邻接表来存 $ Floyd$ 用 \(1s\) 就 能跑过来的图。。。
以下是 \(Floyd\) 的模板:
#include<iostream>
#include<cstdio>
#include<cstring>
#define int long long int
using namespace std;
int map[5010][5010];
int n,m;
const int INF=0x7fffffff;
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
map[i][j]=INF;
}
}
for(int i=1;i<=n;i++)
{
map[i][i]=0;
}
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
map[u][v]=w;
map[v][u]=w;
}
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(map[i][j]>map[i][k]+map[k][j]&&k!=i&&k!=j&&i!=j)
{
map[i][j]=map[i][k]+map[k][j];
}
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cout<<map[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
/*样例输入
4 6
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
输出:
0 2 4 3
2 0 2 1
4 2 0 3
3 1 3 0
*、
[Dijkstra及其优化]
-
定义: \(Dijkstra\) 算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
-
Q&A:
Q: \(Dijkstra\) 比 \(Floyd\) 好在哪里呢??
A:上面说过 \(Dijkstra\) 是典型的单源最短路径算法,何谓单源最短路??就是给定一个起始点,求这个点到所有点的最短路,求一遍 \(Dijkstra\) (非优化)的时间复杂度是 \(O(N^2)\) ,你要是想求出每个点到每个点的最短路,我还是建议你用 \(Floyd\) ,因为 \(Dijkstra\) 求 \(N\) 遍单源最短路的复杂度和 \(Floyd\) 一样都是 \(O(N^3)\) ,当然,这里说的都是未优化的情况,优化的 \(Dijkstra\) 下面会讲到。 -
\(Dijkstra\) 是一种类似于贪心的算法,具体步骤:
- 当到一个时间点时,图上已经有一部分的点,最短路径已经确定,而部分点尚未确定。
- 在所有未确定的点中选择离原点最近的点,把它认为是这两个点之间的最短距离。
- 然后把这个点所有的出边都遍历一边,并更新所有点。
\(Dijkstra\) 从随意一个点出发,到达各点的最短路径长度的代码如下:
//#include<iostream>
//#include<cstdio>
//#include<cstring>
//#define int long long int
//
//using namespace std;
//
//int map[5010][5010];
//int dis[5010];
//int fl[5010];
//int n,m,s;
//const int inf=2147483647;
//
//inline void dijkstra(int u)
//{
// memset(dis,63,sizeof(dis));
// int start=u;
// fl[start]=1;
// for(int i=1;i<=n;i++)
// {
// dis[i]=min(dis[i],map[start][i]);
// }
// for(int i=1;i<=n-1;i++)
// {
// int minn=inf;
// for(int j=1;j<=n;j++)
// {
// if(fl[j]==0&&minn>dis[j])
// {
// minn=dis[j];
// start=j;
// }
// }
// fl[start]=1;
// for(int j=1;j<=n;j++)
// {
// dis[j]=min(dis[j],dis[start]+map[start][j]);
// }
// }
//}
//
//signed main()
//{
// cin>>n>>m>>s;
// memset(map,63,sizeof(map));
// for(int i=1;i<=m;i++)
// {
// int a,b,c;
// cin>>a>>b>>c;
// map[a][b]=c;
// map[b][a]=c;
// }
// for(int i=1;i<=n;i++)
// {
// map[i][i]=0;
// }
// dijkstra(s);
// for(int i=1;i<=n;i++)
// {
// cout<<dis[i]<<" ";
// }
// return 0;
//}
#include<iostream>
#include<cstdio>
#include<cstring>
#define int long long int
using namespace std;
int value[10010];
int to[10010];
int nxt[10010];
int head[10010];
int total;
int fl[10010];
int dis[10010];
int n,m,s;
inline void add(int a,int b,int c)
{
total++;
to[total]=b;
value[total]=c;
nxt[total]=head[a];
head[a]=total;
}
void dijkstra(int u)
{
memset(dis,63,sizeof(dis));
memset(fl,0,sizeof(fl));
dis[u]=0;
for(int i=1;i<n;i++)
{
int start=-1;
for(int j=1;j<=n;j++)
{
if(fl[j]==0&&(dis[start]>dis[j]||start==-1))
{
start=j;
}
}
fl[start]=1;
for(int e=head[start];e;e=nxt[e])
{
dis[to[e]]=min(dis[to[e]],dis[start]+value[e]);
}
}
}
signed main()
{
cin>>n>>m>>s;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
dijkstra(s);
for(int i=1;i<=n;i++)
{
cout<<dis[i]<<" ";
}
cout<<endl;
return 0;
}
- Q&A
Q:那 \(Dijkstra\) 的时间复杂度是 \(O(N^2)\) ,那跑 \(N\) 遍单元最短路的复杂度不就是 \(O(N^3)\) 了吗,那和 \(Floyd\) 没有什么区别啊
A:别着急,上面的是未优化的,我们还有堆优化过的 \(Dijkstra\),跑一遍的时间复杂度就成了 \(O(NlogN)\) 。
下面上代码:
(感谢gyh大佬的大力支持)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
const int INF=2147483647;
const int MARX=1e5+10;
using namespace std;
struct edge
{
int u,v,w,ne;
}e[MARX<<1];
struct p
{
int num,diss;
bool operator<(const p &a)const
{
return diss>a.diss;
}
}tmp;
int head[MARX],dis[MARX];
bool f[MARX];
int num,n,m,s,x;
void add(int u,int v,int w)
{
e[++num].ne=head[u];
head[u]=num;
e[num].u=u;
e[num].v=v;
e[num].w=w;
}
void dj(int s)
{
priority_queue <p> q;
tmp.num=s;
tmp.diss=0;
q.push(tmp);
for(int i=1;i<=n;i++)
{
dis[i]=INF;
}
dis[s]=0;
while(!q.empty())
{
int top=q.top().num;
q.pop();
if(f[top])
{
continue;
}
f[top]=1;
for(int j=head[top];j;j=e[j].ne)//找k点的临点,并进行比较
{
if(dis[e[j].v] > dis[top]+e[j].w && (!f[e[j].v]))
{
dis[e[j].v] = dis[top]+e[j].w;
tmp.num=e[j].v;
tmp.diss=dis[e[j].v];
q.push(tmp);
}
}
}
}
signed main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
dj(s);
for(int i=1;i<=n;i++)
{
printf("%d ",dis[i]);
}
return 0;
}
/*
//
By:Luckyblock
使用STL中的 pair类实现
简单好写
#include<cstdio>
#include<cstring>
#include<ctype.h>
#include<queue>
#define int long long
const int MARX = 2e6+10;
//=============================================================
struct edge
{
int u,v,w,ne;
}e[MARX<<1];
int n,m,num, head[MARX];
int dis[MARX];
bool vis[MARX];
//=============================================================
inline int read()
{
int s=1, w=0; char ch=getchar();
for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1;
for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0';
return s*w;
}
void add(int u,int v,int w)
{
e[++num].u = u,e[num].v = v, e[num].w = w;
e[num].ne = head[u], head[u] = num;
}
void dijkstra(int start)
{
std::priority_queue <std::pair<int,int> > q;
memset(dis,63,sizeof(dis));
dis[start] = 0 ;
q.push(std::make_pair(0,start));
for(; !q.empty(); )
{
std::pair <int,int> top = q.top(); q.pop();
if(vis[top.second]) continue;
vis[top.second] = 1;
for(int i=head[top.second]; i; i = e[i].ne)
if(dis[e[i].v] > dis[e[i].u] + e[i].w)
{
dis[e[i].v] = dis[e[i].u] + e[i].w;
q.push(std::make_pair(-dis[e[i].v], e[i].v));
}
}
}
//=============================================================
signed main()
{
n = read(), m = read();
int s = read();
for(int i=1; i<=m; i++)
{
int u = read(), v = read(), w = read();
add(u,v,w);
}
dijkstra(s);
for(int i=1; i<=n; i++) printf("%lld ",dis[i]);
}
*/
[SPFA及其优化]
- 定义:\(SPFA\) 算法是 \(Bellman-Ford\) 算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。
- \(SPFA\) 一般是用于判断负环的,要是题目中给的图并没有负边权,那么还是建议用 \(Dijkstra\) 。。。
- Q&A
Q:那怎么判断是否有负环呢??
A:其实很简单,就只要判断一下是否有一个点入队次数超过 \(N\) 就好了。 - 实现方法:建立一个队列,初始时队列里只有起始点,在建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。
\(SPFA\) 代码以及优化:(在这里再次感谢gyh大佬的支持)
#include<cstdio>
#include<queue>
#define INF 2147483647
using namespace std;
queue<int>q;
struct edg
{
int u,v,next;
int w;
} edge[5000500];
int head[3000000];
int ans[3000000],vis[3000000];
int num=0;
int s,t;
void build(int u,int v,int w)
{
edge[++num].next=head[u];
head[u]=num;
edge[num].u=u;
edge[num].v=v;
edge[num].w=w;
}
void SPFA(int s)
{
ans[s]=0;
vis[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].v;
if(ans[v]>edge[i].w+ans[u])
{
ans[v]=edge[i].w+ans[u];
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=1; i<=n; i++)
ans[i]=INF;
for(int i=1; i<=m; i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
build(a,b,c);
}
SPFA(s);
for(int i=1; i<=n; i++)
printf("%d ",ans[i]);
return 0;
}
//==========简单好用优先队列优化==========
//优化很成功,时间复杂度较低,吊打DJ BOBO
/*
//将queue改为priority_queue,注意将q.front()改为q.top()
//同时自定义优先级 , 以最短路径长度升序排列
#include<cstdio>
#include<queue>
#define INF 2147483647
using namespace std;
struct edg
{
int u,v,next;
int w;
} edge[5000500];
int head[3000000];
int ans[3000000],vis[3000000];
int num=0;
int s,t;
struct cmp1
{
bool operator ()(const int a,const int b)
{
return ans[a]>ans[b];
}
};
priority_queue <int,vector<int>,cmp1> q;
void build(int u,int v,int w)
{
edge[++num].next=head[u];head[u]=num;
edge[num].u=u;edge[num].v=v;edge[num].w=w;
}
void SPFA(int s)
{
ans[s]=0;
vis[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.top();
q.pop();
vis[u]=0;
for(int i=head[u]; i; i=edge[i].next)
{
int v=edge[i].v;
if(ans[v]>edge[i].w+ans[u])
{
ans[v]=edge[i].w+ans[u];
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=1; i<=n; i++) ans[i]=INF;
for(int i=1; i<=m; i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
build(a,b,c);
}
SPFA(s);
for(int i=1; i<=n; i++)
printf("%d ",ans[i]);
}
*/
//==========可能负优化的LLL+SLF优化================
/*
#include<bits/stdc++.h>
#define mokou 2147483647
using namespace std;
struct edg
{
int u,v,w,next;
}asd[5000550];
int sum,n,m,s,vis[150000],sp[150000],head[150000];
void put(int a,int b,int c)
{
asd[++ sum].u = a;
asd[sum].v = b;
asd[sum].w = c;
asd[sum].next = head[a];
head[a] = sum;
}
deque<int>q;
int main(){
int cnt = 1,tot = 0;
cin >> n >> m >> s;
for(int i = 1;i <= m;i ++)
{
int u,v,w;
cin >> u >> v >> w;
put(u,v,w);
}
for(int i = 1;i <= n;i ++)
sp[i] = mokou;
sp[s] = 0;
vis[s] = 1;
q.push_front(s);
while(! q.empty())
{
int x = q.front();
while(cnt * sp[x] > tot)
{
q.pop_front();
q.push_back(x);
x = q.front();
}
q.pop_front();
cnt --, tot -= sp[x], vis[x] = false;
for(int i = head[x]; i; i = asd[i].next)
if(sp[asd[i].v] > sp[x] + asd[i].w)
{
sp[asd[i].v] = sp[x] + asd[i].w;
if(! vis[asd[i].v])
{
vis[asd[i].v] = 1;
if(q.empty() || sp[asd[i].v] > sp[q.front()])
q.push_back(asd[i].v);
else
q.push_front(asd[i].v);
cnt++, tot += sp[asd[i].v];
}
}
}
for(int i = 1;i <= n;i ++)
cout << sp[i] << " ";
}
*/
有什么问题可以在下方评论留言或找我私聊哦
\(QQ:2876140034\)
\(Luogu:Soroak\)