【学习笔记】最短路
Mysterious_Mini · · 个人记录
本文同步在 CSDN 上进行发表。
给定一个有
n 个点,m 条有向边的图。请你计算从s 出发,到每个点的距离。
这就是经典的单源最短路径问题,我们可以用很多种算法来实现它。
注:
- 下文所有
n,m,k 均与上方引用语句含义相同。 - 本文存图方式大多为链式前向星。
由于下文不给完整程序,故补全链式前向星存图代码如下,方便读者理解下文的内容:
int h[NR], tot;
struct Edge
{
int u, v, w, next;
} e[NR];
void add(int u, int v, int w)
{
tot++;
e[tot].u = u, e[tot].v = v, e[tot].w = w;
e[tot].next = h[u], h[u] = tot;
}
不会链式前向星的读者请看这篇文章。
算法 1 : \text{Floyd}
三维的状态可以进行降维,把
简化状态:
状态转移:
这里
核心代码:
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
时间复杂度:
优点:代码好写,可以处理带负权边的情况。 缺点:时间复杂度较高,不能处理负环。
算法 2 : \text{SPFA \& Bellman-Ford}
- 重复上述操作
(n-1) 次。
时间复杂度:
我之所以将这个算法称为“老古董”,是因为它已经过时了。它的优化版本
因为只有更新过
感觉实现起来像广搜。
核心代码:
queue <int> q;
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0; vis[s] = true; q.push(s);
while (!q.empty())
{
int u = q.front(); q.pop(); vis[u] = false;
for (int i = h[u]; i > 0; i = e[i].next)
{
int v = e[i].v, w = e[i].w;
if (dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
if (!vis[v]) q.push(v), vis[v] = true;
}
}
}
时间复杂度:
优点:可以出现负权边,可以判负环,稀疏图中效率高。
缺点:稠密图中
关于
\text{SPFA} :它死了。
言归正传,
判断负环很简单,只要把上面代码拿出来改一改就行了。所以我们先讲思路。
思路不难,但是有点坑。这篇题解对我启发很大,尤其是这句话,请读者务必理解:
不能判松弛次数,要判入队次数!
定义
核心代码:
把上面代码的这句话:
if (!vis[v]) q.push(v), vis[v] = true;
稍作修改,变成:
if (!vis[v])
{
q.push(v); vis[v] = true; cnt[v]++;
if (cnt[v] >= n) return puts("YES"), 0;
}
算法 3 : \text{Dijkstra}
- 重复前两步,直到所有点都被标记。
核心代码:
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
for (int i = 1; i < n; i++)
{
int u = 0;
for (int j = 1; j <= n; j++)
if (!vis[j] && (u == 0 || dis[u] > dis[j])) u = j;
vis[u] = true;
for (int j = h[u]; j > 0; j = e[j].next)
{
int v = e[j].v;
dis[v] = min(dis[v], dis[u] + e[j].w);
}
}
时间复杂度:
这个复杂度依然可以优化。我们发现,朴素的
定义一个结构体
struct Node
{
int id, dist;
bool operator < (const Node &x) const
{
return dist > x.dist;
}
};
priority_queue <Node> q;
优点:效率极高。
缺点:不能出现负权边,不能处理负环。
总结
数据小的题目:用
判断负环或有负权边:用
带非负权边的图:用