AcWing算法基础课 最短路

最短路问题

一、单源最短路(一个点到所有点的最短距离)

1、所有边的权重都为正

n个点,m个边

(1)朴素dijkstra O(n^2)

适合稠密图,O(n^2)=O(m)

算法步骤:

①初始化距离 dis[1]=0,dis[i]=+∞,集合s为已经找到的点的集合

② for i=1:n

  找到点t,t不在s中且距离最近

  将s加入t

  用t更新其他所有点的距离,dis[t]+t到其他点的边的距离

稠密图适合用邻接矩阵存储!

例题:849. Dijkstra求最短路 I

复制代码
#include<iostream>
#include<cstring>
using namespace std;

const int N=510;

int A_M[N][N];

int n,m;
int dis[N];
bool st[N];
int dijkstra()
{
    memset(dis,0x3f,sizeof(dis));
    dis[1]=0;//dis初始化
    for(int i=0;i<n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
        {
            if(!st[j]&&(t==-1||dis[j]<dis[t]))
            t=j;
        }
        if(t!=-1)
        {
            st[t]=true;
            for(int j=1;j<=n;j++)
            {
                dis[j]=min(dis[t]+A_M[t][j],dis[j]);
            }
        }
    }
    return dis[n]==0x3f3f3f3f?-1:dis[n];
}
int main()
{
    memset(A_M,0x3f,sizeof(A_M));
    cin>>n>>m;
    while (m -- )
    {
        int x,y,z;
        cin>>x>>y>>z;
        A_M[x][y]=min(A_M[x][y],z);
    }
    for(int i=0;i<N;i++)
        A_M[i][i]=0;
    cout<<dijkstra();
    return 0;

}                        
View Code
复制代码

 

(2)堆优化版的dijkstra O(mlogn)

适合稀疏图,O(n)=O(m),稀疏图使用邻接表存储,h[],e[],ne[],w[],idx,分别存储邻接表头,节点编号,节点的下一个节点,节点的权重(距离),可插入节点

对(1)②中,将全部点按距离加入最小堆中,则找到点t的复杂度为O(1),用t更新其他全部点,复杂度是mlogn(全部边的数量乘在堆中修改的复杂度)

堆可以手写或用priority_queue,由于priority_queue不支持修改,可以仅不断插入,则对于一个点,后插入的距离一定小于先插入,复杂度变为O(mlogm)=O(mlogn)

例题:850. Dijkstra求最短路 II

复制代码
#include<iostream>
#include <queue>
#include<cstring>
using namespace std;

const int N=150010;
int dis[N];
bool vis[N];
typedef pair<int,int> PII;
int h[N],e[N],ne[N],w[N],idx=0;

int n,m;

void insert(int x,int y,int z)
{
    idx++;
    e[idx]=y;
    ne[idx]=h[x];
    w[idx]=z;
    h[x]=idx;
}
int dijkstra()
{
    memset(dis,0x3f,sizeof dis);
    dis[1]=0;
    
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({0,1});
    while(heap.size())
    {

        auto [dist,node]=heap.top();
        //cout<<node<<endl;
        heap.pop();
        if(vis[node])
            continue;
        vis[node]=true;
        for(int i=h[node];i!=0;i=ne[i])
        {
            int j=e[i];
            //cout<<j<<endl;
            if(dis[j]>dist+w[i])
            {
                dis[j]=(dist+w[i]);
                heap.push({dis[j],j});
                
            }
        }

    }
    return dis[n]==0x3f3f3f3f?-1:dis[n];
}
int main()
{
    cin>>n>>m;
    while (m -- )
    {
        int x,y,z;
        cin>>x>>y>>z;
        insert(x,y,z);
    }
    cout<<dijkstra();
    return 0;
}
View Code
复制代码

 

2、存在负权重边

若存在负权回路,则最小距离可能是-∞

(1)Bellman-Ford O(nm)

可以求限制步数k的最短路

算法可以不用存储图,而是将所有边(a->b=w)用数组存储

①初始化dis[],dis[1]=0,dis[i]=∞

②for n   //当前迭代次数k,代表着从当前点,经过不超过k步到达每个点的最短路径

  dis_fore=dis;//防止一次循环迭代多步,记录上一步的dis

  for 所有边 a, b, w

    dis[b]=min(dis_fore[b],dis_fore[a]+w);//类似dijkstra,每次对所有边进行更新,而不是对最小距离点的边进行更新

