牛牛取快遞(一)從dfs到dijkstra以及堆優化的dijkstra
由於上一篇博客的第三題“牛牛取快遞”上次還未ac,因此今天特意再來嘗試一下,上次使用暴力dfs搜索最短路徑超時的我,在大佬們解題思路的“熏陶”之下,終於在我的記憶深處找回了那被封印依舊的dijkstra算法。
dijkstra算法主要是選擇一個初始點s,每次選擇一個離s距離最小並未被選過的點t,並通過t作為跳板,更新其余點到s的距離(如果比原來還長就不更新啦)。然後不斷重復這個過程即可。(ps:不要和prime搞混啦,雖然他們主要思路很相似)。
然後,題目沒審仔細的我用鄰接矩陣寫出了以下代碼。
import java.util.Scanner; public class Main {static int [][] road; static int min; public static void main(String[] args) { Scanner in = new Scanner(System.in); while (in.hasNextInt()) {//註意while處理多個case int n = in.nextInt(); int M = in.nextInt(); int S = in.nextInt(); int T = in.nextInt(); road= new int[n+1][n+1]; for(int i = 0;i<M;i++){ int s = in.nextInt(); int t = in.nextInt(); int D = in.nextInt(); if(road[s][t]==0||road[s][t]>D) road[s][t] = D; } min = Integer.MAX_VALUE;int res = dijkstra(S, T) + dijkstra(T, S); System.out.println(res); } } static int dijkstra(int s,int t){ // 接受一個有向圖的權重矩陣,和一個起點編號start(從0編號,頂點存在數組中) // 返回一個int[] 數組,表示從start到它的最短路徑長度 int n = road.length; // 頂點個數 int[] shortPath = new int[n]; // 保存start到其他各點的最短路徑 int[] visited = new int[n]; // 標記當前該頂點的最短路徑是否已經求出,1表示已求出 // 初始化,第一個頂點已經求出 for(int i = 1;i<n;i++){ shortPath[i] = (road[s][i]==0?Integer.MAX_VALUE:road[s][i]); } shortPath[s] = 0; visited[s] = 1; for (int count = 2; count < n; count++) { // 要加入n-1個頂點 int k = -1; // 選出一個距離初始頂點start最近的未標記頂點 int dmin = Integer.MAX_VALUE; for (int i = 1; i < n; i++) { if (visited[i] == 0 && shortPath[i] < dmin) { dmin = shortPath[i]; k = i; } } if(k==-1){ return shortPath[t]; } // 將新選出的頂點標記為已求出最短路徑,且到start的最短路徑就是dmin shortPath[k] = dmin; visited[k] = 1; // 以k為中間點,修正從start到未訪問各點的距離 for (int i = 1; i < n; i++) { if (visited[i] == 0 && road[k][i]!=0 &&shortPath[k] + road[k][i] < shortPath[i]){//使用k作為跳板,更新未被訪問過並且通過k之後離start更近的點的距離 shortPath[i] = shortPath[k] + road[k][i]; } } if(visited[t] == 1){ return shortPath[t]; } } return shortPath[t]; } }
因為題目中這個條件,用鄰接矩陣來做,測試數據跑到80%報出了堆溢出,那麽我的第一反應自然是把鄰接矩陣改成鄰接表就成了。
既然有了思路,改起來也沒有太大的問題,代碼如下,這下總能ac了吧,我邊祈禱邊按下了“提交”按鈕。
static LinkedList<Integer[]>[] road;//鄰接表 static int dijkstra(int s,int t){ // 接受一個有向圖的權重矩陣,和一個起點編號start(從0編號,頂點存在數組中) // 返回一個int[] 數組,表示從start到它的最短路徑長度 int n = road.length; // 頂點個數 int[] shortPath = new int[n]; // 保存start到其他各點的最短路徑 int[] visited = new int[n]; // 標記當前該頂點的最短路徑是否已經求出,1表示已求出 // 初始化,第一個頂點已經求出 for(int i = 1;i<n;i++){ shortPath[i] = Integer.MAX_VALUE; } for(Integer[] kv:road[s]){ if(shortPath[kv[0]]>kv[1]){ shortPath[kv[0]] = kv[1]; } } visited[s] = 1; for (int count = 2; count < n; count++) { // 要加入n-1個頂點 int k = -1; // 選出一個距離初始頂點start最近的未標記頂點 int dmin = Integer.MAX_VALUE/2; for (int i = 1; i < n; i++) { if (visited[i] == 0 && shortPath[i] < dmin) { dmin = shortPath[i]; k = i; } } if(k==-1){ return shortPath[t]; } // 將新選出的頂點標記為已求出最短路徑,且到start的最短路徑就是dmin shortPath[k] = dmin; visited[k] = 1; int fromToDis; // 以k為中間點,修正從start到未訪問各點的距離 for (int i = 1; i < n; i++) { if(visited[i] == 0){ fromToDis = find(k, i); if(fromToDis + shortPath[k]< shortPath[i]){ shortPath[i] = fromToDis+shortPath[k]; } } } if(visited[t] == 1){ return shortPath[t]; } } return shortPath[t]; } //從鄰接表查找from到to有路的距離 static int find(int from,int to){ int res = Integer.MAX_VALUE/2; for (Integer[] kv: road[from]) { if(kv[0] == to && kv[1]<res){ res = kv[1]; } } return res; }
結果很快就出來了,可是,嗯?還是80%的通過率?不過這次是超時了,鄰接矩陣可以一次查出從a節點到b節點是否有邊,距離是多少,而鄰接表則需要查詢一個鏈表,效率自然是變低了,那麽是否有優化的辦法呢?
經過查閱資料後,我發現了dijstra堆優化的一種算法,通過一個優先隊列記錄下一個點以及離原點的距離,每次取隊列的頭判斷該節點是否加入過,若沒加入過,就用該節點更新其余點的距離,感覺似乎是比普通的方法快一些,堆的插入以及取頭都是o(logn)的,很快,堆優化的dijstra也碼出來了。
static int dijkstra(int s, int t) { // 堆優化 int n = road.length; int[] visited = new int[n]; int[] dis = new int[n]; int to, d; Integer[] toDis; PriorityQueue<Integer[]> queue = new PriorityQueue<>((Integer[] o1,Integer[] o2)->o1[1]-o2[1]); for (int i = 1; i < n; i++) { dis[i] = Integer.MAX_VALUE / 2; visited[i] = 0; } dis[s] = 0;// 到自己的距離為0 int newdis; queue.offer(new Integer[] { s, 0 }); while (!queue.isEmpty()) { toDis = queue.element(); queue.poll(); to = toDis[0]; d = toDis[1]; if (visited[to] == 1) { continue; } visited[to] = 1; for (int i = 1; i < n; i++) { newdis = dis[to] + find(to, i); if (visited[i] == 0 && dis[i] > newdis) {// i沒去過,並且dis[to]+find(to,i)直接到i的距離大於到to再到i的距離 dis[i] = newdis; queue.offer(new Integer[] { i, newdis }); } } if (visited[t] == 1) { return dis[t]; } } return dis[t]; }
嗯,結果也不算出乎意料,同樣只有80%的通過率,最主要的問題應該在於dijstra每次更新距離時需要查找是否存在a到b的邊,以及其最短距離,因此每次查詢的效率較低,而堆優化的性能在測試用例中效果並不是特別明顯吧。
從鄰接矩陣到鄰接表再到堆優化的dijstra,經過三次編碼之後,雖然通過率沒有提高,但是能dijstra的思路基本是不會再忘了的(畢竟前後三次從滿懷期待的編碼到僅有80%通過而產生的失落),尤其是prime和dijstra的不同之處(其實一開始我以為是同一種思路嘿嘿),下次應該會學習spfa算法再嘗試本題,因為看到不少c++通過的都是用spfa算法做的。這也算自己日後需要填的一個坑吧。
牛牛取快遞(一)從dfs到dijkstra以及堆優化的dijkstra