根據這個貼: Please Share dijkstra's algorithm questions
Graph - Dijkstra's4926 views
- The Maze III
- The Maze II
- Network Delay Time
- Cheapest Flights Within K Stops
- Reachable Nodes In Subdivided Graph
- Path with Maximum Probability
- Find the City With the Smallest Number of Neighbors at a Threshold Distance
- Minimum Cost to Make at Least One Valid Path in a Grid
- Path With Minimum Effort
- Number of Restricted Paths From First to Last Node
Time complexity \(O(E \log V)\)
only use when the weight of edges is nonnegative
0. 變形題:邊權為1~5
先介紹一道變形題,感覺還挺有意思的,來自 題目求助|【求助】如何用線性時間復雜度解決單源最短路徑問題(詳情請看原文)
題目:
Develop a linear-time (i.e., O(m + n)-time) algorithm that solves the Single Source Shortest Path problem for graphs whose edge weights are positive integers bounded by 5. (Hint. You can either modify Dijstra’s algorithm or consider using Breath-First-Search.)
方法:貼下已經有大佬提供的思路了,我實現了一下
邊權受限的話可以用桶代替 \(dijkstra\) 算法里的堆。
開一個 \(5n+1\) 大小的數組作桶,一開始把起點放入 \(0\) 號桶,之后遍歷桶,碰到桶里有節點,就把節點拿出來更新與這個點相鄰的點的距離,更新了的節點再放入新的桶,由於 \(dijkstra\) 算法的距離不減,因此更新的節點一定不會放到已經遍歷過的桶里,復雜度 \(O(∣V∣+∣E∣)\),是線性的。
而且上述解法對於邊權很大的情況也能用,把桶的大小從原來的全是 \(1\) 調整為按指數增長即可,這個算法叫做基數堆,時間復雜度多一個 \(\log\)。
實現:
點擊查看代碼
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
struct Node
{
int v, d; //該節點的編號與距離
};
void dijkstra(vector<vector<Node>>& graph, int start, int end, vector<int>& dist) {
dist[start] = 0;
// priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
int n = graph.size();
vector<int> q[5*n+1]; // 長度為5*n+1的數組,用於代替優先隊列(相同最小距離的節點可能有多個)
q[0].push_back(start);
int i = 0;
while (i < 5*n+1) {
cout << "i = " << i << endl;
vector<int> us = q[i];i++;
if(us.size() == 0) continue;
// if(u == end) break;
for(int u : us) {
if(dist[u] < i-1) continue; // 之前已經求得了,但是可能出現在當前位置的列表中
dist[u] = i-1;
for(auto& node : graph[u]) {
int v = node.v, d = node.d;
if(dist[v] > dist[u] + d) {
dist[v] = dist[u] + d;
q[dist[v]].push_back(v);
}
}
for(int j = 0;j < 5*n+1;j++) {
if(q[j].size() > 0) {
for(int k = 0;k < q[j].size();k++) {
char ch = k == q[j].size()-1 ? ' ' : ',';
cout << q[j][k] << ch;
}
} else {
cout << 0 << " ";
}
}
cout << endl;
}
}
}
int main() {
int n, m;
cin >> n >> m;
vector<vector<Node>> graph(n+1);
for(int i = 0; i < m; i++) {
int u, v, d;
cin >> u >> v >> d;
graph[u].push_back({v, d});
graph[v].push_back({u, d});
}
vector<int> dist(n+1, INT_MAX);
dijkstra(graph, 1, n, dist);
for(int i = 1; i <= n; i++) {
cout << i << ": " << dist[i] << endl;
}
// cout << dist[n] << endl;
return 0;
}
注意由於同一位置(也就是同一距離)可以對應多個節點,應該用個vector來表示桶
Leetcode 743. 網絡延遲時間
題目:求有向圖中的單源最短路
方法:模板題
由於所有的權重值都為非負數,所以可以考慮用dijkstra算法求解。具體思路見代碼中注釋。
點擊查看代碼
class Solution {
public:
struct Node {
int id, w;
Node(int id, int w): id(id), w(w) {}
};
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
vector<vector<Node>> graph(n+1);
for(auto& item : times) {
graph[item[0]].push_back({item[1], item[2]});
// graph[item[1]].push_back({item[0], item[2]}); // 有向圖
}
vector<int> dist(n+1, INT_MAX);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
dist[k] = 0;
pq.push({0, k});
while(!pq.empty()) {
int u = pq.top().second, d = pq.top().first;
pq.pop();
if(dist[u] < d) continue; // 剪枝,已經求得更小的了
for(auto& node : graph[u]) {
int v = node.id, d = node.w;
if(dist[v] > dist[u] + d) {
dist[v] = dist[u] + d;
pq.push({dist[v], v});
}
}
}
int ans = 0;
for(int i = 1; i <= n; i++) {
ans = max(ans, dist[i]);
}
return ans == INT_MAX ? -1 : ans;
}
};
Leetcode 787. K 站中轉內最便宜的航班
題目:求src到dst的最短距離,但是不能用超過K個中轉點
方法:還是用最小距離做優先隊列,只是節點的狀態由單一的 \([id]\) 變成了 \([id, step]\)
ps:列表的寫法好爽(參考 Dijkstra’s Shortest Path Algorithm / LeetCode 787. Cheapest Flights Within K Stops)
點擊查看代碼
class Solution {
public:
typedef tuple<int, int, int> Node;
struct Edge {
int id, w;
Edge(int id, int w): id(id), w(w) {}
};
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
vector<vector<Edge>> graph(n+1);
for(auto& flight : flights) {
graph[flight[0]].push_back({flight[1], flight[2]});
// graph[item[1]].push_back({item[0], item[2]});
}
// vector<int> d(n+1, INT_MAX);
int d[n+1][k+2]; // 記錄當前最小值,用於剪枝
memset(d, 0x3f3f3f3f, sizeof(d));
priority_queue<Node, vector<Node>, greater<Node>> pq;
pq.push({0, src, 0});
while(!pq.empty()) {
auto [dist,u,step]=pq.top(); pq.pop();
bool flag = false; // 這個剪枝很重要
for(int i = 0;i <= step;i++) {
if(d[u][i] < dist) {flag = true; break;}
}
if(flag) continue;
d[u][step] = dist;
if(u == dst) return dist;
if(step >= k+1) continue;
for(auto& [v,w] : graph[u]) { // 都沒用到松弛,更像是BFS了
pq.push({dist+w, v, step+1});
}
}
return -1;
}
};
還有一個DP的思路,其實就是Bellman-Ford算法
點擊查看代碼
class Solution {
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
int dp[k+2][n];
const int INF = 0x3f3f3f3f;
memset(dp, INF, sizeof(dp));
for(int i = 0;i <= k+1;i++) dp[i][src] = 0;
for(int i = 1; i <= k+1; i++) {
for(auto& flight : flights) {
int u = flight[0], v = flight[1], w = flight[2];
dp[i][v] = min(dp[i][v], dp[i-1][u] + w);
}
}
return dp[k+1][dst] == INF ? -1 : dp[k+1][dst];
}
};
LCP 07. 傳遞信息
題意:求經過k輪到達某節點的方案數
方法:和上題類似,dp,不過不是求min,而是求和
點擊查看代碼
class Solution {
public:
int numWays(int n, vector<vector<int>>& relation, int k) {
int dp[k+1][n]; // dp[i][j] 表示經過i輪,到達j的方案數
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for(int i = 1;i <= k;i++) {
for(auto& rel : relation) {
int u = rel[0], v = rel[1];
dp[i][v] += dp[i-1][u];
}
}
return dp[k][n-1];
}
};
Leetcode 1334. 閾值距離內鄰居最少的城市
題意:找到一個城市,在其距離distanceThreshold
的城市數最少
方法:由於要求所有城市之間的距離,當然是Floyd啊
點擊查看代碼
class Solution {
public:
int findTheCity(int n, vector<vector<int>>& edges, int distanceThreshold) {
int d[n][n];
const int INF = 0x3f3f3f3f;
memset(d, INF, sizeof(d));
for(auto& edge : edges) {
int u = edge[0], v = edge[1], w = edge[2];
d[u][v] = d[v][u] = w;
}
for(int k = 0; k < n;k++) {
for(int i = 0;i < n;i++) {
for(int j = 0;j < n;j++) {
d[i][j] = min(d[i][j], d[i][k]+d[k][j]);
}
}
}
int mymin = n, id;
for(int i = 0;i < n;i++) {
int cnt = 0;
for(int j = 0;j < n;j++) {
if(i != j && d[i][j] <= distanceThreshold) cnt++;
}
if(cnt <= mymin) {
mymin = cnt;
id = i;
}
}
return id;
}
};
Leetcode1368. 使網格圖至少有一條有效路徑的最小代價
題目:每個格子上有個數字,代表可以可以走哪個方向,你也可以修改,求達到(n,m)的最少修改次數
方法:01BFS
問題可以抽象成構建一個圖,每一個方向都可以向其四個方向建邊,若需要改變方向則邊權是1,若不需要改變方向邊權是0。然后找(0, 0)點到(n-1, m-1)的最短路。
思路就是采用0-1BFS,如果前進到下一個位置不需要代價,則插入到隊首。如果前進到下一個位置需要代價,那么插入到隊尾。這樣就能保證整個隊列是有序的。這樣就不需要優先隊列了。
點擊查看代碼
class Solution {
public:
int minCost(vector<vector<int>>& grid) {
int n = grid.size(), m = grid[0].size(), tot = n*m;
const int dx[] = {0, 0, 1, -1}, dy[] = {1, -1, 0, 0};
vector<int>dist(tot, -1);
deque<pair<int, int>>dq;
dq.push_front({0, 0});
while(!dq.empty()) {
auto p = dq.front();dq.pop_front();
int id = p.second, d = p.first;
// cout << id << " " << d << endl;
dist[id] = d;
if(id == tot-1) return d;
int x = id/m, y = id%m;
for(int i = 0;i < 4;i++) {
int xx = x+dx[i], yy = y+dy[i], id = xx*m+yy;
// cout << "xx: " << xx << " " << yy << endl;
if(xx < 0 || xx >= n || yy < 0 || yy >= m) continue;
if(dist[id] != -1) continue; // 可代替vis
if(grid[x][y] == i+1) dq.push_front({d, id});
else dq.push_back({d+1, id});
}
}
return dist[tot-1];
}
};
參考鏈接: