最短路算法
被这几个板子折磨,打一打最近学的最短路模板
1.Floyd朴素算法
优点:编程复杂度低,并且状态转移方程可以多变
缺点:时空复杂度高,不易优化
最短路
#include <bits/stdc++.h>
using namespace std;
#define map mymap
const int p=100+1;
int n,m,s,t;
int ans;
int map[p][p];
int main(){
cin>>n>>m>>s>>t;
memset(map,0x3f,sizeof(map));
int x,y;
for(int i=1;i<=m;i++){
cin>>x>>y;
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
map[i][j]=min(map[i][j],map[i][k]+map[k][j]);
}
}
}
cout<<map[s][t];
return 0;
}
最小环
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int w[105][105];
int s[105][105];
int n,m;
int x,y,l;
int ans=0x3f3f3f3f;
int main()
{
scanf("%d%d",&n,&m);
memset(w,0x3f3f3f3f,sizeof(w));
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&l);
w[x][y]=min(w[x][y],l);
w[y][x]=min(w[y][x],l);
}
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++)
{
s[i][j]=w[i][j];
}
}
for (int k=1;k<=n;k++)
{
for (int i=1;i<k;i++)//判断回路
{
for (int j=i+1;j<k;j++)
ans=min(ans,w[i][j]+s[i][k]+s[k][j]);
//此时 w[i][j]最短路一定不经过k点,因为更新还没有更新到k,0到k-1更新完毕,所以这个式子才是判断回路
}
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++)
{
w[i][j]=min(w[i][j],w[i][k]+w[k][j]);
}
}
}
printf("%d\n",ans);
return 0;
}
2.Dijsktra迪杰斯特拉算法
优点:时间复杂度较小,为O(n^2)级(应用数据规模从500到10000),可以被优化,复杂度稳定
Upd:其实是mlogn反正远古博客直接就混乱邪恶吧
缺点:无法处理负边权问题,单源
如果有特殊性质还是可以跑负权的:把连通块看成广义点以后整张图是DAG
1.无优化
普通的Dijsktra算法
#include <bits/stdc++.h>
using namespace std;
int a[101][3];
double c[101];
bool b[101];
double f[101][101];
int n,m,s,t;
void in(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i][1],&a[i][2]);
}
memset(f,127,sizeof(f));
scanf("%d",&m);
int x,y;
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
f[x][y]=f[y][x]=sqrt(pow(double(a[x][1]-a[y][1]),2)+pow(double(a[x][2]-a[y][2]),2));
}
scanf("%d%d",&s,&t);
}
void dij(){
memset(b,0,sizeof(b));
b[s]=1;
c[s]=0;
for(int i=1;i<=n;i++){
c[i]=f[s][i];
}
double minl;
for(int i=1;i<n;i++){
minl=1e30;
int k=0;
for(int j=1;j<=n;j++){
if((!b[j])&&(c[j]<minl)){
minl=c[j];
k=j;
}
}
if(k==0){
break;
}
b[k]=1;
for(int j=1;j<=n;j++){
if(c[k]+f[k][j]<c[j]){
c[j]=c[k]+f[k][j];
}
}
}
}
void out(){
printf("%.2lf",c[t]);
}
int main(){
in();
dij();
out();
return 0;
}
优先队列优化
#include <bits/stdc++.h>
using namespace std;
const int o=1e5;
int n,m,s,t,cnt,x,y,z;
int dis[o],head[o];
bool vis[o];
struct path{
int s;
int t;
int v;
int n;
}a[o];
struct node{
int id;
int exp;
}p;
struct cmp{
bool operator()(node a,node b){
return a.exp>b.exp;
}
};
priority_queue<node,vector<node>,cmp>q;
void add(int st,int t,int v){
cnt++;
a[cnt].s=st;
a[cnt].t=t;
a[cnt].v=v;
a[cnt].n=head[st];
head[st]=cnt;
}
void in(){
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
}
void dij(int s){
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[s]=0;
p.id=s;
p.exp=0;
q.push(p);
while(!q.empty()){
p=q.top();
q.pop();
int to=p.id;
if(vis[to]){
continue;
}
vis[to]=1;
for(int i=head[to];i>0;i=a[i].n){
int j=a[i].t;
if(dis[j]>dis[to]+a[i].v){
dis[j]=dis[to]+a[i].v;
if(!vis[j]){
node r;
r.id=j;
r.exp=dis[j];
q.push(r);
}
}
}
}
}
void out(){
cout<<dis[t];
}
int main(){
in();
dij(s);
out();
return 0;
}
3.spfa算法
优点:可解负边权问题,时间复杂度为O(kn),k常数较小,因此快,可优化
缺点:可被特殊构造的数据卡掉退化为O(mn)
upd:当时写的东西看不明白,但是尽量别写别的直接卡成指数
然后卡SPFA学了网格图
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define map mymap
queue<int>q;
int n,m,map[2021][2022],dis[2005];
bool vis[2005];
void spfa(){
q.push(1);
vis[1]=true;
dis[1]=0;
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=false;
for(int i=1;i<=n;i++){
if(map[x][i]>0&&(min(dis[x],map[x][i])>dis[i]||!dis[x])){
if(!dis[x]){
dis[i]=map[x][i];
}
else{
dis[i]=min(dis[x],map[x][i]);
}
if(!vis[i]){
vis[i]=true;
q.push(i);
}
}
}
}
}
int main(){
scanf("%d",&n);
memset(map,0,sizeof(map));
for(int i=2;i<=n;i++){
dis[i]=0;
}
int f,t,p;
while(scanf("%d%d%d",&f,&t,&p)==3){
if(!f&&!t&&!p){
break;
}
else{
map[f][t]=p;
}
}
spfa();
for(int i=2;i<=n;i++){
printf("%d\n",dis[i]);
}
return 0;
}
再贴一下二维SPFA和SPFA判断有无负环然后跑路
upd:二维实际上就是做n次SPFA,判断负环入队次数考虑k的大小
随机图上k大于3直接认定有负环问题并不是非常大但是还是错的
二维SPFA
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std ;
const int INF=0xfffffff ;
struct node{
int s,t,v,nxt ;
}e[1000005] ;
int n,m,k,cnt,head[100005],vis[100005][55],dis[100005][55] ;
void add(int s,int t,int v)
{
e[cnt].s=s ;
e[cnt].t=t ;
e[cnt].v=v ;
e[cnt].nxt=head[s] ;
head[s]=cnt++ ;
}
void spfa(int s)
{
for(int i=0 ;i<=n ;i++)
for(int j=0 ;j<55 ;j++)
dis[i][j]=INF ;
dis[s][0]=0 ;
memset(vis,0,sizeof(vis)) ;
vis[s][0]=1 ;
queue <pair<int,int> > q ;
q.push(make_pair(s,0)) ;
while(!q.empty())
{
pair<int,int> u=q.front() ;
q.pop() ;
vis[u.first][u.second]=0 ;
int step=u.second+1 ;
if(step>k)step=k ;
for(int i=head[u.first] ;i!=-1 ;i=e[i].nxt)
{
int tt=e[i].t ;
if(dis[tt][step]>dis[u.first][u.second]+e[i].v)
{
dis[tt][step]=dis[u.first][u.second]+e[i].v ;
if(!vis[tt][step])
{
vis[tt][step]=1 ;
q.push(make_pair(tt,step)) ;
}
}
}
}
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
cnt=0 ;
memset(head,-1,sizeof(head)) ;
while(m--)
{
int a,b,c ;
scanf("%d%d%d",&a,&b,&c) ;
add(a,b,c) ;
add(b,a,c) ;
}
int s,t ;
scanf("%d%d%d",&s,&t,&k) ;
k=k/10+(k%10!=0) ;
spfa(s) ;
if(dis[t][k]==INF)puts("-1") ;
else printf("%d\n",dis[t][k]) ;
}
return 0 ;
}
SPFA判断负环
#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
int const maxNum=1001;
int const Infinity=99999999;
int map[maxNum][maxNum],dis[maxNum];//dis用来存放点的最佳路径
int nodeNum,time[maxNum];//time用来记录结点入队的次数
int vst[maxNum],visited[maxNum];//标志是否入队,标志点是否扫描过
bool SPFA(int start)//经典的SPFA算法
{
int i,p;
queue<int> que;
memset(vst,0,sizeof(vst));
memset(time,0,sizeof(time));
for(i=1;i<=nodeNum;i++)
dis[i]=Infinity;
dis[start]=0;
vst[start]=1;
que.push(start);
time[start]++;//起点先标志入队一次
while(!que.empty())
{
p=que.front();
que.pop();
vst[p]=0;
for(i=1;i<=nodeNum;i++)
{
visited[i]=true;//这里用来标志已经被扫描过的点。注意visited跟vst数组的区别
if(dis[p]+map[p][i]<dis[i])
{
dis[i]=dis[p]+map[p][i];
if(!vst[i])
{
que.push(i);
time[i]++;
if(time[i]>nodeNum)//当同一结点入队次数超过点的总数-1,即大于等于nodeNum时,存在负环,此题的关键
return true;
vst[i]=1;
}
}
}
}
return false;
}
int main(void)
{
int cas,n,num,i,s,e,w,j;
scanf("%d",&cas);//田地的个数
while(cas--)
{
scanf("%d%d%d",&nodeNum,&n,&num);//结点数,边数,虫洞数
for(i=1;i<=nodeNum;i++)
for(j=1;j<=nodeNum;j++)
map[i][j]=Infinity;
for(i=1;i<=n;i++)
{
scanf("%d%d%d",&s,&e,&w);//边的起点,边的终点,走这条边所花的时间
if(map[s][e]>=w)
{
map[s][e]=w;//注意重边这种情况,取最小的那个
}
if(map[e][s]>=w)//双向边,应该每次都比较一下,(但是这道题目不比较也过)
{
map[e][s]=w;
}
}
for(i=1;i<=num;i++)
{
scanf("%d%d%d",&s,&e,&w);
if(map[s][e]>-w)
map[s][e]=-w;//这道题目这里似乎说明得不是很严谨,如果一条边原来是Infinity,而现在有一个负的边,那不就替代了么
//题目似乎被理想化了,这种情况不考虑在其中,不知道说得对不对,望高手指教
}
memset(visited,0,sizeof(visited));
for(i=1;i<=nodeNum;i++)//考虑到图可能不是完全连通图,即存在离散的子图
{
if(visited[i]) continue;//增加这一步以减少不必要的计算
if(SPFA(i))
{
cout<<"YES"<<endl;
break;
}
} if(i==nodeNum+1)
cout<<"NO"<<endl;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具