日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

修路问题算法的总结

發布時間:2025/3/20 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 修路问题算法的总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

十一期間看到了以下幾個類似的問題,最開始也是有點混淆的狀態,這里做一下簡單的學習記錄,希望可以為有同樣問題的小伙伴提供幫助,篇幅比較長,建議收藏后再閱讀。
問題描述如下(這里以簡單得連接圖表示各個連接信息):
1.有7個村莊(A, B, C, D, E, F, G) ,現在需要修路把7個村莊連通,各個村莊的距離用邊線表示(權) ,比如 A – B 距離 5公里,問:如何修路保證各個村莊都能連通,并且總的修建公路總里程最短?

2.與1的問題是一樣的,某城市新增7個站點(A, B, C, D, E, F, G) ,現在需要修路把7個站點連通,各個站點的距離用邊線表示(權) ,比如 A – B 距離 12公里,問:如何修路保證各個站點都能連通,并且總的修建公路總里程最短?

3.有7個村莊(A, B, C, D, E, F, G) ,現在有六個郵差,從G點出發,需要分別把郵件分別送到 A, B, C , D, E, F 六個村莊,各個村莊的距離用邊線表示(權) ,比如 A – B 距離 5公里
問:如何計算出G村莊到 其它各個村莊的最短距離? 如果從其它點出發到各個點的最短距離又是多少?

4.有7個村莊(A, B, C, D, E, F, G),各個村莊的距離用邊線表示(權) ,比如 A – B 距離 5公里
問:如何計算出各村莊到 其它各村莊的最短距離?

其實以上四個問題的描述可以簡述成以下三個類別:
1.求各個點都連接且權值最小的路線(問題1和問題2);
2.以某一個點作為出發點,求這個點到其它各個點的權值最小的路線(問題3,可以看成是問題4的一個子集);
3.以每一個點作為出發點,求這些點到其它各個點的權值最小的路線(問題4,可以看成是問題3 的一個全集)。

問題的描述很簡單,其實都可以歸類為修路問題,需要考慮采用什么方式對問題進行求解。修路問題本質就是就是最小生成樹問題,什么是最小生成樹(Minimum Cost Spanning Tree,簡稱MST)?給定一個帶權的無向連通圖,如何選取一棵生成樹,使樹上所有邊上權的總和為最小,這叫最小生成樹。最小生成樹有以下特點:
(1)N個頂點,一定有N-1條邊
(2)包含全部頂點
(3)N-1條邊都在圖中

求最小生成樹的算法主要是普里姆算法和克魯斯卡爾算法。

普里姆算法

1)普利姆(Prim)算法求最小生成樹,也就是在包含n個頂點的連通圖中,找出只有(n-1)條邊包含所有n個頂點的連通子圖,也就是所謂的極小連通子圖;
2)普利姆的算法如下:
(1)設G=(V,E)是連通網,T=(U,D)是最小生成樹,V,U是頂點集合,E,D是邊的集合;
(2)若從頂點u開始構造最小生成樹,則從集合V中取出頂點u放入集合U中,標記頂點v的visited[u]=1;
(3)若集合U中頂點ui與集合V-U中的頂點vj之間存在邊,則尋找這些邊中權值最小的邊,但不能構成回路,將頂點vj加入集合U中,將邊(ui,vj)加入集合D中,標記visited[vj]=1;
(4)重復步驟(2),直到U與V相等,即所有頂點都被標記為訪問過,此時D中有n-1條邊。

