Dijkstra 算法
普通堆实现的 Dijkstra 算法
- 时间复杂度为
O(m * logm)
,m 为边数
-
distance[i]
表示从源点到 i 点的最短距离,visited[i]
表示 i 节点是否从小根堆弹出过 -
准备好小根堆,
小根堆
存放记录:(x 点,源点到 x 的距离)
,小根堆根据距离排序 -
令 distance[源点] = 0,(源点,0)入堆
-
从小根堆弹出(u 点,源点到 u 的距离)
a. 如果 visited[u] == true,啥也不做,重复步骤 4
b. 如果 visited[u] == false,令 visited[u] = true,u 就算弹出过了,
然后考察 u 的每一条边,假设某条边去往 v 点,边权为 w
1)如果 visited[v] == false 并且 distance[u] + w < distance[v],
令 distace[v] = distance[u] + w,把(v,distance[u] + w)加入小根堆
2)处理完 u 的每条边后重复步骤 4
-
小根堆为空,过程结束。
P4779 【模板】单源最短路径(标准版)
#include <iostream> #include <vector> #include <algorithm> #include <queue> using namespace std; struct cmp { bool operator()(pair<int, int> &p1, pair<int, int> &p2) { return p1.second > p2.second; } }; int main() { int n, m, s; cin >> n >> m >> s; vector<vector<pair<int, int>>> graph(n + 1); // 建图 for (int i = 0, u, v, w; i < m; ++i) { cin >> u >> v >> w; graph[u].emplace_back(make_pair(v, w)); } // 标记是否从堆中弹出过 vector<bool> visited(n + 1, false); priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> heap; vector<int> distances(n + 1, 0x7fffffff); // 源点入堆 heap.emplace(make_pair(s, 0)); distances[s] = 0; while (!heap.empty()) { auto top = heap.top(); heap.pop(); int u = top.first; if (visited[u]) continue; visited[u] = true; for (const auto &item: graph[u]) { int v = item.first; int w = item.second; if (visited[v]) continue; if (distances[v] > distances[u] + w) { distances[v] = distances[u] + w; heap.emplace(make_pair(v, distances[v])); } } } for (int i = 1; i <= n; ++i) cout << distances[i] << " "; }
743. 网络延迟时间
#include <iostream> #include <vector> #include <unordered_set> #include <algorithm> #include <queue> using namespace std; class Solution { public: struct cmp { bool operator()(pair<int, int> &p1, pair<int, int> &p2) { return p1.second > p2.second; } }; // 节点编号从 1 开始 int networkDelayTime(vector<vector<int>> ×, int n, int k) { // 邻接表建图 vector<vector<pair<int, int>>> graph(n + 1); for (const auto &item: times) graph[item[0]].emplace_back(make_pair(item[1], item[2])); // 记录从源点到每个点的距离 vector<int> distance(n + 1, INT_MAX); // 记录是否从堆中弹出过 vector<int> visited(n + 1, false); // 小根堆:(u,源点到 u 的距离) priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> heap; // k 到自身距离为 0 heap.emplace(make_pair(k, 0)); distance[k] = 0; while (!heap.empty()) { auto p = heap.top(); heap.pop(); int u = p.first; // 已经确定距离的不再处理 if (visited[u] == true) continue; visited[u] = true; for (const auto &item: graph[u]) { int v = item.first; int w = item.second; // 已经确定距离的不再处理 if (visited[v] == true) continue; // 尝试源点在经过 u 的情况下到达 v ,是否可以使到达 v 的距离缩短 if (distance[v] > distance[u] + w) { distance[v] = distance[u] + w; // 这条边入堆 heap.emplace(v, distance[v]); } } } int res = 0; for (int i = 1; i <= n; ++i) { // 有的点到达不了 if (distance[i] == INT_MAX) return -1; res = max(res, distance[i]); } return res; } };
反向索引堆实现 Dijkstra 算法
-
时间复杂度为
O(m * logn)
,m 为边数,n 为节点数 -
堆中存放
(u,d)
,根据 d 排序。普通堆无法找到指定 u 在实现堆的底层数组中的具体位置。反向索引堆的实现需要建立一张反向索引表
,记录 u 在数组中的位置,这样就能直接在堆中修改 u 的 d 信息,然后调整堆,同时也要调整反向索引表。
-
把(源点,0)加入反向索引堆,过程开始
-
反向索引堆弹出(u,源点到 u 的距离),考察 u 的每条边,假设某条边去往 v,边权为 w
a. 如果 v 没有进入过反向索引堆,新增记录(v,源点到 u 的距离 + w)
b. 如果 v 曾经从反向索引堆弹出过,忽略
c. 如果 v 在反向索引堆里,看看源点到 v 的距离能否变小,能就调整堆,否则跳过
d. 处理完 u 的每条边,重复步骤 2
-
反向索引堆为空过程结束。distance 里记录了源点到每个点的最短距离。
P4779 【模板】单源最短路径(标准版)
#include <iostream> #include <vector> #include <algorithm> using namespace std; int n, m, s; // 链式前向星 vector<int> head; vector<int> nxt; vector<int> to; vector<int> weight; int cnt; void initGraph() { // 点的编号从 1 开始 // resize 只会将新增的位置设置为新的值 head.resize(n + 1, 0); fill(head.begin(), head.end(), 0); nxt.resize(m + 1); to.resize(m + 1); weight.resize(m + 1); // 边的编号从 1 开始 cnt = 1; } void addEdge(int u, int v, int w) { nxt[cnt] = head[u]; to[cnt] = v; weight[cnt] = w; head[u] = cnt; cnt++; } vector<int> heap; int heapSize; // 反向索引表 // where[v] = -2,表示v这个节点,已经弹出过了 // where[v] = -1,表示v这个节点,从来没有进入过堆 // where[v] = i(>=0),表示 v 这个节点,在堆上的 i 位置 // 所有 where 的 set 操作都包含在堆的操作中 vector<int> where; // 记录源点到目标点的最短距离 // 所有 distances 的 set 操作与堆的操作分离 vector<int> distances; void initHeap() { heap.resize(n); heapSize = 0; // 初始状态都没进过堆 where.resize(n + 1, -1); fill(where.begin(), where.end(), -1); // 初始最短距离都是无穷大 distances.resize(n + 1, 0x7fffffff); fill(distances.begin(), distances.end(), 0x7fffffff); } // 自顶向下调整堆 void adjustHeapTopDown(int curIndex) { auto temp = heap[curIndex]; int leftChildIndex = 2 * curIndex + 1; while (leftChildIndex <= (heapSize - 1)) { if ((leftChildIndex < heapSize - 1) && distances[heap[leftChildIndex]] > distances[heap[leftChildIndex + 1]]) leftChildIndex++; if (distances[heap[leftChildIndex]] >= distances[temp]) break; heap[curIndex] = heap[leftChildIndex]; // 修改反向索引表 where[heap[leftChildIndex]] = curIndex; curIndex = leftChildIndex; leftChildIndex = 2 * curIndex + 1; } heap[curIndex] = temp; // 修改反向索引表 where[temp] = curIndex; } // 自下而上调整堆 void adjustHeapBottomUP(int curIndex) { auto temp = heap[curIndex]; int parentIndex = (curIndex - 1) / 2; while (parentIndex >= 0) { if (distances[heap[parentIndex]] <= distances[temp]) break; heap[curIndex] = heap[parentIndex]; // 修改反向索引表 where[heap[parentIndex]] = curIndex; curIndex = parentIndex; if (curIndex == 0) break; parentIndex = (curIndex - 1) / 2; } heap[curIndex] = temp; // 修改反向索引表 where[temp] = curIndex; } void addToHeap(int v) { heap[heapSize] = v; heapSize++; adjustHeapBottomUP(heapSize - 1); } int getTop() { int res = heap[0]; heap[0] = heap[heapSize - 1]; heapSize--; adjustHeapTopDown(0); where[res] = -2; return res; } void addOrUpdateOrIgnore(int v, int d) { if (where[v] == -2) { return; } else if (where[v] == -1) { distances[v] = d; // v 不在堆中,新增记录 addToHeap(v); } else if (where[v] >= 0) { // 经过 u 点到达 v 点能使源点到达 v 点距离更短,就更新 if (distances[v] > d) { distances[v] = d; // 修改堆中原有的那条,再向上调整(距离变短,只需要往上调整) adjustHeapBottomUP(where[v]); } } } int main() { cin >> n >> m >> s; // 建图 initGraph(); for (int i = 0, u, v, w; i < m; ++i) { cin >> u >> v >> w; addEdge(u, v, w); } initHeap(); addOrUpdateOrIgnore(s, 0); while (heapSize > 0) { int u = getTop(); for (int edge = head[u]; edge != 0; edge = nxt[edge]) addOrUpdateOrIgnore(to[edge], distances[u] + weight[edge]); } for (int i = 1; i <= n; ++i) cout << distances[i] << " "; }
743. 网络延迟时间
#include <iostream> #include <vector> #include <algorithm> using namespace std; int n, m, s; // 链式前向星 vector<int> head; vector<int> nxt; vector<int> to; vector<int> weight; int cnt; void initGraph() { // 点的编号从 1 开始 // resize 只会将新增的位置设置为新的值 head.resize(n + 1, 0); fill(head.begin(), head.end(), 0); nxt.resize(m + 1); to.resize(m + 1); weight.resize(m + 1); // 边的编号从 1 开始 cnt = 1; } void addEdge(int u, int v, int w) { nxt[cnt] = head[u]; to[cnt] = v; weight[cnt] = w; head[u] = cnt; cnt++; } vector<int> heap; int heapSize; // 反向索引表 // where[v] = -2,表示v这个节点,已经弹出过了 // where[v] = -1,表示v这个节点,从来没有进入过堆 // where[v] = i(>=0),表示 v 这个节点,在堆上的 i 位置 // 所有 where 的 set 操作都包含在堆的操作中 vector<int> where; // 记录源点到目标点的最短距离 // 所有 distances 的 set 操作与堆的操作分离 vector<int> distances; void initHeap() { heap.resize(n); heapSize = 0; // 初始状态都没进过堆 where.resize(n + 1, -1); fill(where.begin(), where.end(), -1); // 初始最短距离都是无穷大 distances.resize(n + 1, 0x7fffffff); fill(distances.begin(), distances.end(), 0x7fffffff); } // 自顶向下调整堆 void adjustHeapTopDown(int curIndex) { auto temp = heap[curIndex]; int leftChildIndex = 2 * curIndex + 1; while (leftChildIndex <= (heapSize - 1)) { if ((leftChildIndex < heapSize - 1) && distances[heap[leftChildIndex]] > distances[heap[leftChildIndex + 1]]) leftChildIndex++; if (distances[heap[leftChildIndex]] >= distances[temp]) break; heap[curIndex] = heap[leftChildIndex]; // 修改反向索引表 where[heap[leftChildIndex]] = curIndex; curIndex = leftChildIndex; leftChildIndex = 2 * curIndex + 1; } heap[curIndex] = temp; // 修改反向索引表 where[temp] = curIndex; } // 自下而上调整堆 void adjustHeapBottomUP(int curIndex) { auto temp = heap[curIndex]; int parentIndex = (curIndex - 1) / 2; while (parentIndex >= 0) { if (distances[heap[parentIndex]] <= distances[temp]) break; heap[curIndex] = heap[parentIndex]; // 修改反向索引表 where[heap[parentIndex]] = curIndex; curIndex = parentIndex; if (curIndex == 0) break; parentIndex = (curIndex - 1) / 2; } heap[curIndex] = temp; // 修改反向索引表 where[temp] = curIndex; } void addToHeap(int v) { heap[heapSize] = v; heapSize++; adjustHeapBottomUP(heapSize - 1); } int getTop() { int res = heap[0]; heap[0] = heap[heapSize - 1]; heapSize--; adjustHeapTopDown(0); where[res] = -2; return res; } void addOrUpdateOrIgnore(int v, int d) { if (where[v] == -2) { return; } else if (where[v] == -1) { distances[v] = d; // v 不在堆中,新增记录 addToHeap(v); } else if (where[v] >= 0) { // 经过 u 点到达 v 点能使源点到达 v 点距离更短,就更新 if (distances[v] > d) { distances[v] = d; // 修改堆中原有的那条,再向上调整(距离变短,只需要往上调整) adjustHeapBottomUP(where[v]); } } } class Solution { public: int networkDelayTime(vector<vector<int>> ×, int N, int k) { n = N; m = times.size(); s = k; // 建图 initGraph(); for (const auto &item: times) addEdge(item[0], item[1], item[2]); initHeap(); addOrUpdateOrIgnore(s, 0); while (heapSize > 0) { int u = getTop(); for (int edge = head[u]; edge != 0; edge = nxt[edge]) addOrUpdateOrIgnore(to[edge], distances[u] + weight[edge]); } int res = 0; for (int i = 1; i <= n; ++i) { if (distances[i] == 0x7fffffff) return -1; res = max(res, distances[i]); } return res; } };
其他练习题
1631. 最小体力消耗路径
#include <iostream> #include <vector> #include <algorithm> #include <queue> using namespace std; class Solution { public: struct cmp { bool operator()(vector<int> &v1, vector<int> &v2) { return v1[2] > v2[2]; } }; vector<int> move{-1, 0, 1, 0, -1}; int rows, columns; bool isCoordinateLegal(int row, int column) { return row >= 0 && row < rows && column >= 0 && column < columns; } int minimumEffortPath(vector<vector<int>> &heights) { rows = heights.size(); columns = heights[0].size(); // 源点到各个点的体力值,记录的是这条路上相邻格子高度差绝对值的最大值 vector<vector<int>> distance(rows, vector<int>(columns, INT_MAX)); // 标记是否从堆中弹出过 vector<vector<bool>> visited(rows, vector<bool>(columns, false)); // (x, y, 体力值),根据体力值排序 priority_queue<vector<int>, vector<vector<int>>, cmp> heap; // 源点入堆 heap.emplace(vector<int>{0, 0, 0}); distance[0][0] = 0; while (!heap.empty()) { auto t = heap.top(); heap.pop(); int x = t[0]; int y = t[1]; int cost = t[2]; if (x == rows - 1 && y == columns - 1) return cost; if (visited[x][y]) continue; visited[x][y] = true; for (int i = 0; i < 4; ++i) { int nx = x + move[i]; int ny = y + move[i + 1]; // 非法或者已经从堆弹出过,就忽略 if (!isCoordinateLegal(nx, ny) || visited[nx][ny]) continue; // 相邻高度差的绝对值 int gap = abs(heights[nx][ny] - heights[x][y]); // 这条路径到点 [nx, ny] 的体力值 // 以前的代价是路径长度,这题的代价是这条路上相邻格子高度差绝对值的最大值 int newCost = max(cost, gap); // 代价可以变小,就更新 if (newCost < distance[nx][ny]) { distance[nx][ny] = newCost; heap.emplace(vector<int>{nx, ny, newCost}); } } } return -1; } };
778. 水位上升的泳池中游泳
#include <iostream> #include <vector> #include <algorithm> #include <queue> using namespace std; class Solution { public: struct cmp { bool operator()(vector<int> &v1, vector<int> &v2) { return v1[2] > v2[2]; } }; vector<int> move{-1, 0, 1, 0, -1}; int rows, columns; bool isCoordinateLegal(int row, int column) { return row >= 0 && row < rows && column >= 0 && column < columns; } int swimInWater(vector<vector<int>> &grid) { rows = grid.size(); columns = grid[0].size(); // 源点到各个点的最小游泳时间 vector<vector<int>> distance(rows, vector<int>(columns, INT_MAX)); // 标记是否从堆中弹出过 vector<vector<bool>> visited(rows, vector<bool>(columns, false)); // (x, y, 最小游泳时间),根据最小游泳时间排序 priority_queue<vector<int>, vector<vector<int>>, cmp> heap; // 至少要等到时间 grid[0][0] distance[0][0] = grid[0][0]; heap.emplace(vector<int>{0, 0, grid[0][0]}); while (!heap.empty()) { auto t = heap.top(); heap.pop(); int x = t[0]; int y = t[1]; int cost = t[2]; if (x == rows - 1 && y == columns - 1) return cost; if (visited[x][y]) continue; visited[x][y] = true; for (int i = 0; i < 4; ++i) { int nx = x + move[i]; int ny = y + move[i + 1]; // 非法或者已经从堆弹出过,就忽略 if (!isCoordinateLegal(nx, ny) || visited[nx][ny]) continue; // 新的代价是游到附近所要等待的最大时间 int newCost = max(cost, grid[nx][ny]); // 代价可以变小,就更新 if (newCost < distance[nx][ny]) { distance[nx][ny] = newCost; heap.emplace(vector<int>{nx, ny, newCost}); } } } return -1; } };
本文作者:n1ce2cv
本文链接:https://www.cnblogs.com/sprinining/p/18419334
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步