[LeetCode] 743. Network Delay Time 網絡延遲時間

 

There are N network nodes, labelled 1 to N.html

Given times, a list of travel times as directededges times[i] = (u, v, w), where u is the source node, v is the target node, and w is the time it takes for a signal to travel from source to target.node

Now, we send a signal from a certain node K. How long will it take for all nodes to receive the signal? If it is impossible, return -1.git

 

Example 1:github

Input: times = [[2,1,1],[2,3,1],[3,4,1]], N = 4, K = 2 Output: 2 

 

Note:算法

  1. N will be in the range [1, 100].
  2. K will be in the range [1, N].
  3. The length of times will be in the range [1, 6000].
  4. All edges times[i] = (u, v, w) will have 1 <= u, v <= N and 0 <= w <= 100.

 

這道題給了咱們一些有向邊,又給了一個結點K,問至少須要多少時間才能從K到達任何一個結點。這其實是一個有向圖求最短路徑的問題,求出K點到每個點到最短路徑,而後取其中最大的一個就是須要的時間了。能夠想成從結點K開始有水流向周圍擴散,當水流到達最遠的一個結點時,那麼其餘全部的結點必定已經流過水了。最短路徑的經常使用解法有迪傑斯特拉算法 Dijkstra Algorithm, 弗洛伊德算法 Floyd-Warshall Algorithm, 和貝爾曼福特算法 Bellman-Ford Algorithm,其中,Floyd 算法是多源最短路徑,即求任意點到任意點到最短路徑,而 Dijkstra 算法和 Bellman-Ford 算法是單源最短路徑,即單個點到任意點到最短路徑。這裏由於起點只有一個K,因此使用單源最短路徑就好了。這三種算法還有一點不一樣,就是 Dijkstra 算法處理有向權重圖時,權重必須爲正,而另外兩種能夠處理負權重有向圖,可是不能出現負環,所謂負環,就是權重均爲負的環。爲啥呢,這裏要先引入鬆弛操做 Relaxtion,這是這三個算法的核心思想,當有對邊 (u, v) 是結點u到結點v,若是 dist(v) > dist(u) + w(u, v),那麼 dist(v) 就能夠被更新,這是全部這些的算法的核心操做。Dijkstra 算法是以起點爲中心,向外層層擴展,直到擴展到終點爲止。根據這特性,用 BFS 來實現時再好不過了,注意 while 循環裏的第一層 for 循環,這保證了每一層的結點先被處理完,纔會進入進入下一層,這種特性在用 BFS 遍歷迷宮統計步數的時候很重要。對於每個結點,都跟其周圍的結點進行 Relaxtion 操做,從而更新周圍結點的距離值。爲了防止重複比較,須要使用 visited 數組來記錄已訪問過的結點,最後在全部的最小路徑中選最大的返回,注意,若是結果 res 爲 INT_MAX,說明有些結點是沒法到達的,返回 -1。普通的實現方法的時間複雜度爲 O(V2),基於優先隊列的實現方法的時間複雜度爲 O(E + VlogV),其中V和E分別爲結點和邊的個數,這裏多說一句,Dijkstra 算法這種類貪心算法的機制,使得其沒法處理有負權重的最短距離,還好這道題的權重都是正數,參見代碼以下:數組

 

解法一:優化

class Solution {
public:
    int networkDelayTime(vector<vector<int>>& times, int N, int K) {
        int res = 0;
        vector<vector<int>> edges(101, vector<int>(101, -1));
        queue<int> q{{K}};
        vector<int> dist(N + 1, INT_MAX);
        dist[K] = 0;
        for (auto e : times) edges[e[0]][e[1]] = e[2];
        while (!q.empty()) {
            unordered_set<int> visited;
            for (int i = q.size(); i > 0; --i) {
                int u = q.front(); q.pop();
                for (int v = 1; v <= 100; ++v) {
                    if (edges[u][v] != -1 && dist[u] + edges[u][v] < dist[v]) {
                        if (!visited.count(v)) {
                            visited.insert(v);
                            q.push(v);
                        }
                        dist[v] = dist[u] + edges[u][v];
                    }
                }
            }
        }
        for (int i = 1; i <= N; ++i) {
            res = max(res, dist[i]);
        }
        return res == INT_MAX ? -1 : res;
    }
};

 