以此算法來分析問題1。假設從點A 開始,獲取所有連接的路線,每次取最小的路線及此路線的另一個點加入,以此循環,步驟大致如下:
①從A頂點開始處理 A-C [7] A-G[2] A-B[5]=> <A,G> 2
② <A,G> 開始 , 將A 和 G 頂點和他們相鄰的還沒有訪問的頂點進行處理 A-C[7] A-B[5] G-B[3] G-E[4] G-F[6]=》<A,G,B>
③ <A,G,B> 開始,將A,G,B 頂點 和他們相鄰的還沒有訪問的頂點進行處理 A-C[7] G-E[4] G-F[6] B-D[9]=><A,G,B,E>
④{A,G,B,E}->F加入, 對應 邊<E,F> 權值:5
⑤{A,G,B,E,F}->D加入 , 對應 邊<F,D> 權值:4
⑥{A,G,B,E,F,D}->C加入 , 對應 邊<A,C> 權值:7 ===> <A,G,B,E,F,D,C>
有了解決問題的思路,接下來用代碼直接對問題進行求解。

/*** @Author likangmin* @create 2020/10/9 16:46*/ public class PrimAlgorithm {public static void main(String[] args) {char[] data = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};int dataNum = data.length;//鄰接矩陣的關系使用二維數組表示,10000這個大數,表示兩個點不聯通int[][] weight = new int[][]{{10000, 5, 7, 10000, 10000, 10000, 2},{5, 10000, 10000, 9, 10000, 10000, 3},{7, 10000, 10000, 10000, 8, 10000, 10000},{10000, 9, 10000, 10000, 10000, 4, 10000},{10000, 10000, 8, 10000, 10000, 5, 4},{10000, 10000, 10000, 4, 5, 10000, 6},{2, 3, 10000, 10000, 4, 6, 10000},};Graph graph = new Graph(dataNum);MinTrees minTrees = new MinTrees();minTrees.createGraph(graph, data, weight);minTrees.showGraph(graph);int num = 0;while (true) {System.out.println("請輸入從哪個點開始('A', 'B', 'C', 'D', 'E', 'F', 'G'):");Scanner sc = new Scanner(System.in);String str = sc.next();switch (str) {case "A":num = 0;break;case "B":num = 1;break;case "C":num = 2;break;case "D":num = 3;break;case "E":num = 4;break;case "F":num = 5;break;case "G":num = 6;break;default:break;}minTrees.prim(graph, num);}}}class MinTrees {public void createGraph(Graph graph, char[] data, int[][] weight) {for (int i = 0; i < data.length; i++) {graph.data[i] = data[i];for (int j = 0; j < data.length; j++) {graph.weight[i][j] = weight[i][j];}}}public void showGraph(Graph graph) {for (int[] link : graph.weight) {System.out.println(Arrays.toString(link));}}public void prim(Graph graph, int index) {//h1 和 h2 記錄兩個頂點的下標int h1 = -1;int h2 = -1;int[] visited = new int[graph.dataNum];//把當前這個結點標記為已訪問visited[index] = 1;int min = 10000;//因為有 graph.verxs頂點,普利姆算法結束后,有 graph.verxs-1邊for (int k = 1; k < graph.dataNum; k++) {//這個是確定每一次生成的子圖 ,和哪個結點的距離最近for (int i = 0; i < graph.dataNum; i++) {for (int j = 0; j < graph.dataNum; j++) {if (visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < min) {//min(尋找已經訪問過的結點和未訪問過的結點間的權值最小的邊)min = graph.weight[i][j];h1 = i;h2 = j;}}}//找到一條邊是最小System.out.println("邊<" + graph.data[h1] + "," + graph.data[h2] + "> 權值:" + min);//將當前這個結點標記為已經訪問visited[h2] = 1;min = 10000;}} }class Graph {//表示圖的節點個數int dataNum;//存放結點數據char[] data;//存放邊,就是我們的鄰接矩陣int[][] weight;public Graph(int dataNum) {this.dataNum = dataNum;this.data = new char[dataNum];this.weight = new int[dataNum][dataNum];} }

我們看一下輸出結果(只選取了A和B,結果正確,其他的讀者可以自行驗證):

請輸入從哪個點開始('A', 'B', 'C', 'D', 'E', 'F', 'G'): A<A,G> 權值:2<G,B> 權值:3<G,E> 權值:4<E,F> 權值:5<F,D> 權值:4<A,C> 權值:7 請輸入從哪個點開始('A', 'B', 'C', 'D', 'E', 'F', 'G'): B<B,G> 權值:3<G,A> 權值:2<G,E> 權值:4<E,F> 權值:5<F,D> 權值:4<A,C> 權值:7

當然,問題2依然可以使用普里姆算法進行求解,讀者有需要可以自行使用該方法求解,這里給大家介紹另外一種求解修路問題的方法:克魯斯卡爾算法,我們將對問題2使用克魯斯卡爾算法進行求解。

克魯斯卡爾算法

基本思想:按照權值從小到大的順序選擇n-1條邊,并保證這n-1條邊不構成回路。
具體做法:首先構造一個只含n個頂點的森林,然后依權值從小到大從連通網中選擇邊加入到森林中,并使森林中不產生回路,直至森林變成一棵樹為止。
例如,對于如問題2的所示的連通網可以有多棵權值總和不相同的生成樹:


而我們要選取的是權值最小的一個路線。以問題2中的圖為例,來對克魯斯卡爾進行演示(假設,用數組R保存最小生成樹結果):
第1步:將邊<E,F>加入R中。
邊<E,F>的權值最小,因此將它加入到最小生成樹結果R中。
第2步:將邊<C,D>加入R中。
上一步操作之后,邊<C,D>的權值最小,因此將它加入到最小生成樹結果R中。
第3步:將邊<D,E>加入R中。
上一步操作之后,邊<D,E>的權值最小,因此將它加入到最小生成樹結果R中。
第4步:將邊<B,F>加入R中。
上一步操作之后,邊<C,E>的權值最小,但<C,E>會和已有的邊構成回路;因此,跳過邊<C,E>。同理,跳過邊<C,F>。將邊<B,F>加入到最小生成樹結果R中。
第5步:將邊<E,G>加入R中。
上一步操作之后,邊<E,G>的權值最小,因此將它加入到最小生成樹結果R中。
第6步:將邊<A,B>加入R中。
上一步操作之后,邊<F,G>的權值最小,但<F,G>會和已有的邊構成回路;因此,跳過邊<F,G>。同理,跳過邊<B,C>。將邊<A,B>加入到最小生成樹結果R中。
此時,最小生成樹構造完成!它包括的邊依次是:<E,F> <C,D> <D,E> <B,F> <E,G> <A,B>。

根據前面介紹的克魯斯卡爾算法的基本思想和做法,我們能夠了解到,克魯斯卡爾算法重點需要解決的以下兩個問題:
問題一,對圖的所有邊按照權值大小進行排序;問題二,將邊添加到最小生成樹中時,怎么樣判斷是否形成了回路。
問題一很好解決,采用排序算法進行排序即可。問題二處理方式是:記錄頂點在"最小生成樹"中的終點,頂點的終點是"在最小生成樹中與它連通的最大頂點"。然后每次需要將一條邊添加到最小生存樹時,判斷該邊的兩個頂點的終點是否重合,重合的話則會構成回路。

以上圖為例,在將<E,F> <C,D> <D,E>加入到最小生成樹R中之后,這幾條邊的頂點就都有了終點:
C的終點是F, D的終點是F,E的終點是F,F的終點是F。將所有頂點按照從小到大的順序排列好之后,某個頂點的終點就是"與它連通的最大頂點"。 因此,接下來,雖然<C,E>是權值最小的邊。但是C和E的終點都是F,即它們的終點相同,因此,將<C,E>加入最小生成樹的話,會形成回路。這就是判斷回路的方式。也就是說,我們加入的邊的兩個頂點不能都指向同一個終點,否則將構成回路。
有了解決問題的思路,接下來用代碼直接對問題進行求解。

/*** @Author likangmin* @create 2020/10/9 19:24*/ public class KruskalCase {private int edgeNum; //邊的個數private char[] vertexs; //頂點數組private int[][] matrix; //鄰接矩陣//使用 INF 表示兩個頂點不能連通private static final int INF = Integer.MAX_VALUE;public static void main(String[] args) {char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};//克魯斯卡爾算法的鄰接矩陣 int matrix[][] = {/*A*//*B*//*C*//*D*//*E*//*F*//*G*//*A*/ { 0, 12, INF, INF, INF, 16, 14},/*B*/ { 12, 0, 10, INF, INF, 7, INF},/*C*/ { INF, 10, 0, 3, 5, 6, INF},/*D*/ { INF, INF, 3, 0, 4, INF, INF},/*E*/ { INF, INF, 5, 4, 0, 2, 8},/*F*/ { 16, 7, 6, INF, 2, 0, 9},/*G*/ { 14, INF, INF, INF, 8, 9, 0}}; //創建KruskalCase 對象實例KruskalCase kruskalCase = new KruskalCase(vertexs, matrix);kruskalCase.kruskal();}//構造器public KruskalCase(char[] vertexs, int[][] matrix) {this.vertexs=vertexs;this.matrix=matrix;//統計邊的條數for(int i =0; i < vlen; i++) {for(int j = i+1; j < vlen; j++) {if(this.matrix[i][j] != INF) {edgeNum++;}}}}public void kruskal() {int index = 0; //表示最后結果數組的數目int[] ends = new int[edgeNum]; //用于保存"已有最小生成樹" 中的每個頂點在最小生成樹中的終點//創建結果數組, 保存最后的最小生成樹EData[] rets = new EData[edgeNum];//獲取圖中 所有的邊的集合EData[] edges = getEdges();System.out.println("圖的邊的集合=" + Arrays.toString(edges) + " 共"+ edges.length); //12//按照邊的權值大小進行排序(從小到大)sortEdges(edges);//遍歷edges 數組,將邊添加到最小生成樹中時,判斷是準備加入的邊否形成了回路,如果沒有,就加入 rets, 否則不能加入for(int i=0; i < edgeNum; i++) {//獲取到第i條邊的第一個頂點(起點)int p1 = getPosition(edges[i].start); //獲取到第i條邊的第2個頂點int p2 = getPosition(edges[i].end); //獲取p1這個頂點在已有最小生成樹中的終點int m = getEnd(ends, p1); //獲取p2這個頂點在已有最小生成樹中的終點int n = getEnd(ends, p2); //是否構成回路if(m != n) { //沒有構成回路ends[m] = n; // 設置m 在"已有最小生成樹"中的終點 rets[index++] = edges[i]; //有一條邊加入到rets數組}}//<E,F> <C,D> <D,E> <B,F> <E,G> <A,B>。//統計并打印 "最小生成樹", 輸出 retsSystem.out.println("最小生成樹為");for(int i = 0; i < index; i++) {System.out.println(rets[i]);}}/*** 功能:對邊進行排序處理, 冒泡排序* @param edges 邊的集合*/private void sortEdges(EData[] edges) {for(int i = 0; i < edges.length - 1; i++) {for(int j = 0; j < edges.length - 1 - i; j++) {if(edges[j].weight > edges[j+1].weight) {//交換EData tmp = edges[j];edges[j] = edges[j+1];edges[j+1] = tmp;}}}}/*** * @param ch 頂點的值,比如'A','B'* @return 返回ch頂點對應的下標,如果找不到,返回-1*/private int getPosition(char ch) {for(int i = 0; i < vertexs.length; i++) {if(vertexs[i] == ch) {//找到return i;}}//找不到,返回-1return -1;}/*** 功能: 獲取圖中邊,放到EData[] 數組中,后面我們需要遍歷該數組* 是通過matrix 鄰接矩陣來獲取* EData[] 形式 [['A','B', 12], ['B','F',7], .....]* @return*/private EData[] getEdges() {int index = 0;EData[] edges = new EData[edgeNum];for(int i = 0; i < vertexs.length; i++) {for(int j=i+1; j <vertexs.length; j++) {if(matrix[i][j] != INF) {edges[index++] = new EData(vertexs[i], vertexs[j], matrix[i][j]);}}}return edges;}/*** 功能: 獲取下標為i的頂點的終點(), 用于后面判斷兩個頂點的終點是否相同* @param ends : 數組就是記錄了各個頂點對應的終點是哪個,ends 數組是在遍歷過程中,逐步形成* @param i : 表示傳入的頂點對應的下標* @return 返回的就是 下標為i的這個頂點對應的終點的下標, 一會回頭還有來理解*/private int getEnd(int[] ends, int i) { while(ends[i] != 0) {i = ends[i];}return i;}}//創建一個類EData ,它的對象實例就表示一條邊 class EData {char start; //邊的一個點char end; //邊的另外一個點int weight; //邊的權值//構造器public EData(char start, char end, int weight) {this.start = start;this.end = end;this.weight = weight;}@Overridepublic String toString() {return "EData [<" + start + ", " + end + ">= " + weight + "]";} }

我們看一下輸出結果,最小生成樹為:

EData [<E, F>= 2] EData [<C, D>= 3] EData [<D, E>= 4] EData [<B, F>= 7] EData [<E, G>= 8] EData [<A, B>= 12]

即為問題2的求解。
問題1和問題2雖然問題一樣,但是兩種解法卻有不同的地方。使用普里姆算法時,我們可以指定從哪個點開始,但不管從哪個點開始,最終形成的路線是確定的;而克魯斯卡爾算法在解決問題的時候不用指定從哪個點開始,最終給出問題的解決路線。兩種方法雖然略有差別,但最后的性質是一樣的,讀者可以依據自己的需求和實際使用場景進行選擇。問題1和問題2只需要求出可以聯通的一個最短路線。如果是對于某一個點,要求這個點可以到達其他的任何一個點,求出滿足這樣條件下的最短路線又該如何求解呢?
迪杰斯特拉(Dijkstra)算法是典型最短路徑算法,用于計算一個結點到其他結點的最短路徑。 它的主要特點是以起始點為中心向外層層擴展(廣度優先搜索思想,不了解的同學可以自行百度,這里不做擴展),直到擴展到終點為止。

迪杰斯特拉(Dijkstra)算法

迪杰斯特拉(Dijkstra)算法過程是這樣的:
①設置出發頂點為v,頂點集合V{v1,v2,vi…},v到V中各頂點的距離構成距離集合Dis,Dis{d1,d2,di…},Dis集合記錄著v到圖中各頂點的距離(到自身可以看作0,v到vi距離對應為di)
②從Dis中選擇值最小的di并移出Dis集合,同時移出V集合中對應的頂點vi,此時的v到vi即為最短路徑
③更新Dis集合,更新規則為:比較v到V集合中頂點的距離值,與v通過vi到V集合中頂點的距離值,保留值較小的一個(同時也應該更新頂點的前驅節點為vi,表明是通過vi到達的)
④重復執行②③步驟,直到最短路徑頂點為目標頂點即可結束
看步驟也許還不明白,這里我們依然以圖解的方式進行說明。這里假設以問題3中的G作為出發頂點,這里需要定義三個集合的類:

//已訪問頂點集合 class VisitedVertex{ //記錄各個頂點是否訪問過 1表示訪問過,0未訪問,會動態更新 public int[] already_arr; //每個下標對應的值為前一個頂點下標, 會動態更新 public int[] pre_visited; //記錄出發頂點到其他所有頂點的距離,比如G為出發頂點,就會記錄G到其它頂點的距離,會動態更新,求的最短距離就會存放到dis public int[] dis; }

在還沒有進行訪問前,需要初始化各個變量:

以G為出發頂點訪問過一次后的,變量的值變為:

將以上圖轉化以下,得到如下形式:

第一行表示already_arr,當前數據表示G已經被訪問過,第二行表示pre_visited,當前數據表示A,B,E,F的前驅結點都是G,第三行表示 dis,當前數據表示A到G距離2,B到G距離3,E到G距離4,F到G距離6,N表示當前點與G沒有直接相連。G 訪問完后,得到最小的距離點A,接下來 A點作為新的訪問頂點(注意不是出發頂點)重復上述步驟。這樣通過遍歷所有的頂點,即可得到問題的求解。
有了解決問題的思路,接下來用代碼直接對問題進行求解。

/*** @Author likangmin* @create 2020/10/10 9:28*/ public class DijkstraAlgorithm {public static void main(String[] args) {char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };//鄰接矩陣int[][] matrix = new int[vertex.length][vertex.length];final int N = 65535;// 表示不可以連接matrix[0]=new int[]{N,5,7,N,N,N,2}; matrix[1]=new int[]{5,N,N,9,N,N,3}; matrix[2]=new int[]{7,N,N,N,8,N,N}; matrix[3]=new int[]{N,9,N,N,N,4,N}; matrix[4]=new int[]{N,N,8,N,N,5,4}; matrix[5]=new int[]{N,N,N,4,5,N,6}; matrix[6]=new int[]{2,3,N,N,4,6,N};//創建 Graph對象Graph graph = new Graph(vertex, matrix);//測試迪杰斯特拉算法graph.dsj(6);graph.showDijkstra();}}class Graph {private char[] vertex; // 頂點數組private int[][] matrix; // 鄰接矩陣private VisitedVertex vv; //已經訪問的頂點的集合// 構造器public Graph(char[] vertex, int[][] matrix) {this.vertex = vertex;this.matrix = matrix;}//顯示結果public void showDijkstra() {vv.show();}//迪杰斯特拉算法實現/*** * @param index 表示出發頂點對應的下標*/public void dsj(int index) {vv = new VisitedVertex(vertex.length, index);update(index);//更新index頂點到周圍頂點的距離和前驅頂點for(int j = 1; j <vertex.length; j++) {index = vv.updateArr();// 選擇并返回新的訪問頂點update(index); // 更新index頂點到周圍頂點的距離和前驅頂點} }//更新index下標頂點到周圍頂點的距離和周圍頂點的前驅頂點,private void update(int index) {int len = 0;//根據遍歷我們的鄰接矩陣的 matrix[index]行for(int j = 0; j < matrix[index].length; j++) {// len 含義是 : 出發頂點到index頂點的距離 + 從index頂點到j頂點的距離的和 len = vv.getDis(index) + matrix[index][j];// 如果j頂點沒有被訪問過,并且 len 小于出發頂點到j頂點的距離,就需要更新if(!vv.in(j) && len < vv.getDis(j)) {vv.updatePre(j, index); //更新j頂點的前驅為index頂點vv.updateDis(j, len); //更新出發頂點到j頂點的距離}}} }// 已訪問頂點集合 class VisitedVertex {// 記錄各個頂點是否訪問過 1表示訪問過,0未訪問,會動態更新public int[] already_arr;// 每個下標對應的值為前一個頂點下標, 會動態更新public int[] pre_visited;// 記錄出發頂點到其他所有頂點的距離,比如G為出發頂點,就會記錄G到其它頂點的距離,會動態更新,求的最短距離就會存放到dispublic int[] dis;//構造器/*** * @param length :表示頂點的個數 * @param index: 出發頂點對應的下標, 比如G頂點,下標就是6*/public VisitedVertex(int length, int index) {this.already_arr = new int[length];this.pre_visited = new int[length];this.dis = new int[length];//初始化 dis數組Arrays.fill(dis, 65535);this.already_arr[index] = 1; //設置出發頂點被訪問過this.dis[index] = 0;//設置出發頂點的訪問距離為0}/*** 功能: 判斷index頂點是否被訪問過* @param index* @return 如果訪問過,就返回true, 否則訪問false*/public boolean in(int index) {return already_arr[index] == 1;}/*** 功能: 更新出發頂點到index頂點的距離* @param index* @param len*/public void updateDis(int index, int len) {dis[index] = len;}/*** 功能: 更新pre這個頂點的前驅頂點為index頂點* @param pre* @param index*/public void updatePre(int pre, int index) {pre_visited[pre] = index;}/*** 功能:返回出發頂點到index頂點的距離* @param index*/public int getDis(int index) {return dis[index];}/*** 繼續選擇并返回新的訪問頂點, 比如這里的G 完后,就是 A點作為新的訪問頂點(注意不是出發頂點)* @return*/public int updateArr() {int min = 65535, index = 0;for(int i = 0; i < already_arr.length; i++) {if(already_arr[i] == 0 && dis[i] < min ) {min = dis[i];index = i;}}//更新 index 頂點被訪問過already_arr[index] = 1;return index;}//顯示最后的結果//即將三個數組的情況輸出public void show() {char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };int count = 0;for (int i : dis) {if (i != 65535) {System.out.print(vertex[count] + "("+i+") ");} else {System.out.println("N ");}count++;}System.out.println();} }

我們看一下輸出結果:

A(2) B(3) C(9) D(10) E(4) F(6) G(0)

問題3只是針對某一個點到其他點的最短距離的求解,如果我們不滿足于某一個點,而是希望所有的點都需要一條到其他點的最短路徑,可以怎么處理呢?當然我們可以利用以上方式,每個點都使用一遍方法進行求解,但這樣不方便,如果點的數量足夠多的話,操作起來就會很麻煩。那么有沒有什么方法可以一次性得到每一個點到其他點的最短路徑的集合呢?這里就需要使用弗洛伊德算法。

弗洛伊德算法

和Dijkstra算法一樣,弗洛伊德(Floyd)算法也是一種用于尋找給定的加權圖中頂點間最短路徑的算法。該算法名稱以創始人之一、1978年圖靈獎獲得者、斯坦福大學計算機科學系教授羅伯特·弗洛伊德命名。
弗洛伊德算法與迪杰斯特拉算法有什么區別呢?弗洛伊德算法(Floyd)計算圖中各個頂點之間的最短路徑,迪杰斯特拉算法用于計算圖中某一個頂點到其他頂點的最短路徑。迪杰斯特拉算法通過選定的被訪問頂點,求出從出發訪問頂點到其他頂點的最短路徑;弗洛伊德算法中每一個頂點都是出發訪問點,所以需要將每一個頂點看做被訪問頂點,求出從每一個頂點到其他頂點的最短路徑。弗洛伊德算法的大致步驟如下:
①設置頂點vi到頂點vk的最短路徑已知為Lik,頂點vk到vj的最短路徑已知為Lkj,頂點vi到vj的路徑為Lij,則vi到vj的最短路徑為:min((Lik+Lkj),Lij),vk的取值為圖中所有頂點,則可獲得vi到vj的最短路徑;
②至于vi到vk的最短路徑Lik或者vk到vj的最短路徑Lkj,是以同樣的方式獲得。
以問題4為例,初始各點之間的距離表如圖所示:

將A作為中間頂點,可以有三種路線:. C-A-G [9],C-A-B [12],G-A-B [7]。把A作為中間頂點的所有情況都進行遍歷, 具體轉換過程為:以A頂點作為中間頂點,B->A->C的距離由N->9,同理C到B;C->A->G的距離由N->12,同理G到C;G->B的距離為3小于7,所以不用變。更換中間頂點,循環執行操作,直到所有頂點都作為中間頂點更新后,計算結束就會得到更新距離表 和 前驅關系。第一次更新后距離表和前驅關系為:

有了解決問題的思路,接下來用代碼直接對問題進行求解。

/*** @Author likangmin* @create 2020/10/10 10:35*/ public class FloydAlgorithm {public static void main(String[] args) {char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };//創建鄰接矩陣int[][] matrix = new int[vertex.length][vertex.length];final int N = 65535;matrix[0] = new int[] { 0, 5, 7, N, N, N, 2 };matrix[1] = new int[] { 5, 0, N, 9, N, N, 3 };matrix[2] = new int[] { 7, N, 0, N, 8, N, N };matrix[3] = new int[] { N, 9, N, 0, N, 4, N };matrix[4] = new int[] { N, N, 8, N, 0, 5, 4 };matrix[5] = new int[] { N, N, N, 4, 5, 0, 6 };matrix[6] = new int[] { 2, 3, N, N, 4, 6, 0 };//創建 Graph 對象Graph graph = new Graph(vertex.length, matrix, vertex);//調用弗洛伊德算法graph.floyd();graph.show();}}// 創建圖 class Graph {private char[] vertex; // 存放頂點的數組private int[][] dis; // 保存,從各個頂點出發到其它頂點的距離,最后的結果,也是保留在該數組private int[][] pre;// 保存到達目標頂點的前驅頂點// 構造器/*** @param length 大小* @param matrix 鄰接矩陣 * @param vertex 頂點數組 */public Graph(int length, int[][] matrix, char[] vertex) {this.vertex = vertex;this.dis = matrix;this.pre = new int[length][length];// 對pre數組初始化, 注意存放的是前驅頂點的下標for (int i = 0; i < length; i++) {Arrays.fill(pre[i], i);}}// 顯示pre數組和dis數組public void show() {char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };for (int k = 0; k < dis.length; k++) {// 輸出dis數組的一行數據for (int i = 0; i < dis.length; i++) {System.out.print("("+vertex[k]+"->"+vertex[i]+":" + dis[k][i] + ") ");}}}//弗洛伊德算法, 比較容易理解,而且容易實現public void floyd() {int len = 0; //變量保存距離//對中間頂點遍歷, k 就是中間頂點的下標 [A, B, C, D, E, F, G] for(int k = 0; k < dis.length; k++) { // //從i頂點開始出發 [A, B, C, D, E, F, G]for(int i = 0; i < dis.length; i++) {//到達j頂點 // [A, B, C, D, E, F, G]for(int j = 0; j < dis.length; j++) {len = dis[i][k] + dis[k][j];// => 求出從i 頂點出發,經過 k中間頂點,到達 j 頂點距離if(len < dis[i][j]) {//如果len小于 dis[i][j]dis[i][j] = len;//更新距離pre[i][j] = pre[k][j];//更新前驅頂點}}}}} }

最后結果為:

A到其他點的最短路徑集合 (A->A:0) (A->B:5) (A->C:7) (A->D:12) (A->E:6) (A->F:8) (A->G:2) B到其他點的最短路徑集合 (B->A:5) (B->B:0) (B->C:12) (B->D:9) (B->E:7) (B->F:9) (B->G:3) C到其他點的最短路徑集合 (C->A:7) (C->B:12) (C->C:0) (C->D:17) (C->E:8) (C->F:13) (C->G:9) D到其他點的最短路徑集合 (D->A:12) (D->B:9) (D->C:17) (D->D:0) (D->E:9) (D->F:4) (D->G:10) E到其他點的最短路徑集合 (E->A:6) (E->B:7) (E->C:8) (E->D:9) (E->E:0) (E->F:5) (E->G:4) F到其他點的最短路徑集合 (F->A:8) (F->B:9) (F->C:13) (F->D:4) (F->E:5) (F->F:0) (F->G:6) G到其他點的最短路徑集合 (G->A:2) (G->B:3) (G->C:9) (G->D:10) (G->E:4) (G->F:6) (G->G:0)

至此,以上問題已全部得到解決,同時對于普里姆算法,克魯斯卡爾算法,弗洛伊德算法與迪杰斯特拉算法的原理和解法進行了詳細的分析,希望對大家有所幫助。

總結

以上是生活随笔為你收集整理的修路问题算法的总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。