Dijkstra学习笔记

· · 个人记录

模板题:P4779

Dijkstra算法

### 做法 首先要定义松弛操作。对于一条边($u,v$),松弛操作对应下面的运算:$dis_{v}$ = $dis_{u}$ + $w_{u,v}$。 对于$Dijkstra$,我们可以将结点分成两个集合:已确定最短路长度的点集(记为$S$集合)的和未确定最短路长度的点集(记为$ T$集合)。一开始所有的点都属于$T$集合。设源点为$s$,初始化$dis_{s} =0$,其他$dis$为正无穷。每一次从 $T$集合中选最短路最小的加入$S$集合中,并对其出边进行松弛操作,重复操作直到$T$集合为空为止。 ### 证明 关于证明,可以用反证法,但我也不会证。主要是证明在执行操作时,取出的结点$u$最短路均已经被确定,即满足$D(u)$ = $dis(u)$。大概感性理解一下,因为每次选出的点都是$T$集合中最短路最小的点。具体证明可以参考[oiwiki](https://oiwiki.com/graph/shortest-path/#%E6%AD%A3%E7%A1%AE%E6%80%A7%E8%AF%81%E6%98%8E)。 ### 时间复杂度 $Dijksra$有三种实现方法,一种是直接每次暴力寻找最短路最小的点,时间复杂度$O(n^{2} + m)$,这种方法时间复杂度很高,不常用。第二种是用优先队列优化,这种方法是最普遍的。因为每个点可能被修改多次,所以优先队列里的数是$O(m)$的,总复杂度$O(mlogm)$,可以接受。第三种是用线段树优化,支持单点修改和全局查询,时间复杂度$O(mlogn)$,但是码量比较大。 ### Code 这里是用优先队列写的 ```cpp #include<bits/stdc++.h> using namespace std; typedef pair <int,int> pii; #define inf 0x3f3f3f3f const int MAXN = 1e5 + 10; int head[MAXN],cnt,n,m,x,y,z,dis[MAXN],s; bool vis[MAXN]; struct Node { int u,v,w,nxt; }e[MAXN << 1]; void add(int u,int v,int w){e[++cnt] = {u,v,w,head[u]};head[u] = cnt;} priority_queue <pii,vector <pii>,greater <pii> > q; void dijkstra(int s,int n) { memset(dis,inf,sizeof dis); dis[s] = 0; q.push(make_pair(n,s)); while(!q.empty()) { int u = q.top().second ;q.pop(); if(vis[u] == true) continue; vis[u] = true; for(int i = head[u]; ~ i;i = e[i].nxt) { int now = e[i].v; if(dis[u] + e[i].w < dis[now]) { dis[now] = dis[u] + e[i].w; q.push(make_pair(dis[now],now)); } } } } int main() { memset(head,-1,sizeof head); cin >> n >> m >> s; for(int i = 1;i <= m;i++) cin >> x >> y >> z,add(x,y,z); dijkstra(s,0); for(int i = 1;i <= n;i++) cout << dis[i] << " "; return 0; } ```