笔记:代码随想录算法训练营day65:dijkstra(堆优化版)精讲、Bellman_ford 算法精讲
学习资料:代码随想录
文中含大模型生成内容
dijkstra(堆优化版)精讲
卡码网:47. 参加科学大会
利用广度优先搜索去遍历图。使用list来存储图。将边按权值(将当前点的下一节点以及边的值)放入优先级队列,判定条件依然为minDis数组[当前](代表当前节点到源节点的最短距离)的值是否大于minDis[上一节点]
#include <iostream>
#include <vector>
#include <climits> //包含各种整数类型的最大值和最小值
#include <queue>
#include <list>
using namespace std;
class mycompare{
public:
bool operator()(const pair<int,int>& lhs,const pair<int,int>& rhs){
return lhs.second>rhs.second; //std::priority_queue 默认是大顶堆std::priority_queue<T, Container, Compare> 的 Compare 并不是直接定义排序规则,而是一个谓词,用于判断“谁的优先级更低”。
//默认情况下,std::priority_queue 使用 std::less<T> 作为比较器,因此它构造的是一个 大顶堆(最大堆),即 大的元素优先级高。
//但是,当我们自定义 Compare 时,我们的比较器 operator() 必须返回 true 表示 lhs 的优先级低于 rhs,即 rhs 应该排在 lhs 之前。
}
};
struct Edge{
int destination;
int value;
Edge(int e,int v):destination(e),value(v){};
};
int main(){
int n,m,s,e,v;
cin>>n>>m;
vector<list<Edge>> traffic(n+1);
for(int i=0;i<m;i++){
cin>>s>>e>>v;
traffic[s].push_back(Edge(e,v));
}
vector<int> minDis(n+1,INT_MAX);
vector<bool> visited(n+1,false);
priority_queue <pair<int,int>,vector<pair<int,int>>,mycompare> pq;
int start = 1;
int end = n;
pq.push(pair<int,int>(start,0));
minDis[start]=0;
while(!pq.empty()){
pair<int,int> cur =pq.top(); //优先级队列自动排好序了,所以不用再for去找了
pq.pop();
if(visited[cur.first]) continue;
visited[cur.first]=true;
for(Edge edges:traffic[cur.first]){
if(!visited[edges.destination]&&minDis[cur.first]+edges.value<minDis[edges.destination]){
minDis[edges.destination]=minDis[cur.first]+edges.value;
pq.push(pair<int,int>(edges.destination,minDis[edges.destination]));
}
}
}
if(minDis[end]==INT_MAX) cout<<-1;
else cout<<minDis[end];
}
- 时间复杂度:O(ElogE) E 为边的数量
- 空间复杂度:O(N + E) N 为节点的数量
比较函数定义有点绕,这个 operator()
的作用是:
-
当
lhs.second > rhs.second
时,返回true
,表示lhs
的优先级低于rhs
,因此rhs
应该排在前面(堆顶)。 -
这样,
priority_queue
就会把 second 值较小的元素放在堆顶,从而形成 小顶堆(最小堆)。
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) { return lhs.second > rhs.second; }是c++中的仿函数写法。在 priority_queue
里,我们传入的是 比较器类(MyComparator
),它没有普通的函数,而是 重载了 operator()
,所以可以像函数一样使用。
2. 为什么不用普通函数,而是用 operator()
?
因为 priority_queue
需要一个可调用的比较器对象,而 STL 容器一般不接受裸函数(bool cmp(pair<int, int>, pair<int, int>)
)。
只能传递:
-
仿函数(重载
operator()
的类) ✅ -
Lambda 表达式 ✅
-
函数指针(但不推荐,性能差) ❌
优先级队列基础知识:C++ 容器类 <priority_queue> | 菜鸟教程
Bellman_ford 算法精讲
卡码网:94. 城市间货物运输 I
Bellman_ford 算法解决边有负权值的情况
对所有边松弛一次,相当于计算 起点到达 与起点一条边相连的节点 的最短距离(代码随想录)需要对所有边松弛n-1次才能得到 起点(节点1) 到终点(节点n)的最短距离
松弛:理解上来说,松弛就是更新minDis数组的过程,对A到B这条边
if (minDist[B] > minDist[A] + value) minDist[B] = minDist[A] + value
在判断条件中要加上判断该节点minDist数组是否被计算过,边的起点的minDist没被更新时不能更新边的终点
minDist[from] != INT_MAX
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int main(){
int n,m,s,t,v;
cin>>n>>m;
vector<vector<int>> traffics;
for(int i=0;i<m;i++){
cin>>s>>t>>v;
traffics.push_back({s,t,v});
}
int start=1;
int end=n;
vector<int> transCost(n+1,INT_MAX);
transCost[1]=0;
for(int i=0;i<n-1;i++){ //松弛次数为点数-1,而不是边数m-1
for(vector<int>& side : traffics){
int from=side[0];
int to = side[1];
int price = side[2];
if(transCost[from]!=INT_MAX&&transCost[to]>transCost[from]+price){
transCost[to]=transCost[from]+price;
}
}
}
if(transCost[end]==INT_MAX) cout<<"unconnected";
else cout<<transCost[end];
}
- 时间复杂度: O(N * E) , N为节点数量,E为图中边的数量
- 空间复杂度: O(N) ,即 minDist 数组所开辟的空间
题目说保证道路网络中不存在任何负权回路,对于负权回路使用该写法的话,比如下面这种情况:
1 → 2 → 3 → 4 → 2
设这个回路 2 → 3 → 4 → 2
的权值总和是负数,比如 -10
。
那么你可以无限次从城市1绕一圈这个回路,每次都让总花费减 10
,理论上可以让路径成本趋近于负无穷!
这种情况下,没有最短路径这个概念了,因为你永远可以更短(或者说赚更多)。
对于朴素dijkstra,都是正数的情况下对于图中(图源代码随想录)这种,
个人小疑点:如果-300处是正数的话,就能正确了吗?答案是当然,可以随便更改1到2,1到3,2到3三个位置处的权值,取一个极端情况,1到2和1到3都是1,但第一步选的1到3,因为是正数,所以1到2的1随便加一个正数都比1到3大了,所以不会出现错过1到2到3权值更小但被错过的情况