单源最短路入门(Dijkstra + SPFA)
SPFA
SPFA很像BFS
(1)起点s入队,计算它所有邻居到s的最短距离(当前最短距离,不是全局最短距离,把计算一个节点到s的最短路径称为更新状态)。把s出队,状态有更新的节点入队。也就是说队列中只处理更新的节点,其他节点无影响不处理。
(2)现在队列头部是s的一个邻居u。弹出u,更新其他所有邻居的状态,把其中有变化的节点入队。
(3)这里要注意后面计算中u可能还会更新,因此只要节点状态有变化,就可以入队处理。
(4)继续上述过程,直到队列为空。
但是SPFA的稳定性是不稳定的,可能只有很少的节点重复入队也可能有很多。竞赛中常用SPFA的不稳定性卡SPFA,因此没有负权节点时优先使用较稳定的Dijkstra
SPFA模板(邻接表+队列)
#include <bits/stdc++.h>
using namespace std;
#define N 500001
const int INF = INT_MAX;
using namespace std;
struct edge
{
int to;
int w;
};
vector<edge> e[N];//e[i]:存第i个节点连接的所有边
queue<int> q;
bool vis[N];
int n, m, s;
long long dis[N];
void spfa()
{
for (int i = 1; i <= n; i++)
{
dis[i] = INF;
vis[i] = false;
}//初始化
q.push(s);//初始边入队
vis[s] = true;
dis[s] = 0;
while (!q.empty())
{
int t = q.front();
q.pop();//队头出队
vis[t] = false;
for (int i = 0; i < e[t].size(); i++)//遍历邻边
{
int to = e[t][i].to;
if (dis[to] > dis[t] + e[t][i].w)//t的第i个邻居更近
{
dis[to] = dis[t] + e[t][i].w;
if (!vis[to])
{
q.push(to);
vis[to] = false;
}
}
}
}
}
int main()
{
cin >> n >> m >> s;
for (int i = 1; i <= m; i++)
{
int a, b, c;
cin >> a >> b >> c;
e[a].push_back(edge{b, c});//有向图
}
spfa();
for (int i = 1; i <= n; i++)
cout << dis[i] << " ";
return 0;
}
Dijkstra
Dijkstra用了贪心的思想,因此不能处理负边权,稳定而高效。
程序的主要内容是维护两个集合,即已确定最短路径的节点集合A,这些节点向外扩散的邻居节点集合B。
程序思路如下:
(1)把起点s放到A中,把s所有的邻居放到B中。此时,邻居到s的距离就是直连距离。
(2)从B中找到距离起点s最短的节点u,放到A中。
(3)把u所有的新邻居放到B中。显然,u的每一条边都连接了一个邻居,每个新邻居都要加进去。其中u的一个新邻居v,它到s的距离dis(s,v)等于dis(s,u)+dis(u,v)。
(4)重复(2)(3),直到B为空时结束。
Dijkstra模板(堆优化版邻接表+优先队列)
#include <bits/stdc++.h>
using namespace std;
const int INF = INT_MAX;
const int N = 200005;
struct edge
{
int from, to, w;
edge(int a, int b, int c)
{
from = a;
to = b;
w = c;
}
};
vector<edge> e[N];//e[i]:存第i个节点连接的所有边
struct node
{
int id, dis;//id节点 与 dis距离
node(int a, int b)
{
id = a;
dis = b;
}
bool operator<(const node &a) const
{
return dis > a.dis;
}
};
int n, m, s;
void djk()
{
int dis[N];
bool done[N];
for (int i = 1; i <= n; i++)
{
dis[i] = INF;
done[i] = false;
}//初始化
dis[s] = 0;
priority_queue<node> v;
v.push(node(s, 0));
while (!v.empty())
{
node u = v.top();
v.pop();
int id = u.id;
if (done[id])
continue;丢弃已找到最短路径的节点,即集合A中节点
done[id] = true;
for (int i = 0; i < e[id].size(); i++)//遍历邻点
{
edge y = e[id][i];
if (done[y.to])
continue;//丢弃已找到最短路径的节点
if (dis[y.to] > y.w + u.dis)
{
dis[y.to] = y.w + u.dis;
v.push(node(y.to, dis[y.to]));//扩展新的邻居先放到优先队列里
}
}
}
for (int i = 1; i <= n;i++)
cout << dis[i] << " ";
}
int main()
{
ios::sync_with_stdio(0);
cin >> n >> m >> s;
for (int i = 1; i <= m;i++)
{
int a, b, c;
cin >> a >> b >> c;
e[a].push_back(edge(a, b, c));
}
djk();
return 0;
}