圖論基礎——最短路問題
阿新 • • 發佈:2019-02-09
一。單源最短路問題1。(Bellman-Ford)
1.當圖為DAG時,把圖拓撲排序一下,然後用遞推關係d[i] = d[j] + e(i, j)。 2.當圖有圈且不存在負圈時,無法一下子全部遞推出來,我們用一個迴圈套住遞推式子,迴圈在每個節點都算出來時退出。用這個演算法能檢驗負圈:struct edge{int from, to, cost;}; edge es[MAX_E]; int d[MAX_V]; int V, E; 從s到所有點的最短距離 void shortest_path(int s) { for(int i = 0; i < V; i++) d[i] = INF; d[s] = 0; while(true) { bool update = false; for(int i = 0; i < E; i++){ edge e = es[i]; if(d[e.from] != INF && d[e.to] > d[e.from] + e.cost){ d[e.to] = d[e.from] + e.cost; update = true; } } if(!update) break; } }
用Bellman_Ford檢查負圈 bool find_negative_loop() { memset(d, 0, sizeof(d)); for(int i = 0; i < V; i++) for(int j = 0; j < E; j++) { edge e = es[j]; if(d[e.to] > d[e.from] + e.cost){ d[e.to] = d[e.from] + e.cost; if(i == v - 1) return true; } } return false; }
二。單源最短路問題2.(Dijkstra)
在上一個演算法中,如果d[i]沒有最短,那麼由d[i]遞推出來的都是無用的。而且,如果d[i]沒有變化,每次迴圈也要檢查一遍所有i連結的頂點。Dijkstra演算法則是從已經確定是最短的頂點往後推。已確定的距離最短的點就是沒用過的點中d[i]最小的i,這些d[i]一定是有已經確定的最短點推出來的。d陣列初始化為足夠大的數字。
int cost[MAX_V][MAX_V]; int d[MAX_V]; bool used[MAX_V]; int V; void dijkstra() { fill(d, d + V, INF); fill(used, used + V, false); d[s] = 0; while(true) { int v = -1; for(int u = 0; u < V; u++){ if(!used[u] && (v == -1 || d[u] < d[v]) ) v = u; } if(v == -1) break; used[v] = true; for(int u = 0; u < V; u++) d[u] = min(d[u], d[v] + cost[v][u]); } }
找最小點的操作可以用堆來優化。
堆優化
struct edge{int to, cost; };
typedef pair<int, int> P;
int V;
vector<edge> G[MAX_V];
int d[MAX_V];
void dijkstra(int s)
{
priority_queue<P, vector<P>, greater<P> > que;
fill(d, d + V, INF);
d[s] = 0;
que.push(P(0, s));
while(!que.empty())
{
P p = que.top();que.pop();
int v = p.second;
if(d[v] < p.first)
continue;
for(int i = 0; i < G[v].size(); i++){
edge e = G[v][i];
if(d[e.to] > d[v] + e.cost){
d[e.to] = d[v] + e.cost;
que.push(P(d[e.to], e.to));
}
}
}
}
如果存在負邊,要用第一種。
三。任意兩點間的最短路(Floyd_Warshall)
用DP解決。這和走不同路撿不同數量蘋果差不多。
如果頂點上限為k:逗號左邊是不含k,右邊是含k。
狀態轉移方程:c[i, j, k]=min{c[i, j, k-1], c [i, k, k-1]+c [k, j, k-1]},k>0。
實現時不用為上限k多給陣列開一維
int d[MAX_V][MAX_V];
int V;
void warshall_floyd()
{
for(int k = 0; k < V; k++)
for(int i = 0; i < V; i++)
for(int j = 0; j < V; j++)
d[i][j] = min(d[i, j], d[i][k] + d[k][j]);
}
一定先遞增列舉k,這樣k才能從k-1轉移過來。