知识体系
朴素Dijkstra算法
介绍:
Dijkstra算法是基于贪心的思想,每一步都找当前情况下的最优解,那么最后找出来的就是整体的最优解。
适用情况:
1.边权为正数
2.单源最短路
3.稠密图(n为点数,m为边数,n^2于m是一个级别,也就是边特别多)
图解步骤:
<1>用一个 dist 数组保存源点到其余各个节点的距离,dist[i] 表示源点到节点 i 的距离。初始时,dist 数组的各个元素为无穷大。
用一个状态数组 state 记录是否找到了源点到该节点的最短距离,state[i] 如果为真,则表示找到了源点到节点 i 的最短距离,state[i] 如果为假,则表示源点到节点 i 的最短距离还没有找到。初始时,state 各个元素为假。
<2>源点到源点的距离为 0。即dist[1] = 0。
<3>遍历 dist 数组,找到一个节点,这个节点是:没有确定最短路径的节点中距离源点最近的点。假设该节点编号为 i。此时就找到了源点到该节点的最短距离,state[i] 置为 1。
<4>遍历 i 所有可以到达的节点 j,如果 dist[j] 大于 dist[i] 加上 i -> j 的距离,即 dist[j] > dist[i] + w[i][j](w[i][j] 为 i -> j 的距离) ,则更新 dist[j] = dist[i] + w[i][j]。
<5>重复 3 4 步骤,直到所有节点的状态都被置为 1。
<6>此时 dist 数组中,就保存了源点到其余各个节点的最短距离。
伪代码实现:
int dist[n],state[n];
dist[1] = 0, state[1] = 1;
for(i:1 ~ n)
{
t <- 没有确定最短路径的节点中距离源点最近的点;
state[t] = 1;
更新 dist;
}
例题:
邻接表做法 :
#include<iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, M = 100010;
int h[N], e[M], ne[M], w[M], idx;//邻接表存储图
int state[N];//state 记录是否找到了源点到该节点的最短距离
int dist[N];//dist 数组保存源点到其余各个节点的距离
int n, m;//图的节点个数和边数
void add(int a, int b, int c)//插入边
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void Dijkstra()
{
memset(dist, 0x3f, sizeof(dist));//dist 数组的各个元素为无穷大
dist[1] = 0;//源点到源点的距离为置为 0
for (int i = 0; i < n; i++)
{
int t = -1;
for (int j = 1; j <= n; j++)//遍历 dist 数组,找到没有确定最短路径的节点中距离源点最近的点t
{
if (!state[j] && (t == -1 || dist[j] < dist[t]))
t = j;
}
state[t] = 1;//state[i] 置为 1。
for (int j = h[t]; j != -1; j = ne[j])//遍历 t 所有可以到达的节点 i
{
int i = e[j];
dist[i] = min(dist[i], dist[t] + w[j]);//更新 dist[j]
}
}
}
int main()
{
memset(h, -1, sizeof(h));//邻接表初始化
cin >> n >> m;
while (m--)//读入 m 条边
{
int a, b, w;
cin >> a >> b >> w;
add(a, b, w);
}
Dijkstra();
if (dist[n] != 0x3f3f3f3f)//如果dist[n]被更新了,则存在路径
cout << dist[n];
else
cout << "-1";
}
邻接矩阵做法:
#include<bits/stdc++.h>
using namespace std;
int dist[510];
int g[510][510];
int st[510];
int n,m;
int dijkstra()
{
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
for(int j=1;j<=n;j++)
{
int t=0;// dist[0]=0x3f3f3f3f
for(int i=1;i<=n;i++)
{
if(st[i]==0&&dist[t]>dist[i])//找到1~n里没有确定的点中的最短路径点
t=i;
}
st[t]=1;
for(int i=1;i<=n;i++)
dist[i]=min(dist[i],dist[t]+g[t][i]);
}
if(dist[n]==0x3f3f3f3f) return -1; //如果第n个点路径为无穷大即不存在最低路径
return dist[n];
}
int main()
{
memset(g,0x3f,sizeof(g));
cin>>n>>m;
while(m--)
{
int a,b,z;
cin>>a>>b>>z;
g[a][b]=min(g[a][b],z);
}
cout<<dijkstra();
return 0;
}
堆优化的Dijkstra
板子:
const int N = 2e5 + 10;
int key[N * 2], h[N * 2], ne[N * 2], idx, vue[N* 2], dist[N * 2], flag[N * 2];
void add(int u, int v, int x) {
key[idx] = v;
vue[idx] = x;
ne[idx] = h[u];
h[u] = idx++;
}
int dijkstra(int start, int n)//求start到n的最短路
{
memset(dist, 0x3f, sizeof dist);//距离初始化为无穷大
dist[start] = 0;
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<std::pair<int, int>>> heap;//小根堆
heap.push({0, start});//插入距离和节点编号
while (len(heap))
{
auto t = heap.top();//取距离源点最近的点
heap.pop();
int ver = t.second,distance = t.first;//ver:节点编号,distance:源点距离ver 的距离
if (flag[ver]) continue;//如果距离已经确定,则跳过该点
flag[ver] = 1;
for (int i = h[ver]; i != -1; i = ne[i])//更新ver所指向的节点距离
{
int j = key[i];
if (dist[j] > dist[ver] + vue[i])
{
dist[j] = distance+ vue[i];
heap.push({dist[j], j});//距离变小,则入堆
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
例题:
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>//堆的头文件
using namespace std;
typedef pair<int, int> PII;//堆里存储距离和节点编号
const int N = 1e6 + 10;
int n, m;//节点数量和边数
int h[N], w[N], e[N], ne[N], idx;//邻接表存储图
int dist[N];//存储距离
bool st[N];//存储状态
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);//距离初始化为无穷大
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;//小根堆
heap.push({0, 1});//插入距离和节点编号
while (heap.size())
{
auto t = heap.top();//取距离源点最近的点
heap.pop();
int ver = t.second,distance = t.first;//ver:节点编号,distance:源点距离ver 的距离
if (st[ver]) continue;//如果距离已经确定,则跳过该点
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])//更新ver所指向的节点距离
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = distance+ w[i];
heap.push({dist[j], j});//距离变小,则入堆
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
cout << dijkstra() << endl;
return 0;
}
Bellman-frod
为什么Dijkstra不能解决含有负权边的问题?:
会发生串联
例题
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 510,M = 10010;
struct Edge
{
int a;//起点
int b;//终点
int w;//边的权重;
}e[M];
int dist[N];//存储当前点到起点的距离;
int back[N];//作为备份存储;
int n,m,k;//n个点,m条边,k为最大限制边数;
void bellman_ford()
{
memset(dist ,0x3f, sizeof dist);//对距离数组初始化,所有边都为无穷大;
dist[1] = 0;//起点到起点的距离为0;
for(int i = 0;i < k;i++)//最大限制是k条边以内;
{
memcpy(back,dist,sizeof dist); //使用back 先将dist中的数据拷贝,防止出现串联更新;
for(int j = 0;j < m; j++ )
{
int a = e[j].a;
int b = e[j].b;
int c = e[j].w;
dist[b] = min(dist[b], back[a]+c);//每一条边都要比较更新,但是不会一直更新下去发生串联,而dijkstra算法则是一直会更新下去
}
}
}
int main()
{
scanf("%d %d %d",&n,&m,&k);
for(int i=0 ;i<m; i++)
{
int a,b,w;
scanf("%d %d %d",&a,&b,&w);
e[i] = {a,b,w};
}
bellman_ford();
if(dist[n] > 0x3f3f3f/2) printf("impossible");
else printf("%d",dist[n]);
return 0;
}
spfa
求最短路:
思路过程:
1.建立一个队列,初始时队列里只有起始点。
2.再建立一个数组记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。
3.再建立一个数组,标记点是否在队列中。
4.队头不断出队,计算始点起点经过队头到其他点的距离是否变短,如果变短且被点不在队列中,则把该点加入到队尾。
5.重复执行直到队列为空。
6.在保存最短路径的数组中,就得到了最短路径
图解过程:
给定一个有向图,如下,求A~E的最短路。
源点A首先入队,然后A出队,计算出到BC的距离会变短,更新距离数组,BC没在队列中,BC入队
B出队,计算出到D的距离变短,更新距离数组,D没在队列中,D入队。然后C出队,无点可更新。
D出队,计算出到E的距离变短,更新距离数组,E没在队列中,E入队。
E出队,此时队列为空,源点到所有点的最短路已被找到,A->E的最短路即为8
例题
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100010;
int h[N], e[N], w[N], ne[N], idx;//邻接表,存储图
int st[N];//标记顶点是不是在队列中
int dist[N];//保存最短路径的值
int q[N], hh, tt = -1;//队列
void add(int a, int b, int c){//图中添加边和边的端点
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void spfa(){
q[++tt] = 1;//从1号顶点开始松弛,1号顶点入队
dist[1] = 0;//1号到1号的距离为 0
st[1] = 1;//1号顶点在队列中
while(tt >= hh){//不断进行松弛
int a = q[hh++];//取对头记作a,进行松弛
st[a] = 0;//取完队头后,a不在队列中了
for(int i = h[a]; i != -1; i = ne[i])//遍历所有和a相连的点
{
int b = e[i], c = w[i];//获得和a相连的点和边
if(dist[b] > dist[a] + c){//如果可以距离变得更短,则更新距离
dist[b] = dist[a] + c;//更新距离
if(!st[b]){//如果没在队列中
q[++tt] = b;//入队
st[b] = 1;//打标记
}
}
}
}
}
int main(){
memset(h, -1, sizeof h);//初始化邻接表
memset(dist, 0x3f, sizeof dist);//初始化距离
int n, m;//保存点的数量和边的数量
cin >> n >> m;
for(int i = 0; i < m; i++){//读入每条边和边的端点
int a, b, w;
cin >> a >> b >> w;
add(a, b, w);//加入到邻接表
}
spfa();
if(dist[n] == 0x3f3f3f3f )//如果到n点的距离是无穷,则不能到达
cout << "impossible";
else cout << dist[n];//否则能到达,输出距离
return 0;
}
练习:E-小红勇闯地下城_牛客周赛 Round 33 (nowcoder.com)
Floyd
初始化:
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}