資料結構基礎溫故-5.圖(下):最短路徑
圖的最重要的應用之一就是在交通運輸和通訊網路中尋找最短路徑。例如在交通網路中經常會遇到這樣的問題:兩地之間是否有公路可通;在有多條公路可通的情況下,哪一條路徑是最短的等等。這就是帶權圖中求最短路徑的問題,此時路徑的長度不再是路徑上邊的數目總和,而是路徑上的邊所帶權值的和。帶權圖分為無向帶權圖和有向帶權圖,但如果從A地到B地有一條公路,A地和B地的海拔高度不同,由於上坡和下坡的車速不同,那麼邊<A,B>和邊<B,A>上表示行駛時間的權值也不同。考慮到交通網路中的這種有向性,本篇也只討論有向帶權圖的最短路徑。一般習慣將路徑的開始頂點成為源點,路徑的最後一個頂點成為終點。
一、單源點最短路徑
單源點最短路徑是指給定一個出發點(源點)和一個有向圖,求出源點到其他各頂點之間的最短路徑。對於下圖左邊所示的有向圖G,設頂點0為源點,則其到其他各頂點的最短路徑如下圖右邊所示。
從上圖中可以看出,從源點0到終點4一共有4條路徑:
(1)0→4 路徑長度:90
(2)0→3→4 路徑長度:90
(3)0→1→2→4 路徑長度:70
(4)0→3→2→4 路徑長度:60
可以看出,源點0到終點4的最短路徑為第(4)條路徑。為了求出最短路徑,前人們已經做很多工作,為我們奉獻了兩個重要的演算法:Dijkstra(迪傑斯特拉)和Floyd(弗洛伊德)演算法,下面我們就來看看這兩個演算法。
二、Dijkstra演算法
2.1 演算法思想
Dijkstra在對最短路徑的求解方式做了大量的觀察之後,首先提出了按路徑長度遞增產生與頂點之間的路徑最短的演算法,以他的名字稱之為Dijkstra演算法。
Dijkstra演算法的基本思想是:將圖中頂點的集合分為兩組S和U,並按最短路徑長度的遞增次序依次將集合U中的頂點加入到S中,在加入的過程中,總保持從原點v到S中各頂點的最短路徑長度不大於從原點v到U中任何頂點的最短路徑長度。
Dijkstra演算法採用鄰接矩陣儲存圖的資訊並計算源點到圖中其餘頂點的最短路徑,如下圖所示。
2.2 演算法實現
(1)程式碼實現
static void Dijkstra(int[,] cost, int v) { int n = cost.GetLength(1); // 計算頂點個數 int[] s = new int[n]; // 集合S int[] dist = new int[n]; // 結果集 int[] path = new int[n]; // 路徑集 for (int i = 0; i < n; i++) { // 初始化結果集 dist[i] = cost[v, i]; // 初始化路徑集 if (cost[v, i] > 0) { // 如果源點與頂點存在邊 path[i] = v; } else { // 如果源點與頂點不存在邊 path[i] = -1; } } s[v] = 1; // 將源點加入集合S path[v] = 0; for (int i = 0; i < n; i++) { int u = 0; // 指示剩餘頂點在dist集合中的最小值的索引號 int minDis = int.MaxValue; // 指示剩餘頂點在dist集合中的最小值大小 // 01.計算dist集合中的最小值 for (int j = 0; j < n; j++) { if (s[j] == 0 && dist[j] > 0 && dist[j] < minDis) { u = j; minDis = dist[j]; } } s[u] = 1; // 將抽出的頂點放入集合S中 // 02.計算源點經過頂點u到其餘頂點的距離 for (int j = 0; j < n; j++) { // 如果頂點不在集合S中 if (s[j] == 0) { // 加入的頂點如與其餘頂點存在邊,並且重新計算的值小於原值 if (cost[u, j] > 0 && (dist[j] == 0 || dist[u] + cost[u, j] < dist[j])) { // 計算更小的值代替原值 dist[j] = dist[u] + cost[u, j]; path[j] = u; } } } } // 列印源點到各頂點的路徑及距離 for (int i = 0; i < n; i++) { if (s[i] == 1) { Console.Write("從{0}到{1}的最短路徑為:", v, i); Console.Write(v + "→"); // 使用遞迴獲取指定頂點在路徑上的前一頂點 GetPath(path, i, v); Console.Write(i + Environment.NewLine + "SUM:"); Console.WriteLine("路徑長度為:{0}", dist[i]); } } } static void GetPath(int[] path, int i, int v) { int k = path[i]; if (k == v) { return; } GetPath(path, k, v); Console.Write(k + "→"); }
這裡經歷了三個步驟,第一步是構造集合U,第二步是尋找最短路徑,第三步是重新計算替換原有路徑。
(2)基本測試
這裡要構造的有向帶權圖如下圖所示:
static void DijkstraTest() { int[,] cost = new int[5, 5]; // 初始化鄰接矩陣 cost[0, 1] = 10; cost[0, 3] = 30; cost[0, 4] = 90; cost[1, 2] = 50; cost[2, 4] = 10; cost[3, 2] = 20; cost[3, 4] = 60; // 使用Dijkstra演算法計算最短路徑 Dijkstra(cost, 0); }View Code
執行結果如下圖所示:
從圖中可以看出,從源點0到終點4的最短路徑為:0→3→2→4,該路徑長度為60。
三、Floyd演算法
3.1 演算法思想
Floyd(弗洛伊德)演算法提出了另外一種用於計算有向圖中所有頂點間的最短路徑,這種演算法成為Floyd演算法,它的形式較Dijkstra演算法更為簡單。
Floyd演算法仍然使用鄰接矩陣儲存的圖,同時定義了一個二維陣列A,其中每一個分量A[i,j]是頂點i到頂點j的最短路徑長度。另外還使用了另一個二維陣列path來儲存最短路徑資訊。Floyd演算法的基本思想如下:
(1)初始時,對圖中任意兩個頂點Vi和Vj,如果從Vi到Vj存在邊,則從Vi到Vj存在一條長度為cost[i,j]的路徑,但該路徑不一定是最短路徑。初始化時,A[i,j]=cost[i,j]。
(2)在圖中任意兩個頂點Vi和Vj之間加入頂點Vk,如果Vi經Vk到達Vj的路徑存在並更短,則用A[i,k]+A[k,j]的值代替原來的A[i,j]值。
(3)重複步驟(2),直到將所有頂點作為中間點依次加入集合中,並通過迭代公式不斷修正A[i,j]的值,最終獲得任意頂點的最短路徑長度。
3.2 演算法實現
(1)程式碼實現
static void Floyd(int[,] cost, int v) { int n = cost.GetLength(1); // 獲取頂點個數 int[,] A = new int[n, n]; // 存放最短路徑長度 int[,] path = new int[n, n];// 存放最短路徑資訊 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { // 輔助陣列A和path的初始化 A[i, j] = cost[i, j]; path[i, j] = -1; } } // Flyod演算法核心程式碼部分 for (int k = 0; k < n; k++) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { // 如果存在中間頂點K的路徑 if (i != j && A[i, k] != 0 && A[k, j] != 0) { // 如果加入中間頂點k後的路徑更短 if (A[i, j] == 0 || A[i, j] > A[i, k] + A[k, j]) { // 使用新路徑代替原路徑 A[i, j] = A[i, k] + A[k, j]; path[i, j] = k; } } } } } // 列印最短路徑及路徑長度 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (A[i, j] == 0) { if (i != j) { Console.WriteLine("從{0}到{1}沒有路徑!", i, j); } } else { Console.Write("從{0}到{1}的路徑為:", i, j); Console.Write(i + "→"); // 使用遞迴獲取指定頂點的路徑 GetPath(path, i, j); Console.Write(j + " "); Console.WriteLine("路徑長度為:{0}", A[i, j]); } } Console.WriteLine(); } } static void GetPath(int[,] path, int i, int j) { int k = path[i, j]; if (k == -1) { return; } GetPath(path, i, k); Console.Write(k + "→"); GetPath(path, k, j); }
(2)基本測試
static void FloydTest() { int[,] cost = new int[5, 5]; // 初始化鄰接矩陣 cost[0, 1] = 10; cost[0, 3] = 30; cost[0, 4] = 90; cost[1, 2] = 50; cost[2, 4] = 10; cost[3, 2] = 20; cost[3, 4] = 60; // 使用Flyod演算法計算最短路徑 Floyd(cost, 0); }View Code
執行結果如下圖所示:
參考資料
(1)程傑,《大話資料結構》
(2)陳廣,《資料結構(C#語言描述)》
(3)段恩澤,《資料結構(C#語言版)》
作者:周旭龍
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。
相關推薦
資料結構基礎溫故-5.圖(下):最短路徑
圖的最重要的應用之一就是在交通運輸和通訊網路中尋找最短路徑。例如在交通網路中經常會遇到這樣的問題:兩地之間是否有公路可通;在有多條公路可通的情況下,哪一條路徑是最短的等等。這就是帶權圖中求最短路徑的問題,此時路徑的長度不再是路徑上邊的數目總和,而是路徑上的邊所帶權值的和。帶權圖分為無向帶權圖和有向帶權圖,但如
資料結構基礎溫故-5.圖(中):最小生成樹演算法
圖的“多對多”特性使得圖在結構設計和演算法實現上較為困難,這時就需要根據具體應用將圖轉換為不同的樹來簡化問題的求解。 一、生成樹與最小生成樹 1.1 生成樹 對於一個無向圖,含有連通圖全部頂點的一個極小連通子圖成為生成樹(Spanning Tree)。其本質就是從連通圖任一頂點出發進行遍歷操作所經過
資料結構基礎溫故-5.圖(中):圖的遍歷演算法
上一篇我們瞭解了圖的基本概念、術語以及儲存結構,還對鄰接表結構進行了模擬實現。本篇我們來了解一下圖的遍歷,和樹的遍歷類似,從圖的某一頂點出發訪問圖中其餘頂點,並且使每一個頂點僅被訪問一次,這一過程就叫做圖的遍歷(Traversing Graph)。如果只訪問圖的頂點而不關注邊的資訊,那麼圖的遍歷十分簡單,使用
資料結構基礎溫故-5.圖(上):圖的基本概念
前面幾篇已經介紹了線性表和樹兩類資料結構,線性表中的元素是“一對一”的關係,樹中的元素是“一對多”的關係,本章所述的圖結構中的元素則是“多對多”的關係。圖(Graph)是一種複雜的非線性結構,在圖結構中,每個元素都可以有零個或多個前驅,也可以有零個或多個後繼,也就是說,元素之間的關係是任意的。現實生活中的很多
資料結構基礎溫故-6.查詢(下):雜湊表
雜湊(雜湊)技術既是一種儲存方法,也是一種查詢方法。然而它與線性表、樹、圖等結構不同的是,前面幾種結構,資料元素之間都存在某種邏輯關係,可以用連線圖示表示出來,而雜湊技術的記錄之間不存在什麼邏輯關係,它只與關鍵字有關聯。因此,雜湊主要是面向查詢的儲存結構。雜湊技術最適合的求解問題是查詢與給定值相等的記錄。
資料結構基礎溫故-6.查詢(上):基本查詢與樹表查詢
只要你開啟電腦,就會涉及到查詢技術。如炒股軟體中查股票資訊、硬碟檔案中找照片、在光碟中搜DVD,甚至玩遊戲時在記憶體中查詢攻擊力、魅力值等資料修改用來作弊等,都要涉及到查詢。當然,在網際網路上查詢資訊就更加是家常便飯。查詢是計算機應用中最常用的操作之一,也是許多程式中最耗時的一部分,查詢方法的優劣對於系統的執
資料結構基礎之圖(下):最短路徑
轉自:http://www.cnblogs.com/edisonchou/p/4691020.html 圖(下):最短路徑 圖的最重要的應用之一就是在交通運輸和通訊網路中尋找最短路徑。例如在交通網路中經常會遇到這樣的問題:兩地之間是否有公路可通;在有多條公路可通的情況下,哪
資料結構基礎溫故-1.線性表(中)
在上一篇中,我們學習了線性表最基礎的表現形式-順序表,但是其存在一定缺點:必須佔用一整塊事先分配好的儲存空間,在插入和刪除操作上需要移動大量元素(即操作不方便),於是不受固定儲存空間限制並且可以進行比較快捷地插入和刪除操作的連結串列橫空出世,所以我們就來複習一下連結串列。 一、單鏈表基礎 1.1 單鏈表的
資料結構基礎溫故-4.樹與二叉樹(下)
上面兩篇我們瞭解了樹的基本概念以及二叉樹的遍歷演算法,還對二叉查詢樹進行了模擬實現。數學表示式求值是程式設計語言編譯中的一個基本問題,表示式求值是棧應用的一個典型案例,表示式分為字首、中綴和字尾三種形式。這裡,我們通過一個四則運算的應用場景,藉助二叉樹來幫助求解表示式的值。首先,將表示式轉換為二叉樹,然後通過
資料結構基礎溫故-4.樹與二叉樹(中)
在上一篇中,我們瞭解了樹的基本概念以及二叉樹的基本特點和程式碼實現,還用遞迴的方式對二叉樹的三種遍歷演算法進行了程式碼實現。但是,由於遞迴需要系統堆疊,所以空間消耗要比非遞迴程式碼要大很多。而且,如果遞迴深度太大,可能系統撐不住。因此,我們使用非遞迴(這裡主要是迴圈,迴圈方法比遞迴方法快, 因為迴圈避免了一系
資料結構基礎溫故-4.樹與二叉樹(上)
前面所討論的線性表元素之間都是一對一的關係,今天我們所看到的結構各元素之間卻是一對多的關係。樹在計算機中有著廣泛的應用,甚至在計算機的日常使用中,也可以看到樹形結構的身影,如下圖所示的Windows資源管理器和應用程式的選單都屬於樹形結構。樹形結構是一種典型的非線性結構,除了用於表示相鄰關係外,還可以表示層次
資料結構基礎溫故-1.線性表(下)
在上一篇中,我們瞭解了單鏈表與雙鏈表,本次將單鏈表中終端結點的指標端由空指標改為指向頭結點,就使整個單鏈表形成一個環,這種頭尾相接的單鏈表稱為單迴圈連結串列,簡稱迴圈連結串列(circular linked list)。 一、迴圈連結串列基礎 1.1 迴圈連結串列節點結構 迴圈連結串列和單鏈表的
資料結構基礎之圖(中):最小生成樹演算法
轉自:http://www.cnblogs.com/edisonchou/p/4681602.html 圖(中):最小生成樹演算法 圖的“多對多”特性使得圖在結構設計和演算法實現上較為困難,這時就需要根據具體應用將圖轉換為不同的樹來簡化問題的求解。 一、生成樹與最小生成
資料結構基礎溫故-2.棧
現實生活中的事情往往都能總結歸納成一定的資料結構,例如餐館中餐盤的堆疊和使用,羽毛球筒裡裝的羽毛球等都是典型的棧結構。而在.NET中,值型別線上程棧上進行分配,引用型別在託管堆上進行分配,本文所說的“棧”正是這種資料結構。棧和佇列都是常用的資料結構,它們的邏輯結構與線性表相通,不同之處則在於操作受某種特殊限制
資料結構基礎溫故-3.佇列
在日常生活中,佇列的例子比比皆是,例如在車展排隊買票,排在隊頭的處理完離開,後來的必須在隊尾排隊等候。在程式設計中,佇列也有著廣泛的應用,例如計算機的任務排程系統、為了削減高峰時期訂單請求的訊息佇列等等。與棧類似,佇列也是屬於操作受限的線性表,不過佇列是隻允許在一端進行插入,在另一端進行刪除。在其他資料結構如
資料結構基礎溫故-7.排序
排序(Sorting)是計算機內經常進行的一種操作,其目的是將一組“無序”的記錄序列調整為按關鍵字“有序”的記錄序列。如何進行排序,特別是高效率地進行排序時計算機工作者學習和研究的重要課題之一。排序有內部排序和外部排序之分,若整個排序過程不需要訪問外存便能完成,則稱此類排序為內部排序,反之則為外部排序。本篇主
陳越《資料結構》第七講 圖(中)二
最短路徑問題 定義: 在網路中,求兩個不同頂點之間的所有路徑中,邊的權值之和最小的那一條路徑。這條路徑就是兩點之間的 最短路徑(Shortest Path)。 - 第一個頂點為 源點
資料結構-棧和佇列面試題(下)
面試題四:元素出棧、入棧順序的合法性。如入棧的序列(1,2,3,4,5),出棧序列為(4,5,3,2,1)。 思路: ①首先判斷出棧入棧序列長度是否一致,不一致直接返回false; ②借用一個臨時的棧,依次遍歷入棧序列的每一個元素,每次
資料結構入門---初始二叉樹(下)
這篇文章我們準備將二叉樹實現為具體的程式碼 首先我們要從二叉樹的遍歷說起。二叉樹的遍歷主要有四種形式 1. 前序遍歷 方法:如果二叉樹為空,則直接返回。如果二叉樹非空,則訪問根結點,再前序遍歷左子樹,然後前序遍歷右子樹 我們可以知道這樣的遍歷方式是以遞迴
圖相關(二)圖的鄰接矩陣表示(C++)及最短路徑演算法
一.Dijikstra演算法 注意:計算最短路徑時,需要把鄰接矩陣中沒有邊的位置初始化為無窮大;此處以INF表示,INF可以取0x3f3f3f3f,不能直接設為INT_MAX(因為做加法時會上溢)。 測試用圖: 其鄰接矩陣表示為: vector<vector<int