图论--单源最短路之--Dijkstra
DIJKSTRA
注意Dijkstra不能处理带负权的图,若带负权请使用spfa外加判断各点入队的次数。以下的描述都是基于图中的边无负权。
Dijkstra简述
dij是基于贪心的一种求解单源最短路径的算法,效率非常高。贪心算法的求解是来源于最优子结构的性质,从局部最优逐渐构造到全局最优。
- 最优子结构性质:最短路径的子路径仍然是最短路径。
- 贪心选择性质:最短子路径长度递增,逐次生成最短路径。
算法步骤
vis数组用于标记顶点的最短路径是否确定,dis标记顶点到源点的最短路径长度。
- 初始初始时dis[源点]为0,其他初始化为INFINIRTE(无穷大)。
- 遍历图中的所有点,找到dis最小的且未确定最短路的点u,此时u的最短路径就是dis了,标记u的最短路径已经确定。 *** 最优子结构 ***
- 更新u的所有邻接顶点到源点的最短路径长度dis。 *** 最优子结构的拓展构造;最短路径或是直接从源点到该点,或是经过其他顶点到该点。***
- 重复2、3直至所有顶点都被确定,或无法再继续到达其他顶点,结束算法
对于这种最朴素的dijkstra,时间复杂度为O(V2 + E)。
#define Inf 0x3f3f3f3f const int maxn = 1e3 + 2; int dis[maxn], table[maxn][maxn], N; bool vis[maxn]; //记录是否已经找到达该点的最小值 void dijkstra() { for(int i=1; i<=N; ++i) dis[i] = table[i][s]; dis[1] = 0; memset(vis, false, sizeof(vis)); vis[s] = true; for(int i=1; i<=N; ++i){ int index = -1, mindis = Inf; for(int j=1; j <= N; ++j){ if(!vis[j] && dis[j] < mindis){ // 遍历所有点找到到确定点集距离最小的点 mindis = dis[j]; index = j; } } if(index = -1) break; //最短路不存在 vis[index] = true; // index点到源点的最短路确定! for(int j=1; j<=N; ++j){ // 更新剩余点的最短路 if(dis[j] > dis[index] + table[index][j] && !vis[j]) dis[j] = dis[index] + table[index][j]; } } return; }
优化
下面对朴素的算法进行优化。1)邻接表存图:减少不必要的点的遍历。2)利用priotity_queue:堆优化找点的过程,这样子找点的复杂度就由原本的O(V)降为O(logV),故复杂度上限为O((E+V)logV)。对于这个复杂度的问题,我觉得有必要好好说说:
总的点数V,总的边数E。
- O(logV):用于在堆中找出dis最小的顶点。最优时是根节点,然而许多时候并不是,那么根节点出堆之后堆的维护特性就需要O(logV)的复杂度。所以对于V个顶点的图,最坏情况查找到顶点复杂度VO(logV)。
- O(logV):确定完一个dis最小的顶点u之后,更新u所有邻接点的dis,更新的邻接点v入堆,堆的插入操作就是O(logV)。u的临界点的数量最多就是边的数量E,所以更新dis部分的最坏复杂度EO(logV)。
- 所以总的时间复杂度就是:VO(logV)+EO(logV) = (V+E)log(V)。
下面这个来自poj的一道题,这里的dijkstra用了priority_queue优化,其实就是将大循环里的第一个循环找最小的节点的操作让priority_queue自行完成。题目为Sightseeing。这里的dijkstra不仅堆优化,而且同时查找了最短路和次短路,所以原本的数组都增加了一维。
#include <cstdio> #include <cstring> #include <iostream> #include <queue> using namespace std; #define ll long long #define Inf 2147483647 const int maxe = 50000 + 5 maxv = 50000 + 5; struct Edge{ int to, w; int next; Edge(int a=0, int b=0, int c=0):to(a), w(b), next(c){} }e[maxe * 2]; int head[maxv], cnte, pw[amxv]; struct Node{ int u; ll dis; Node(int a=0, ll b=0):u(a), dis(b){} bool operator < (const Node& b) const{ return this->dis > b.dis; } }; bool vis[maxv]; ll ans, dis[maxv]; int nv, ne, a, b, w, st; inline void add_edge(const int& from, const int& to, const int& w){ ++cnte; e[cnte].to = to, e[cnte].w = w; e[cnte].next = head[from]; head[from] = cnte; return; } void dijkstra(){ memset(vis, false, sizeof(vis)); for(int i=1; i<=nv; ++i) dis[i] = Inf; //注意这里的Inf不是0x3f3f3f3f只能用循环赋值 dis[st] = 0; priority_queue<Node> pq; while(pq.size()) pq.pop(); pq.push(Node(st, 0)); while(pq.size()){ Node cur = pq.top(); pq.pop(); if(vis[cur.u]) continue; vis[cur.u] = true; for(int i = head[cur.u]; i; i = e[i].next) { if(dis[e[i].to] > dis[cur.u] + e[i].w){ dis[e[i].to] = dis[cur.u] + e[i].w; pq.push(Node(e[i].to, dis[e[i].to])); //无需再判断是否已经确定,直接入队即可,因为每次弹出的值都是经过筛选的最小值 //且访问过的不会再 } else continue; } } return; }
#include <iostream> #include <cstdio> #include <algorithm> #include <queue> #include <cstring> using namespace std; const int N = 1e3 + 5, M = 1e4 + 5, Inf = 0x3f3f3f3f; struct Edge{ int to, w; int next; }e[M]; int head[N], cnte; int t, n, m, u, v, w, st, en; int dis[N][2], vis[N][2], cnt[N][2]; struct Node{ int index, dis, type; Node(int a=0, int b=0, int c=0):index(a), dis(b), type(c){} bool operator < (const Node& b) const{ return this->dis > b.dis; } };//看到这各重载应该就懂要干嘛了吧,对,用priority_queue优化dijkstra! void dijkstra(){ priority_queue<Node> pq; memset(vis, 0, sizeof(vis)); memset(cnt, 0, sizeof(cnt)); for(int i=1; i<=n; ++i) dis[i][0] = dis[i][1] = Inf; dis[st][0] = 0; cnt[st][0] = 1;//最短路确定存在 // pq.push({st, 0, 0}); pq.push(Node(st, 0, 0)); while(pq.size()){ Node cur = pq.top(); pq.pop(); if(vis[cur.index][cur.type]) continue; vis[cur.index][cur.type] = true; for(int i = head[cur.index]; i; i = e[i].next) { int nindex = e[i].to, w = e[i].w; if(dis[nindex][0] > dis[cur.index][cur.type] + w){ dis[nindex][1] = dis[nindex][0]; cnt[nindex][1] = cnt[nindex][0]; dis[nindex][0] = dis[cur.index][cur.type] + w; cnt[nindex][0] = cnt[cur.index][cur.type]; // pq.push({nindex, dis[nindex][0], 0}); // pq.push({nindex, dis[nindex][1], 1}); pq.push(Node(nindex, dis[nindex][0], 0)); pq.push(Node(nindex, dis[nindex][1], 1)); } else{ if(dis[nindex][0] == dis[cur.index][cur.type] + w) cnt[nindex][0] += cnt[cur.index][cur.type]; else{ if(dis[nindex][1] > dis[cur.index][cur.type] + w){ dis[nindex][1] = dis[cur.index][cur.type] + w; cnt[nindex][1] = cnt[cur.index][cur.type]; // pq.push({nindex, dis[nindex][1], 1}); pq.push(Node(nindex, dis[nindex][1], 1)); } else{ if(dis[nindex][1] == dis[cur.index][cur.type] + w) cnt[nindex][1] += cnt[cur.index][cur.type]; } } } } } return; } inline void add_edge(const int& from, const int& to, const int& w){ ++cnte; e[cnte].to = to; e[cnte].w = w; e[cnte].next = head[from]; head[from] = cnte; return; } inline int read(){ char ch = getchar(); int ans = 0; while(ch<'0' || ch>'9') ch = getchar(); while(ch >= '0' && ch <= '9'){ ans = (ans << 3) + (ans << 1) + ch - '0'; ch = getchar(); }return ans; } int main() { t = read(); while(t--){ memset(head, 0, sizeof(head)); cnte = 0; n = read(), m = read(); for(int i = 1; i <= m; ++i){ u = read(), v = read(), w = read(); add_edge(u, v, w); } st = read(); en = read(); dijkstra(); if(dis[en][1] == dis[en][0] + 1) cnt[en][0] += cnt[en][1]; printf("%d\n", cnt[en][0]); } return 0; }