利用邻接表存图,无向图边的数量应当开$2m$。常常会开错,开成$2n$或是$m$
1. Dijkstra
Dijkstra基于贪心思想。
两个集合。$S$中的点是已经确定了到源点的最短路的,$V-S$是未知的。此时,$V-S$集合中的$d$全部都是由$S$得来的,换句话说,这些d值对应的最短路统统经过S内的点。
每一步从$V-S$中选择一个$d$最小的点$i$加入$S$中,即最短路得以确定。这个最短路一定是由$S$中的点构成的,并且是$S$中所能构成的最优的,因为它是由$S$中所有与它相邻的点松弛后得来的。利用反证法:如果它还不是最优的,则存在一个点$p$使得
dis[$i$->$p$->起点]<dis[$i$->起点] (1)
前者等于$dis(i,p)+d[p]$,后者等于$d[i]$。然而就目前状况来看,$d[i]<d[p]$,故(1)不成立。或曰$d[p]$还未确定,然而$d[p]$再松弛也不可能小于$d[i]$,因为它再要松弛也是被$i$或$d$比i更大的点松弛了。
因此有结论
dis[$i$->起点]≤dis[$i$->$p$->起点] (2)
也正是因为(2)式,使得Dijkstra只能用于边权非负的图。因为如果$dis(i,p)<0$就不一定满足了。
由于每次都是从一集合中选择$d$最小的元素,故可以用堆来优化。一个点可能会被多条边松弛,因此可能同时在堆中有好几个。此时需要剔除后来的。有一种不用done数组的方法,就是判断堆中存的d值是否大于数组中的d值。如果堆中的偏大则剔除。
以每个点为起点进行松弛,最多松弛成功$m$次。每一次加入堆的复杂度是$logn$。因此Dijkstra堆优化的复杂度是$O(mlogn)$
template_Dijkstra#include <cstdio> #include <algorithm> #include <cstring> #include <queue> using namespace std; inline int read(){ int x(0),w(1); char c = getchar(); while(c^'-' && (c<'0'||c>'9')) c = getchar(); if(c == '-') w = -1, c = getchar(); while(c>='0' && c<='9') x = (x<<3) + (x<<1) + c - '0', c = getchar(); return x*w; } struct node{ int u,d; }; int n,m,s,x,y,z,d[100010]; int head[100010],nxt[200010],to[200010],cost[200010],cnt; priority_queue <node> q; inline bool operator < (const node& a, const node& b){ return a.d > b.d; } inline void add(int u, int v, int w){ to[++cnt] = v; cost[cnt] = w; nxt[cnt] = head[u]; head[u] = cnt; } inline void dijkstra(int S){ for(int i = 0; i <= n+1; ++i) d[i] = 2000000000; d[S] = 0; q.push((node){S,0}); int u,D; while(!q.empty()){ u = q.top().u, D = q.top().d; q.pop(); if(D > d[u]) continue; for(int i = head[u]; i; i = nxt[i]){ if(d[u]+cost[i] < d[to[i]]){ d[to[i]] = d[u]+cost[i]; q.push((node){to[i],d[to[i]]}); } } } } int main(){ n = read(), m = read(), s = read(); for(int i = 1; i <= m; ++i){ x = read(), y = read(), z = read(); add(x,y,z); } dijkstra(s); for(int i = 1; i <= n; ++i){ printf("%d ",d[i]); } return 0; }