算法导论——Dijkstra
Victory_Defeat
2019-08-02 12:03:35
各位久等了!算法导论复活了 ~~虽说有了洛谷日报,我这也没啥用了~~
咳咳,不说废话,直接进入主题!
$Dijkstra$是单源最短路的一种算法(以下简称$Dij$),其本质是**贪心**,因此**不能解决负边权**问题(叹气)
具体思路如下:
1. 定义变量:$s$起点,$dis[i]$表示起点到$i$的最短距离,$map[i][j]$表示边$i,j$的权值,$vis[i]$表示$i$是否被访问
1. 将$map[i][j]$初始化为$INF$,$map[i][i]=0$
1. 读入,并加边
1. $dis[s]=0$,将$dis$更新一遍(不可到达点为$INF$)
1. 找到距**初始点**最近的**未访问**点$t$,记录为访问
1. **松弛**(以$t$为中间点):$dis[i]=\min(dis[i],dis[t]+map[t][i])$
1. 重复$n$遍步骤$5$
1. 输出答案即可
可以看出,$Dij$的时间复杂度是$O(n^2)$,理论上慢于$SPFA$,至于实际嘛……
对于**菊花图**这类恶心的卡$SPFA$的图,$Dij$可以轻松跑过,不过对于[P4779](https://www.luogu.org/problem/P4779)还是远远不够的!
下面,进阶操作来了:
**$Dij$的堆优化**!!!
观察步骤$5$,发现查找这一点可以利用**堆维护$dis$数组**,每次取出堆顶即可
有同学发问了:可是这样就不能判断**是否未访问了**!
不错,但我们可以不停地把堆顶访问过的删掉,直到堆顶是未访问过的
有同学又发问了:可是这样会有重复节点啊!
很好,接下来我们需要引入一个概念:**懒惰删除**
这是指不删除节点,而直接跳过该节点的操作
例如:堆中有点$(1,2),(1,3),(2,3)$
此时$dis[1]=2,dis[2]=3$
在第二次操作时,会发现$dis[1]!=3$,那么,我们就**懒惰删除**,跳过它!
至于堆的实现,可以使用$STL$的$priority\_queue$来代替
~~(您如果想手写堆,我也不拦着您)~~
(本人代码使用平板电视的$priority\_queue$)
注:这题不能用邻接矩阵!!!要用链式前向星(参见[$SPFA$](https://www.luogu.org/blog/Victory-Defeat/suan-fa-dao-lun-spfa))
复杂度分析:对于每个点,删除,插入,取出操作均为$\log n$,每条边也是$\log n$,因此总复杂度为$O((n+m)\log n)$
所以计算一下:最坏情况约为$5000000$左右,建议加个快读,不然……
这样就能$AC\ P4779$了!
代码实现:
```cpp
#include<iostream>
#include<cstdio>
#include<vector>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;
using namespace __gnu_pbds;
struct edge
{
int node,weight;
edge(int node_,int weight_):
node(node_),weight(weight_){}
};
vector<edge>v[100001];
int n,m,s,dis[100001];
bool vis[100001];
inline void dijkstra()
{
__gnu_pbds::priority_queue<pair<int,int>,greater<pair<int,int> >,pairing_heap_tag>q;
q.push(make_pair(0,s));
while(!q.empty())
{
pair<int,int>k=q.top();
q.pop();
if(vis[k.second])continue;
vis[k.second]=1;
dis[k.second]=k.first;
for(auto it=v[k.second].begin();it!=v[k.second].end();++it)
q.push(make_pair(it->weight+k.first,it->node));
}
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;++i)
{
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
v[x].push_back(edge(y,w));
}
dijkstra();
for(int i=1;i<=n;++i)printf("%d ",dis[i]);
putchar('\n');
return 0;
}
```