1. 程式人生 > >牛牛取快遞(一)從dfs到dijkstra以及堆優化的dijkstra

牛牛取快遞(一)從dfs到dijkstra以及堆優化的dijkstra

並不是 rim 鄰接表 != 性能 AD [] 鄰接矩陣 不同

由於上一篇博客的第三題“牛牛取快遞”上次還未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