dijkstra算法(堆优化):
朴素版dijkstra算法的时间复杂度只和节点数量有关系,且时间复杂度为O(n^2)。当处理边很多的稠密图的时候,朴素版dijkstra算法没有问题。但是当遇到边很少的情况(稀疏图)时,时间开销有点过大了,因为朴素版的方法是建立一个二维数组,存储从一个点到另一个点是否存在边。当处理稀疏图时,由于边很少甚至有可能只有一条两条,所以绝大部分空间会被浪费。所以这个时候使用邻接矩阵并不合适,为此我们引入邻接表进行处理。邻接表就是建立一个用来存储链表的数组,每个链表的头节点就是起点,后面就是他指向的点。
dijkstra三部曲:1.找到源点到哪个点距离最近而且这个节点没有被访问过;2.将该节点标记为访问过;3.更新未访问节点到源点的距离。
卡码网:47.参加科学大会
思路:关于从边的角度怎么处理三部曲,我们可以把带权值的边存入小顶堆,因为小顶堆可以实现自动排序,这样我们在从小顶堆取元素时取出的就是距离源点最近的的节点所在的边。除此之外我们在往链表中存储元素时,存储的是节点和权值的键值对,即pair<int,int>。
代码:
#include <iostream>
#include <vector>
#include <list>
#include <queue>
#include <climits>
using namespace std;
class mycomparison{
public:
bool operator()(const pair<int,int> &lhs, const pair<int,int> &rhs){
//lhs表示左边的点 rhs表示右边的点
return lhs.second > rhs.second;
}
};
struct Edge{
int to;
int val;
//构造函数
Edge(int t,int w): to(t),val(w){};//初始化列表
};
int main(){
int n,m,p1,p2,val;
cin>>n>>m;//点数和边数
vector<list<Edge>> grid(n+1);//链表里存的是Edge结构体
for(int i=0;i<m;i++){
cin>>p1>>p2>>val; //p1源点 p2终点
grid[p1].push_back(Edge(p2,val));
}
int start = 1;
int end = n;
vector<int> minDist(n+1,INT_MAX);//存储源点到各个点的最大距离 初始化为最大值
vector<bool> visited(n+1, false);//标记某个节点是否被访问过
//优先级队列, 距离最小的节点在最上面 数据存入pair数组
priority_queue<pair<int,int>, vector<pair<int,int>>, mycomparison> pq;
pq.push(pair<int,int>(start,0));//到源点距离为0
minDist[start]=0;
while(!pq.empty()){
//寻找距离源点最近的未访问的节点
pair<int,int> cur = pq.top();//cur表示当前距离最近的节点
pq.pop();
if(visited[cur.first]) continue;//如果节点被访问过就跳过
visited[cur.first]=true;//将当前访问的节点标记为true
//更新未访问节点到当前节点的距离
for(Edge edge : grid[cur.first]){//遍历当前节点为源点的所有边
if(!visited[edge.to] && minDist[cur.first] + edge.val <minDist[edge.to]){
minDist[edge.to] = minDist[cur.first] + edge.val;
//将更新后的距离加入优先级队列
pq.push(pair<int, int>(edge.to, minDist[edge.to]));
}
}
}
if(minDist[end] == INT_MAX) cout<<-1<<endl; //到达不了终点
else cout<<minDist[end]<<endl;
return 0;
}
Bellman_ford算法:
bellman_ford算法也是求单源最短路径的算法之一,和dijkstra算法相比,这个算法中允许出现负权路径,也就是说允许路径的权值是负的,而dijkstra算法不可以。因为在dijkstra算法中,即使出现了负权路径,而在对某一个节点算最短路径时还没将此路径考虑进范围时,此节点可能就已经被访问从而导致得到的路径不是最短路径的问题。bellman_ford算法的核心思想是对所有边进行n-1次松弛操作。松弛简单来说就是不断更新到某一节点的权值。
松弛一次就是计算 与 源点 相距一条边 的节点的最短距离。
松弛两次就是计算 与 源点 相距两条边 的节点的最短距离。
......
松弛 n-1 次就是计算 与 源点 相距 n-1 条边 的节点的最短距离。
最后得到的就是源点到目标节点的最短距离。
卡码网:94. 城市间货物运输 I
思路:使用一个数组存储一条边的起点终点和权值,然后通过松弛不断更新源点与节点间的最短距离,最后得到结果。
代码:
#include<iostream>
#include<vector>
#include<list>
#include<climits>
using namespace std;
int main(){
int n,m,p1,p2,val;
cin >> n >> m;
vector<vector<int>> grid; //邻接矩阵存储所有边
for(int i=0;i<m;i++){
cin >> p1 >> p2 >> val;
//每个数组里存放起点终点和权值
grid.push_back({p1, p2, val});
}
int start = 1;
int end = n;
vector<int>minDist(n+1,INT_MAX);
minDist[1]=0;
for(int i=0;i<n;i++){
for(vector<int> &side : grid){//grid是存储vector<int>类型的数组
int from = side[0];
int to =side[1];
int price = side[2];//当前边的权值
//在松弛之前要保证起点要被计算过 也就是距离不可以为INT_MAX
if(minDist[from] != INT_MAX && minDist[to] > minDist[from] + price){
minDist[to] = minDist[from] + price;
}
}
}
if(minDist[end] == INT_MAX) cout << "unconnected" << endl;
else cout << minDist[end] <<endl;
return 0;
}