你必须会的--Dijkstra算法--单源最短路径问题
文章目錄
- 一.算法原理
- 1.基本原理
- 2.如何保存最短路徑?
- 二.算法實戰一
- 1.測試
- 2.結果
- 二.算法實戰2
- Input
- Output
- Sample Input
- Sample Output
一.算法原理
1.基本原理
Dijkstra算法是利用廣度優先搜索思想(BFS)+ 貪心策略的一種單源最短路徑算法,從Bellman Ford算法演化而來,但是核心的松弛(Relaxation)思想并沒有變:
RELAX(u, v, w)if v.d > u.d + w(u,v)v.d = u.d + w(u,v)v.π = u不同的Dijkstra算法加入了一個優先隊列用來獲取每次離起點最近的點。該算法以起點為中心,將相鄰邊加入到優先隊列中,記錄每一個點到源點的最近距離,并維護著離源點最近的那個結點。
每趟從此隊列中獲取到獲取其中距起點最近的點,在該點利用松弛操作(relaxation)更新該點的各相鄰節點到源點的最近距離(這里用到了貪心算法原理), relaxation操作其實就是如果相鄰結點經過該節點比不經過該節點時到源點的距離小,那么就縮短該相鄰結點到源結點的距離,存入到優先隊列中,如果已經加入到優先隊列中,則使用優先隊列的DECREASE-KEY操作來更新該結點到源點的距離,這個操作的時間復雜度一般都取決于優先隊列的底層實現。
偽代碼:
DIJKSTRA(G,w,s) //記錄已訪問結點 S = ? //初始化待訪問的優先隊列 Q = G.V while Q!= ?u = EXTRACT-MIN(Q)S = S ∪ {u};for each vertex v Q.Adj(u)RELAX(v)Dijkstra算法的時間復雜度根據優先隊列的實現不同而不同,如果優先隊列是用二叉堆來實現,那么EXTRACT-MIN操作與DECREASE-KEY操作的時間復雜度都為O(lgV),V是圖G頂點的集合,圖G中的所有邊都被訪問過,根據聚合分析,那么松弛操作總耗時為O(E*lgV),從優先隊列獲取最近點的總耗時為O(V*lgV),總的耗時為O(V*lgV + E*lgV),如何源點對于每一個其他結點都存在路徑,也就是說|E| > |V|,那么算法的復雜度可以表示為O(E*lgV)
考慮到經常調用DECREASE-KEY操作,所以如果采用斐波那契隊列來實現,DECREASE-KEY操作的時間復雜度為O(1),整個算法的時間復雜度可以塌縮到O(V*lgV+E)
2.如何保存最短路徑?
在pathTo集合中,設置此節點的上一節點。如果這點沒有被訪問過,就加入到優先隊列中,就這樣重復操作層層向外遍歷,最后就可以生成一個最短路徑樹,對于從該源點到某一點的最短路徑問題,只要看該點是否被訪問過,被訪問過的點說明存在最短路徑,回溯pathTo集合,如pathTo(A) = B, B是使A到源點距離最近的相鄰點(由貪心算法可知),pathTo(B) = C , C是使B到源點距離最近的相鄰點,反復操作,直到pathTo(X) = 源點。即可得到最短路徑
二.算法實戰一
package com.example.Dijkstra;import java.util.*;public class Dijkstra {// <1, [2, 3]>表示節點1的父節點是節點2,到源點距離為3private Map<Integer, int[]> disTo;/*** @param edges 一個節點到其他節點的距離* [[0, 1, 1], [1, 2, 2]] 表示點0到點1的距離為1,點1到點2的距離為2* @param n 所有節點個數 1<= n <= 1000* @param k 源節點 1< k <n*/public Map<Integer, int[]> Dijkstra(int[][] edges, int n, int k) {//通過edges數組生成有向圖,使用鄰接表存儲,用map+list模擬。//<0, <{1,2}, {2,3}>>表示節點0有1,2兩個相鄰節點,距離分別為2, 3Map<Integer, List<int[]>> graph = new HashMap<>();for (int[] edge : edges) {if (!graph.containsKey(edge[0]))graph.put(edge[0], new ArrayList<>());graph.get(edge[0]).add(new int[]{edge[1], edge[2]});}//初始化disTodisTo = new HashMap<>();for(int i = 0; i<n; i++)disTo.put(i, new int[]{0, 0});disTo.put(k, new int[]{-1, Integer.MAX_VALUE});//Dijkstra//省略了最小優先隊列,用isSeen數組輔助disTo來取代boolean[] isSeen = new boolean[n];while(true){//得到距離最近點int candiNode = -1;int candiDistance = Integer.MAX_VALUE;for(int i=0;i<n;i++){int[] temp = disTo.get(i);if(!isSeen[i] && temp[1]<candiDistance){candiNode = i;candiDistance = temp[1];}}if(candiNode == -1) break;isSeen[candiNode] = true;if(graph.containsKey(candiNode))for(int[] edge : graph.get(candiNode)){if(disTo.get(edge[0])[1]> candiDistance + edge[1]){//對該點相鄰點進行伸縮操作disTo.get(edge[0])[1] = candiDistance + edge[1];//更新父節點disTo.get(edge[0])[0] = candiNode;}}}return disTo;}/*** 輸出結果* @param disTo* @param end*/public void printPath(Map<Integer, int[]> disTo,int pathTo){int distance = disTo.get(pathTo)[1];List<Integer> path = new ArrayList<>();int temp = pathTo;path.add(temp);while (temp!=0 && temp!=-1){temp = disTo.get(temp)[0];path.add(temp);}System.out.print("從初始節點到節點"+end+"的最短距離為"+distance+"\n"+"最短路徑為:\n"+path.get(0));for(int i=1;i<path.size();i++){System.out.print("<--"+path.get(i));}}}這個實現我沒有用優先隊列,直接用的數組遍歷來獲取最近點,采取遍歷的方式獲取每次迭代的最近節點,會讓算法的復雜度達到O(∣N∣2)O(|N|^2)O(∣N∣2)
如果利用最小優先隊列這個堆式結構,算法的復雜度會縮小至O(∣E∣+NlogN)O(|E|+NlogN)O(∣E∣+NlogN)
1.測試
輸入:
| {{0, 1, 15},{0, 3, 5}, | 6 | 0 | 4 |
| {1, 5, 6}, {3, 5, 20}, | |||
| {1, 2, 15}, {3, 2, 30}, | |||
| {2, 4, 10}, {5, 4, 9}}; |
預計輸出:最短距離:30 路徑:0–>1–>5–>4
環境:windows10,java11
2.結果
二.算法實戰2
教授Leyni喜歡跟羅莉一起玩,這次,XianGe也來了,他們處在一個r * c的矩形區域中。Leyni處在矩形區域的入口外(左上角左側),XianGe處在矩形區域的出口外(右下角右側),羅莉處在矩形區域內。現在Leyni要喊話給XianGe,可是聲音在這個矩形區域內只能橫向或者垂直傳遞,而且,如果某個羅莉聽到了聲音(聽到聲音的羅莉不會阻礙聲音繼續傳播),Leyni可以命令她作為傳遞者向四周傳遞一次聲音,使得與她同一行同一列的羅莉都能聽到聲音(如下圖右側情況);當然,Leyni也可以讓聽到聲音的羅莉不傳遞(如下圖左側情況);Leyni處在入口(左上角)的左側,所以他只能向第一行傳遞聲音;XianGe處在出口(右下角)的右側,所以他只能接收到最后一行的聲音。
如下圖所示是一個3 * 3的矩形區域,在矩形區域內有兩個羅莉,Leyni命令她們傳遞聲音,并在入口處傳遞聲音給第一行的羅莉,她同時向四周傳遞聲音,第三行的羅莉收到聲音后繼續傳遞,使XianGe聽到了聲音。
現在給出一個矩形區域的大小和各個羅莉的位置情況,Leyni想知道他最少要命令幾個羅莉傳遞聲音才能讓XianGe聽到,請你幫助他!
Input
本題有多組測試數據,輸入的第一行是一個整數T代表著測試數據的數量,接下來是T組測試數據。
對于每組測試數據:
第1行 包含兩個以空格分隔的整數r和c (2?≤ r, c ≤?1000),代表著矩形區域的大小。
第2 … r + 1行 這r行包含了一個r行c列的矩陣,由".“和”#“組成,其中”#“表示羅莉,”."表示空。
Output
對于每組測試數據:
第1行 如果Leyni能成功傳遞聲音給XianGe,則輸出他最少需要命令多少個羅莉負責傳遞聲音;否則輸出-1。
Sample Input
13 3.#..#..#.Sample Output
2這一問題如果用一般的鄰接矩陣來表示,很難想到可以使用Dijkstra算法來解決,但是如果轉變想法,將無關的坐標忽視掉,只關注蘿莉所在的位置,通過蘿莉可以將沿著某一行傳播的聲音轉為沿著某一列傳播的聲音,把行和列都看作點,那么一個蘿莉相當于連接兩個點的邊,如此,最終的實現可以表示為
# include <iostream> # include <queue> # include <vector> using namespace std;struct edge {int from;int to;int w;int pre; }e[1000000]; int r, c, cnt; int head[1000000]; bool visited[2001]; int dist[2001];inline void add(int from, int to, int w) {e[cnt].from = from;e[cnt].to = to;e[cnt].w = w;e[cnt].pre = head[from];head[from] = cnt ++; }void SPFA() {fill(visited, visited + 2001, false);fill(dist, dist + r + c, 0x3f3f3f3f);dist[0] = 0;queue<int> q;q.push(0);while(!q.empty()){int n = q.front();q.pop();visited[n] = false;for(int v = head[n]; v!=-1; v = e[v].pre){int vw = e[v].w, vto = e[v].to;if (dist[vto] > dist[n] + vw){dist[vto] = dist[n] + vw;if (!visited[vto]){visited[vto] = true;q.push(vto);}}}}if (dist[r-1] == 0x3f3f3f3f){cout << -1 << endl;}else cout << dist[r-1] << endl;return; }int main(void) {int N; cin >> N;while(N --){cin >> r >> c;cnt = 0;fill(head, head + 1000000, -1);for(int i = 0; i<r; i++){for(int j = 1; j <= c; j++){char tmp; cin >> tmp;if (tmp == '#'){add(i, r + j, 1);add(r + j, i, 1);}}}SPFA();}return 0; }你可能還敢興趣的圖論算法(均附Java實現代碼):
-
算法實驗–主函數只有五行的Floyed的算法以及最短路徑輸出
-
你必須會的DFS的遞歸實現與堆棧實現
-
你了解歐拉回路嗎?
-
你必須會的啟發式搜索算法–A*算法
總結
以上是生活随笔為你收集整理的你必须会的--Dijkstra算法--单源最短路径问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法实验--主函数只有五行的Floyed
- 下一篇: 安装汇编环境,写一个最简单的窗口程序