853. 有边数限制的最短路

复制代码
 1 #include <iostream>
 2 #include <cstring>
 3 #include <algorithm>
 4 
 5 using namespace std;
 6 
 7 
 8 int n,m,k;
 9 int dis1[510];
10 int dis2[510];
11 vector<pair<pair<int,int>,int>> edge;
12 
13 int main()
14 {
15     cin>>n>>m>>k;
16     while (m -- )
17     {
18         int x,y,z;
19         cin>>x>>y>>z;
20         edge.push_back({{x,y},z});
21     }
22     memset(dis1,0x3f,sizeof dis1);
23     dis1[1]=0;
24     memset(dis2,0x3f,sizeof dis2);
25     dis2[1]=0;
26     int *ptr1=dis1;
27     int *ptr2=dis2;
28     while(k--)
29     {
30         auto tmp=ptr2;
31         ptr2=ptr1;
32         ptr1=tmp;
33         for(auto [node,dist]:edge)
34         {
35             ptr1[node.second]=min(ptr1[node.second],ptr2[node.first]+dist);
36         }
37     }
38     if(ptr1[n]>0x3f3f3f3f/2)
39         cout<<"impossible";
40     else
41         cout<<ptr1[n];
42     return 0;
43 }
View Code
复制代码

 

(2)SPFA 一般:O(m),最坏:O(nm)

可以解决不含负权,也可以解决含负权边,需要不限制步数且图中不含负环(也可以判断是否有负环)

是对Bellman-Ford算法的优化

BF算法的②中,只有点a的距离被减小了,其后继点的距离才会减小

①初始化,queue,将第一个点入队

② while(que不空)

  取出点t=que.front;

  更新t的所有出边 t→b

  将b加入队列que(判断b是否未在队列中)//只要b在队列中,就可以利用本次更新距离的影响

其余和BF都一样

851. spfa求最短路

复制代码
 1 #include<iostream>
 2 #include <queue>
 3 #include<cstring>
 4 using namespace std;
 5 
 6 const int N=100010;
 7 int n,m;
 8 int h[N], e[N], w[N], ne[N], idx=0;
 9 int dis[N];
10 bool in_que[N];
11 
12 void Insert(int x,int y,int z)
13 {
14     idx++;
15     e[idx]=y;
16     ne[idx]=h[x];
17     w[idx]=z;
18     h[x]=idx;
19 }
20 
21 bool SPFA()
22 {
23     queue<int> que;
24     memset(dis,0x3f,sizeof dis);
25     que.push(1);
26     dis[1]=0;
27     in_que[1]=true;
28     while(que.size())
29     {
30         int node=que.front();
31         que.pop();
32         in_que[node]=false;
33         for(int i=h[node];i!=0;i=ne[i])
34         {
35             int j=e[i];
36             if(dis[j]>dis[node]+w[i])
37             {
38                 dis[j]=dis[node]+w[i];
39                 if(!in_que[j])
40                 {
41                     in_que[j]=true;
42                     que.push(j);
43                 }
44             }
45         }
46     }
47     if(dis[n]>0x3f3f3f3f/2)
48         cout<<"impossible";
49     else
50         cout<<dis[n];
51     
52 }
53 int main()
54 {
55     cin>>n>>m;
56     while(m--)
57     {
58         int x,y,z;
59         cin>>x>>y>>z;
60         Insert(x,y,z);
61     }
62     SPFA();
63     return 0;
64 }
View Code
复制代码

当判断是否存在负环时,需要记录cnt[i],表示第i个点的当前最短路径有几步。

每次更新,cnt[j]=cnt[node]+1;

当cnt[j]>=n时,则存在负环(最短路径上存在重复点,重复点处有负环)

判断负环时,初始化时队列中需要放入全部点,cnt[i]、dis[i]初始化为全0,dis[i]表示任意点到当前点i的最小距离

在循环时,负边会令dis[i]逐渐减小

二、多源汇最短路(多个点到多个点的最短路)

Floyd算法,O(n^3)

可以存在负权边,但是不能存在负环

 for k 1:n

  for i 1:n

    for j 1:n

      d[i,j]=min(d[i,j],d[i,k],d[k,j]); 用k更新i和j的距离

注意,k在最外层

posted @   80k  阅读(137)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示