index
#写在前面
https://blog.csdn.net/lafea/article/details/107811927
#dijkstra
##一
----c++版
https://www.acwing.com/problem/content/851/
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=510;
int n,m;
int g[N][N];
int dist[N];//从1走到n号点的当前最短距离
bool st[N];//确定了最短路的点的集合
int dijkstra(){
memset(dist, 0x3f, sizeof dist);
dist[1]=0;
for(int i=0;i<n;i++){//迭代n次
int t=-1;//每次找出没确定最短路的点之中到 确定最短路的点的集合 的距离最小的点
for(int j=1;j<=n;j++)
if(!st[j]&&(t==-1||dist[t]>dist[j]))
t=j;
st[t]=true;//将找到的点加入确定最短路的点的集合
if(t==n)break;
for(int j=1;j<=n;j++)//实际上有m次
dist[j]=min(dist[j],dist[t]+g[t][j]);//用新的点的最短路去尝试更新所有当前最短路(也包括曾确定的)
}
if(dist[n]==0x3f3f3f3f)return -1;
return dist[n];
}
int main(){
scanf("%d%d",&n,&m);
memset(g, 0x3f, sizeof g);
while(m--){
//由于有重边和自环
int a,b,c;scanf("%d%d%d",&a,&b,&c);
g[a][b]=min(g[a][b], c);//不用管自环,用不到
}
int t=dijkstra();
printf("%d",t);
return 0;
}
##二 堆优化版
在朴素版中有【找出没确定最短路的点之中到 确定最短路的点的集合 的距离最小的点】这个操作,
朴素版用循环实现,堆优化直接用优先队列实现,省去了循环的过程
----c++版
你可以手写堆,这样堆里可以维持n个数,也可以使用c++提供的优先队列,但队列里可能有m个数
个人觉得堆优化版的dijkstra更好理解,更直接
https://www.acwing.com/problem/content/852/
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
//稀疏图用邻接表存
const int N=1e6+10;
int n,m;
int h[N],e[N],w[N],ne[N],idx;
int dist[N];
bool st[N];
typedef pair<int, int>pll;//由于我们还需要知道节点编号
void add(int a, int b, int c){
e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
int dijkstra(){
memset(dist, 0x3f, sizeof dist);
dist[1]=0;
priority_queue<pll, vector<pll>, greater<pll>>heap;//小根堆
heap.push({0,1});//1到1最短距离是0
while(heap.size()){
auto t=heap.top();
heap.pop();
int ver=t.second,distance=t.first;
if(st[ver])continue;st[ver]=true; //在集合内就丢掉
for(int i=h[ver];i!=-1;i=ne[i]){
int j=e[i];//更新这个点能走到的所有点的最短距离
if(dist[j]>distance+w[i]){
dist[j]=distance+w[i];
heap.push({dist[j],j});//把更新过的最短路加到堆里,虽然更短路会被堆pop掉,但没关系,因为distj是确实更新了,且堆只是为了找还在集合外的点
}
}
}
if(dist[n]==0x3f3f3f3f)return -1;
return dist[n];
}
int main(){
scanf("%d%d",&n,&m);
memset(h, -1, sizeof h);
while(m--){
//用邻接表存,有重边也没事了,算法保证一定会选最短边
int a,b,c;scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int t=dijkstra();
printf("%d",t);
return 0;
}
#bellman-ford
bellman-ford 的存边方式比较简单,只要能遍历到所有边就行,可以用最简单的开个结构体的方式
基本是两重循环
for 遍历n次:
for 从a到b权值w的所有边:
dist[b]=min(dist[b], dist[a]+w);
每次判断1到b的距离能不能用这条边来更新
遍历完保证对所有边满足dist[b]<=dist[a]+w, 这是松弛操作
bellman-ford可以处理负权边
如果有负权回路,最短路不一定存在
外层循环迭代k次,dist数组的含义是:不超过k条边到各点的最短路距离
如果第n次迭代还有边被更新,说明存在一条最短路上面有n条边,由于最多才n个点,抽屉原理,所有必然有两个点编号一样,存在负环。
有可能有负环又存在最短路的,
如果用spfa,则一定要求图中没有负环
##有边数限制的最短路
这个只能用bellman-ford,一般spfa比bellman-ford好
----c++版
https://www.acwing.com/problem/content/855/
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=510,M=10010;
int n,m,k;
int dist[N],backup[N];
struct edge{
int a,b,w;
}edges[M];
int bellmanford(){
memset(dist, 0x3f, sizeof dist);
dist[1]=0;
for(int i=0;i<k;i++){
//备份dist数组,因为可能出现串联
//即在此次更新过程中用此次更新的距离去更新其他距离,这样限制不了步数
//需要在每次更新时都使用上一次迭代的结果
memcpy(backup, dist, sizeof dist);
for(int j=0;j<m;j++){
int a=edges[j].a,b=edges[j].b,w=edges[j].w;
dist[b]=min(dist[b],backup[a]+w);
}
}
//大于一个比较大的数,因为到不了的点之间的负权边可能会把无穷大更新了
if(dist[n]>0x3f3f3f3f/2)return -1;
return dist[n];
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<m;i++){
int a,b,w;scanf("%d%d%d",&a,&b,&w);
edges[i]={a,b,w};
}
int t=bellmanford();
if(t==-1)puts("impossible");
else printf("%d", t);
return 0;
}
#spfa
网格型图易卡spfa
##spfa求最短路
对bellman-ford的优化
针对松弛操作的优化,如果dist[b]变小了,一定是因为dist[a]变小了,
如果有变小的距离,针对这个距离把之后的全部更新就得
更新过谁,再拿谁来更新别人
----c++版
实现和dijkstra很像
https://www.acwing.com/problem/content/853/
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef pair<int ,int> pll;
const int N=100010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N];
bool st[N];
void add(int a,int b,int c){
e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
//类似宽搜的方式
int spfa(){
memset(dist, 0x3f, sizeof dist);
dist[1]=0;
queue<int>q;
q.push(1);
st[1]=true;//存当前点是否在队列中
while(q.size()){
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];
if(!st[j]){//如果已经在就不用再加了
q.push(j);
st[j]=true;
}
}
}
}
if(dist[n]==0x3f3f3f3f)return -1;
return dist[n];
}
int main(){
scanf("%d%d",&n,&m);
memset(h, -1, sizeof h);
while(m--){
int a,b,c;scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int t=spfa();
if(t==-1)puts("impossible");
else printf("%d",t);
return 0;
}
##spfa判断负环
----c++版
https://www.acwing.com/problem/content/854/
// 思路和bellmanford差不多,抽屉原理,时间复杂度比较高
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
typedef pair<int ,int> pll;
const int N=100010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N], cnt[N];//cnt记录步数,
bool st[N];
void add(int a,int b,int c){
e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
//类似宽搜的方式
int spfa(){
//不需要初始化了
queue<int>q;
for(int i=1;i<=n;i++){
st[i]=true;
q.push(i);
}//不固定起点
while(q.size()){
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n)return true;
if(!st[j]){//如果已经在就不用再加了
q.push(j);
st[j]=true;
}
}
}
}
return false;
}
int main(){
scanf("%d%d",&n,&m);
memset(h, -1, sizeof h);
while(m--){
int a,b,c;scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
if(spfa())puts("Yes");
else puts("No");
return 0;
}