最短路问题
-
单源最短路:一个点到其他点的做短路
-
所有边权都是正数
1.朴素Dijstra算法(稠密图) O(n*2)
2.堆优化Dijstra算法(稀疏图)O(mlogn) -
存在负权边
1.Bellman-fort算法(求有边数限制的单源最短路)O(mn)
2.SPFA 一般O(m),最坏O(mn) -
多源最短路:任意两点间的最短路
Floyd算法 O(n*3)
朴素Dijstra算法
- 算法思想:从起点到终点的最短路的值。贪心的在已更新的结点集当中寻找距离起点最近的结点,并更新周围点,使得到最近的点的返回值。
- 朴素Dijstr算法解决稠密图,用邻接矩阵来存储边权值
g[a][b]=min(g[a][b],c);//a,b,为点,c为边
算法思路:
- (定义一个dist数组,表示点的权值)。初始化dist[i],且第一个点的值为0,dist【1】=0;
memset(dist,0x3f,sizeof dist);
dist[1]=0;
- (迭代n次)
- 设st为当前已确定最短距离的点,遍历所有点,找到t不在st中的距离最近的点
for(int j=1;j<=n;j++){
if(!st[j]&&(t==-1||dist[t]>dist[j]))//!st[j]表示当前这个点还没确定最短路
t=j;}
- 将t存入st中
st[t]=true;
- 用t更新其他点的距离,遍历所有点
for(int j=1;j<=n;j++)
dist[j]=min(dist[j],dist[t]+g[t][j]);
核心代码:
int dijstra()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<n;i++)
{
int t=-1;
for(int j=1;j<=n;j++){
if(!st[j]&&(t==-1||dist[t]>dist[j]))
t=j;}
st[t]=true;
for(int j=1;j<=n;j++)
dist[j]=min(dist[j],dist[t]+g[t][j]);
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
堆优化版Dijstra算法
- 算法思想:
和朴素版Dijstra算法相似,也是求起点到其他点的距离最短路。只是使用优先队列来做,和BFS做法相似。
这里要使用邻接表来存储边的权值。
void add(int a,int b,int c)//加边操作
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
算法思路:
- (定义一个dist数组,表示点的权值)。初始化dist[i],且第一个点的值为0,dist【1】=0;
memset(dist,0x3f,sizeof dist);//初始化dist(距离),dist[1]=0
dist[1]=0;
- 建立小根堆,将1号点插入堆中
priority_queue<PII,vector<PII>,greater<PII>>heap;//建立小根堆
heap.push({0,1});//将1号点插入堆
- 设st为当前已确定最短距离的点,遍历所有点,找到t不在s中的距离最近的点,循环堆是否为空。
- 取栈顶(当前最近点),并删除
auto t=heap.top();//取栈顶
heap.pop();//删栈顶
- 取出标号(second)和距离(first),如果该点被访问,则跳过。
int ver=t.second,d=t.first;//取出标号(second)和距离(first)
if(st[ver]) continue;//如果该点被访问,则跳过;
st[ver] = true;
- 再用这个点来更新其他点,遍历临边。
for(int i=h[ver];i!=-1;i=ne[i])//再用这个点来更新其他点,遍历临边
{
int j=e[i];
if(dist[j]>d+w[i])
{
dist[j]=d+w[i];
heap.push({dist[j],j});
}
}
核心代码:
int dijstra()
{
memset(dist,0x3f,sizeof dist);//初始化dist(距离),dist[1]=0
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII>>heap;//建立小根堆
heap.push({0,1});//将1号点插入堆
while(heap.size())//判断堆是否为空
{
auto t=heap.top();//取栈顶
heap.pop();//删栈顶
int ver=t.second,d=t.first;//取出标号(second)和距离(first)
if(st[ver]) continue;//如果该点被访问,则跳过;
st[ver] = true;
for(int i=h[ver];i!=-1;i=ne[i])//再用这个点来更新其他点,遍历临边
{
int j=e[i];
if(dist[j]>d+w[i])
{
dist[j]=d+w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
Bellman-ford算法(解决有边数限制的最短路)
- 算法思想:
先将每个点到达起点的距离设为正无穷,然后直接暴力枚举所有边,如果一个点的距离可以被更新,就更新该点的距离。枚举一次至少可以得到一个点到达起点的距离,直接无脑枚举n次,就可以得到所有点到起点的最短距离。
核心代码:
int bellman_fort()
{
memset(dist,0x3f,sizeof dist);//初始化
dist[1]=0;
for(int i=0;i<k;i++)//迭代n次
{
memcpy(backup,dist,sizeof dist);//备份dist,防止串连
for(int j=0;j<m;j++)//遍历所有边
{//松弛操作
int a=edges[j].a,b=edges[j].b,k=edges[j].k;
dist[b]=min(dist[b],backup[a]+k);
}
}
if(dist[n]>0x3f3f3f) return -1;
return dist[n];
}
SPFA(对Bellman-Frod做优化)
-
算法思想:
用宽搜(BFS)对Bellman-frod算法进行优化。在Bellman-frod算法中每次迭代的时候遍历所有边,然后来更新点之间的边,但是每次迭代每一条边不一定都会更新,也就是dist[b]>min(dist[b],backup[a]+k)
这个不等式不一定都会把b的边变小。dist[b]=min(dist[b],backup[a]+k)
如果每次迭代想让b此变小,得先让a变小。那么spfa就是对这步进行优化,用宽搜来优化,迭代的时候用一个队列来做 。 -
算法思路:
用一个队列先把起点(源点)放在里面去,只要队列不空,队列里面存的是所有变小的a(结点)。
- 取出队头,然后把队头删除
int t=q.front();
q.pop();
st[t]=false;
- 更新t的所有初边,如果更新成功,将b加入队列
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])//判断如果b是否已经在队列里,如果没有,再加入
{
q.push(j);
st[j]=true;
}
}
}
核心代码:
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;
}
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
SPFA判断负环**(抽屉原理)**:
用cnt[N]来表示当前最短路边的数量。每次更新cnt[x]=cnt[t]+1
如果说过程当中,某一次cnt[x]>=n
,也就是说从1到x至少经过了n条边,也就有n+1个点,由抽屉原理,只应该有n个点,所以可以判断有负环了。
注意:必须是每个点都进行判断,需要把每个点都放入队列中,这样的话每个点的负环都能找到,而不是以一定从1号点开始,因为有可能从1号点的最短路不经过负环。
Floyd算法
- 算法思想:
用邻接矩阵来存储所有的边,d[i,j]。
Floyd算法是一个动态规划算法。
从任意节点i到任意节点j的最短路径只有2种可能,一是直接从i到j,二就是从i经过若干个节点k到j。所以,算法假设d(i,j)为结点i到结点j的最短路径的距离,对于每一个结点k,算法判断d[i,j]>d[i,k]+d[k,j]
是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,便设置d[i,j]=d[i,k]+d[k,j]
,所以,当遍历完所有节点k,d(i,j)中记录的便是i到j的最短路径的距离。
核心代码:
void floyd()
{
for(int k=1;k<=n;i++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
d[i][j]=min(d[i][j],[i][k]+d[k][j]);
}
}
}
}