詳解 Dijkstra迪傑斯特拉算法

摘要:

描述:單源 最短路徑(找到指定頂點到其餘各點的最短路徑)
思想: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
MST路徑不等於最短路徑
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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章