下面來看基於 Bellman-Ford 算法的解法,時間複雜度是 O(VE),V和E分別是結點和邊的個數。這種算法是基於 DP 來求全局最優解,原理是對圖進行 V - 1 次鬆弛操做,這裏的V是全部結點的個數(爲啥是 V-1 次呢,由於最短路徑最多隻有 V-1 條邊,因此只需循環 V-1 次),在重複計算中,使得每一個結點的距離被不停的更新,直到得到最小的距離,這種設計方法融合了暴力搜索之美,寫法簡潔又不失優雅。以前提到了,Bellman-Ford 算法能夠處理負權重的狀況,可是不能有負環存在,通常形式的寫法中最後一部分是檢測負環的,若是存在負環則報錯。不能有負環緣由是,每轉一圈,權重和都在減少,能夠無限轉,那麼最後的最小距離都是負無窮,無心義了。沒有負環的話,V-1 次循環後各點的最小距離應該已經收斂了,因此在檢測負環時,就再循環一次,若是最小距離還能更新的話,就說明存在負環。這道題因爲不存在負權重,因此就不檢測了,參見代碼以下:spa

 

解法二:設計

class Solution {
public:
    int networkDelayTime(vector<vector<int>>& times, int N, int K) {
        int res = 0;
        vector<int> dist(N + 1, INT_MAX);
        dist[K] = 0;
        for (int i = 1; i < N; ++i) {
            for (auto e : times) {
                int u = e[0], v = e[1], w = e[2];
                if (dist[u] != INT_MAX && dist[v] > dist[u] + w) {
                    dist[v] = dist[u] + w;
                }
            }
        }
        for (int i = 1; i <= N; ++i) {
            res = max(res, dist[i]);
        }
        return res == INT_MAX ? -1 : res;
    }
};

 

下面這種解法是 Bellman Ford 解法的優化版本,由熱心網友旅葉提供。之因此能提升運行速度,是由於使用了隊列 queue,這樣對於每一個結點,不用都鬆弛全部的邊,由於大多數的鬆弛計算都是無用功。優化的方法是,若某個點的 dist 值不變,不去更新它,只有當某個點的 dist 值被更新了,纔將其加入 queue,並去更新跟其相連的點,同時還須要加入 HashSet,以避免被反覆錯誤更新,這樣的時間複雜度能夠優化到 O(E+V)。Java 版的代碼在評論區三樓,旅葉聲稱能夠 beat 百分之九十多,但博主改寫的這個 C++ 版本的卻只能 beat 百分之二十多,hmm,因缺斯汀。不過仍是要比上面的解法二快不少,博主又仔細看了看,發現很像解法一和解法二的混合版本哈,參見代碼以下:code

 

解法三:

class Solution {
public:
    int networkDelayTime(vector<vector<int>>& times, int N, int K) {
        int res = 0;
        unordered_map<int, vector<pair<int, int>>> edges;
        vector<int> dist(N + 1, INT_MAX);
        queue<int> q{{K}};
        dist[K] = 0;
        for (auto e : times) edges[e[0]].push_back({e[1], e[2]});
        while (!q.empty()) {
            int u = q.front(); q.pop();
            unordered_set<int> visited;
            for (auto e : edges[u]) {
                int v = e.first, w = e.second;
                if (dist[u] != INT_MAX && dist[u] + w < dist[v]) {
                    dist[v] = dist[u] + w;
                    if (visited.count(v)) continue;
                    visited.insert(v);
                    q.push(v);
                }
            }
        }
        for (int i = 1; i <= N; ++i) {
            res = max(res, dist[i]);
        }
        return res == INT_MAX ? -1 : res;
    }
};

 

討論:最後再來講說這個 Floyd 算法,這也是一種經典的動態規劃算法,目的是要找結點i到結點j的最短路徑。而結點i到結點j的走法就兩種可能,一種是直接從結點i到結點j,另外一種是通過若干個結點k到達結點j。因此對於每一箇中間結點k,檢查 dist(i, k) + dist(k, j) < dist(i, j) 是否成立,成立的話就鬆弛它,這樣遍歷完全部的結點k,dist(i, j) 中就是結點i到結點j的最短距離了。時間複雜度是 O(V3),到處透露着暴力美學。除了這三種算法外,還有一些很相似的優化算法,好比 Bellman-Ford 的優化算法- SPFA 算法,還有融合了 Bellman-Ford 和 Dijkstra 算法的高效的多源最短路徑算法- Johnson 算法,這裏就不過多贅述了,感興趣的童鞋可盡情的 Google 之~

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/743

 

參考資料:

https://leetcode.com/problems/network-delay-time/description/

https://leetcode.com/problems/network-delay-time/discuss/109982/C++-Bellman-Ford

https://leetcode.com/problems/network-delay-time/discuss/109968/Simple-JAVA-Djikstra's-(PriorityQueue-optimized)-Solution-with-explanation

 

LeetCode All in One 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索