摘要:
描述:單源 最短路徑(找到指定頂點到其餘各點的最短路徑)
思想:greedy
複雜度:
時間複雜度:O(n^2)
空間複雜度:O(n)
思想概要:
關鍵:每次將V-U內到起點最短的頂點加入到U,並且嘗試作爲中轉點,縮短起點到V-U其他頂點的距離
這樣做,下一條構成的路徑(設終點爲x),必然是v0到vx的最短路徑,且必定是b2或者是B1+b2。即,v0到vx的最短路徑不會是通過A1 + a2 + A3這種方法產生的
輔助圖解:
U表示已找出的,從v0出發的,最短路徑的終點;V-U是待找出的
(
1:所有頂點都在U內的路徑;
2:連通U和V-U的弧
3:所有頂點都在V-U內的路徑;
Vx表示頂點,
a2表示某條連通U和V-U的弧
A1表示連通起點到弧a2的在U裏的頂點的路徑
A3表示連通弧a2的在V-U裏的頂點到終點的路徑
)
證明:
簡要證明:
按照這個過程,會存在一條比b2/B1+b2算法更短的路徑,那就只能是A1 + a2 + A3,而A1 + a2 + A3比b2/B1+b2小意味着a2必須小於b2,但這與b2的語義(b2是當前連通U和V-U最短的弧) 相矛盾
詳細證明:(反證法:要證v0到x可能是通過A1 + a2 + A3產生的)
假設v0到x的最短路徑的構成是成分是A1 + a2 + A3,則A1 + a2 + A3必然小於b2,且A1 + a2 + A3必然小於任意一條B1+b2。
如果A1 + a2 + A3 < b2,那麼A1,a2,A3三個成分至少有一個是小於b2的。
然後你就突然發現,只要允許路徑權值和爲負值的話,這沒有任何毛病,沒辦法反證!!只有所有弧的權值>=0證明才能成立。
在所有弧的權值都>=0前提下
》對於A1 + a2 + A3 < b2的推論:
A1,a2,A3必須都小於b2,但既然a2<b2,按照約定的算法,下一條被加入的弧應該是a2而不是b2,與算法事實相矛盾
》對於A1 + a2 + A3 < B1 + b2的推論:
如果有A1 + a2 + A3 小於任意一條 B1+b2,就有A1 + a2 < B1+b2,意味着U中任意一個頂點到x的距離都大於a2(即存在另一條弧,比選定的將加入弧更小,因此這次選擇是錯誤的),但按照算法約定,a2纔是下一個被加入的弧
我最大的疑惑:
1.最小生成樹的任意兩點之間的路徑是不是連通這兩點的最短路徑?(這也是Dijkstra跟Prim的區別所在)
答:不是,由下圖可知,A{<v0,v1>,<v1,v2>,<v2,v3>}和B{<v0,v1>,<v1,v3>,<v2,v3>}都是最小生成樹,但A中從v0到v3路徑長6,B中v0到v3路徑長4
2.在將頂點從V-U加入到U過程中,是選擇v0到V-U最短的路徑,還是選擇U到V-U最短的路徑
答:選擇v0到V-U最短的路徑,道理同上,如果只考慮U到V-U而不從這條路徑考慮,遇到這種<v1,v2>和<v0,v3>弧相等的情況就沒辦法準確的抉擇
3.(假設在一個連通圖裏)按迪傑斯特拉方法將某點到其餘所有頂點的最短路徑都建立完後,是不是一棵最小生成樹
(假設在一個無向連通圖裏)首先最短路徑都建立完後,肯定是一個連通圖,因爲所有點都和v0連通,而連通是可傳遞的
其次,它肯定是一棵生成樹,否則它必定有環(連通但不是極小意味着有環)但這跟“只要頂點被加入U,v0到它的最短路徑就已經被找到”的結論不符合
然後它也是最小生成樹。假設它不是MST,
解釋1:那麼在v0到達某點vx之間必定存在另一條更短的路徑,但這是不可能大,正如上面說,如果這另一條是v0直達vx的,就一定是“選V-U中v0到它路徑最短的點”的結果;如果是其他點v間接到達的,因爲v0到v肯定比v0到vx更快,所以v必然在vx之前就加入到U了
解釋2:那麼某點vx被加入U時選擇的必然是另一條更短的路徑,且並非B1+b2或b2產生的結果;
那就只能是A1+a2+A3這種方法的結果了,但這樣被a2連通的節點必定先於vx被加入U,也就是說,實際上會演變成B1+b2的情況
=====================================================================
實現:
注意,迪傑斯特拉的完整過程,是找到一個起點到其餘所有頂點的最短路徑,如果只希望找到指定終點的最短路徑,最糟糕的情況下,需要先找到前面所有頂點的最短路徑
其實,Dijkstra和Prim的思想十分類似,都是貪心算法的思想,我自己想的時候,都是先將需要的數據準備好。但這兩種方法的精妙之處,就在於需要的數據是一邊更新狀態一邊獲取的。
#define INF INT_MAX
struct Edge
{
int b, e, w;
Edge() :b(-1), e(-1), w(-1) {}
Edge(int begin, int end, int weight) :b(begin), e(end), w(weight) {}
};
struct AdjMatrix
{
int vn, en;
vector<vector<int>> m;
AdjMatrix(const vector<vector<int>>& mat)
{
m = mat;
vn = mat.size();
}
};
list<Edge> Dijkstra(const AdjMatrix& mat, int src, int dst)
{
if (mat.vn <= 0) return list<Edge>();
if (src == dst) return list<Edge>{Edge(src,src,0)};
int n = mat.vn;
list<Edge> path;
vector<bool> vs(n, false);
vector<int> prev(n, src);
vector<int> minArc(n, INF);
minArc[src] = 0;
vs[src] = true;
prev[src] = -1;
int k = 1;
int v = src;
while (k <= n - 1)
{
int minw = INT_MAX;
int nextv = -1;
for (int i = 0; i < n; ++i)
{
if (vs[i]) continue;
if ((mat.m[v][i] != INF) && (minArc[v] + mat.m[v][i] < minArc[i])) // 前提是可達
{
minArc[i] = minArc[v] + mat.m[v][i];
prev[i] = v;
}
if (minArc[i] < minw)
{
minw = minArc[i];
nextv = i;
}
}
if (nextv == -1) break;
v = nextv;
vs[v] = true;
if (v == dst) break;
++k;
}
if (v == dst) // 回溯構建路徑
{
int pv, cur = dst;
do {
pv = prev[cur];
path.push_front(Edge(pv, cur, mat.m[pv][cur]));
cur = pv;
} while (pv != src);
}
return path;
}
TestCase:
int main()
{
vector<vector<int>> m{
{INF,4,INF,INF,2},
{4,INF,INF,INF,INF},
{INF,INF,INF,1,3},
{INF,INF,1,INF,INF},
{2,INF,3,INF,INF},
};
AdjMatrix mat(m);
auto l = Floyd(mat);
getchar();
return 0;
}
參考
【1】https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-using-priority_queue-stl/
【2】https://www.geeksforgeeks.org/widest-path-problem-practical-application-of-dijkstras-algorithm/