Dijkstra 算法

news/2024/9/20 7:19:48/文章来源:https://www.cnblogs.com/sprinining/p/18419334

普通堆实现的 Dijkstra 算法

  • 时间复杂度为 O(m * logm),m 为边数
  1. distance[i] 表示从源点到 i 点的最短距离,visited[i] 表示 i 节点是否从小根堆弹出过

  2. 准备好小根堆,小根堆存放记录:(x 点,源点到 x 的距离),小根堆根据距离排序

  3. 令 distance[源点] = 0,(源点,0)入堆

  4. 从小根堆弹出(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

  5. 小根堆为空,过程结束。

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>> &times, 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 到自身距离为 0heap.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 信息,然后调整堆,同时也要调整反向索引表。

  1. 把(源点,0)加入反向索引堆,过程开始

  2. 反向索引堆弹出(u,源点到 u 的距离),考察 u 的每条边,假设某条边去往 v,边权为 w

    a. 如果 v 没有进入过反向索引堆,新增记录(v,源点到 u 的距离 + w)

    b. 如果 v 曾经从反向索引堆弹出过,忽略

    c. 如果 v 在反向索引堆里,看看源点到 v 的距离能否变小,能就调整堆,否则跳过

    d. 处理完 u 的每条边,重复步骤 2

  3. 反向索引堆为空过程结束。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>> &times, 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;}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/799536.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P2710 数列/P2042 [NOI2005] 维护数列

题意(以 P2710 为例)思路 使用 FHQ-Treap 进行求解,清晰明了。对于 insert,先将要插入的数建成一棵树,然后将这棵树放入 FHQ-Treap 中。 对于 delete,将要删除的树分离出来,然后把剩下的部分合并即可,将删除的树的树根丢到废弃节点的栈中以备以后使用(节约空间,不然 …

扩展分析C语言单双引号、反斜杠与注释

目录注释奇怪的注释C风格的注释无法嵌套一些特殊的注释注释的规则建议反斜杠\反斜杠有续行的作用,但要注意续行后不能添加空格回车也能起到换行的作用,那续行符的意义在哪?反斜杠的转义功能单引号和双引号字面值,字符串,字符,字符变量的大小为什么sizeof(1)的大小是4 ?char…

扩展分析单双引号、反斜杠与注释

目录注释奇怪的注释C风格的注释无法嵌套一些特殊的注释注释的规则建议反斜杠\反斜杠有续行的作用,但要注意续行后不能添加空格回车也能起到换行的作用,那续行符的意义在哪?反斜杠的转义功能单引号和双引号字面值,字符串,字符,字符变量的大小为什么sizeof(1)的大小是4 ?char…

C----函数递归之反汇编

环境 win10 vc6.0 debug 代码 关于求阶层问题:n!=n(n-1)!;(n-1)! = (n-1)(n-2)! 例如5!=5(4)! 4!=43! 3!=32! 2!=21 函数递归的出口是1,所以函数递归最重要的条件是去寻找递归的出口 int fun(int i) {int sum = 0;if (i == 1){return 1;}else{sum = i*fun(i-1);}return sum …

地平线占用预测 FlashOcc 参考算法-V1.0

1.简介 3D Occupancy Networks 的基本思路是将三维空间划分成体素网格,并对每个网格进行各类感知任务的预测。目前以网格为中心的方法能够预测每个网格单元的占用率、语义类别、未来运动位移和实例信息。3D occupancy 可以对道路障碍物进行更细粒度的划分,同时获取更精确的占…

手脱upx

其实已经是大一下刚开始的事情了,补个档 手动脱壳の新年快乐 查壳,有壳,UPXX32dbg打开文件,查看初始断点点击PUSHAD跟进,CTRL+*设置EIP,开始F8步过,寻找ESP寄存器第一次单个变红的地址此时的内存窗口开始步过第一次步过就发现ESP单个变红,右键跟进内存窗口然后在第一个…

使用firemin降低火狐内存占用

这些年一直使用火狐浏览器,之前一直在AMD平台的机器使用,没有遇到过内存占用过大的问题(可能也与平台无关)。现在在Intel CPU的机器上使用,时间一久,内存就占用很大。试过Firefox/内存消耗严重里面的办法,效果不明显。也试过修改about:config里面的一些选项,也没有达到…

代码随想录算法 - 回溯算法1

题目1 77. 组合 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1: 输入:n = 4, k = 2 输出: [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ]示例 2: 输入:n = 1, k = 1 输出:[[1]]提示:1 <= n <= 20 1 <= k…

错误

PID自己搭的时候,要注意积分模块的位置,搞不明白好久了,原来是我把积分模块的位置放错了。直接用增益模块不容易出错。

OSG开发笔记(三十):OSG加载动力学仿真K模型文件以及测试Demo

前言Osg需要打开模型文件,但是遇到显示动力学仿真的K模型文件,.k文件是一种描述材料属性的文件,比如密度、弹性模量等,该模型文件不是常规中间开放格式,无法直接支持,需要自定义解析并且重建三维模型。 Demo实际非常流程,因为视频转gif导致部分看起来不行:   交互流畅…

0918高数一千题,多元函数积分学

T17.第一型曲线积分空间形式 用斯托克斯公式化成第二型曲面积分 解第二型曲面积分,用高斯公式或者投影转换法,后者注意正负号,上正下负,前正后负 T18.换路径,但是x=1左半段不能化成lnx算 T19.求偏导就对对应字母求就行,不用对y导x T20.多元极值AC-B2>0A>0,极小 A<…