【图论】最短路算法

知识点图片来源于董晓算法

图的存储:链式前向星

cpp 复制代码
const int N=1e3+10,M=2e3+10,mod=998244353,inf=0x3f3f3f3f;//1e5就超时
struct edge{
    int to,w,nxt;
};
edge e[N];//实际存储容器
int head[N],tot;
int n,m;
void init(){
    forr(i,0,M)e[i].nxt=0;
    forr(i,0,N)head[i]=0;
    tot=0;
}
void addedge(int u,int v,int w){//加点
    e[++tot].to=v;//指向的点
    e[tot].w=w;
    e[tot].nxt=head[u];
    head[u]=tot;
}
void solve(){
    cin>>n>>m;
    forr(i,1,m){
        int u,v,w;cin>>u>>v>>w;
        addedge(u,v,w);
    }
    //遍历一个点的出边
    int now=3;
    for(int i=head[now];i;i=e[i].nxt){
        cout<<e[i].to<<' '<<e[i].w<<endl;
    }
}

单源最短路

Dijkstra

O(n2)模板

样例

输入

4 6 1

1 2 2

2 3 2

2 4 1

1 3 5

3 4 3

1 4 4

输出

0 2 4 3

cpp 复制代码
const int N=1e4+10,mod=998244353,inf=pow(2,31)-1;//1e5就超时
struct edge
{
    int v,w;
};

int n,m,st;
vector<edge>e[N];
int dis[N],vis[N];
void dij(int s){
    forr(i,0,n)dis[i]=inf;//都初始化为inf 注意now初始为0 dis[0]=inf
    dis[s]=0;
    forr(i,1,n){
        int now=0;
        forr(j,1,n)//取出距离st最短的点
            if(!vis[j]&&dis[j]<dis[now])now=j;
        vis[now]=1;//拿出来
        // cout<<now<<endl;
        for(auto ed:e[now]){//用now做跳板更新相邻的边
            int v=ed.v,w=ed.w;
            if(dis[v]>dis[now]+w)dis[v]=dis[now]+w;
        }
        
    }
}
void solve(){
    cin>>n>>m>>st;
    forr(i,1,m){
        int a,b,val;
        cin>>a>>b>>val;
        e[a].push_back({b,val});
    }
    dij(st);
    forr(i,1,n)cout<<dis[i]<<' ';
}

只适用于正边权,不能用于负边权

O(n2)

n=1e3 m=1e6稠密图能过

n=1e6 m=1e6 T

O(mlogm)堆优化

cpp 复制代码
const int N=1e5+10,mod=998244353,inf=pow(2,31)-1;//1e5就超时
struct edge
{
    int v,w;
};
int n,m,st;
vector<edge>e[N];
int dis[N],vis[N];
priority_queue<pair<int,int>>q;//默认为大根堆
void dij(int s){
    forr(i,0,n)dis[i]=inf;//都初始化为inf 注意now初始为0 dis[0]=inf
    dis[s]=0;q.push(make_pair(0,s));
    while (q.size())
    {
        auto t=q.top();q.pop();
        int now=t.second;

        if(vis[now])continue;//把之前插入优先队列的遍历过的点跳过
        vis[now]=1;

        for(auto ed:e[now]){//用now做跳板更新相邻的边 松弛操作
            int v=ed.v,w=ed.w;
            if(dis[v]>dis[now]+w){
                dis[v]=dis[now]+w;
                q.push(make_pair(-dis[v],v));//把更新过的点扔进堆里 d[v]越小 -d[v]越大 利用负号构建小根堆
            }
        }
    }
}
void solve(){
    cin>>n>>m>>st;
    forr(i,1,m){
        int a,b,val;
        cin>>a>>b>>val;
        e[a].push_back({b,val});
    }
    dij(st);
    forr(i,1,n)cout<<dis[i]<<' ';
}

O(mlogm)

m=1e6可过

路径记录和输出

cpp 复制代码
void dij(int s){
    forr(i,0,n)dis[i]=inf;//都初始化为inf 注意now初始为0 dis[0]=inf
    dis[s]=0;q.push(make_pair(0,s));
    while (q.size())
    {
        auto t=q.top();q.pop();
        int now=t.second;

        if(vis[now])continue;//把之前插入优先队列的遍历过的点跳过
        vis[now]=1;

        for(auto ed:e[now]){//用now做跳板更新相邻的边 松弛操作
            int v=ed.v,w=ed.w;
            if(dis[v]>dis[now]+w){
                dis[v]=dis[now]+w;
                pre[v]=now;//记录前驱点
                q.push(make_pair(-dis[v],v));//把更新过的点扔进堆里 d[v]越小 -d[v]越大 利用负号构建小根堆
            }
        }
    }
}
//输出任意一点到st的最短路径
void dfs_path(int now){//dfs从末点遍历到st
    if(now==st)return cout<<now<<endl,void();
    dfs_path(pre[now]);
    cout<<now<<endl;
}

Bellman-Ford O(nm)


可以用来判断负环
P3385

cpp 复制代码
struct edge{int v,w;};
vector<edge>e[N];
int d[N];//距离
int n,m;
bool bellmanford(int s){
    forr(i,1,n)d[i]=inf;
    d[s]=0;
    bool fg;//是否松弛
    //O(nm)
    forr(i,1,n){//更新n次 因为一个点可以多次更新,所以可判负环
        fg=0;
        forr(u,1,n){//以每一个点为跳板
            if(d[u]==inf)continue;
            for(auto ed:e[u]){//更新每条出边
                int v=ed.v,w=ed.w;
                if(d[v]>d[u]+w)d[v]=d[u]+w,fg=1;
            }
        }
        if(!fg)break;
    }
    return fg;//第n轮仍为true则有环 因为负边权让d越来越小 必定有松弛操作
}
void solve(){
    cin>>n>>m;
    forr(i,1,n)e[i].clear();
    forr(i,1,m){
        int u,v,w;cin>>u>>v>>w;
        e[u].push_back({v,w});
        if(w>=0)e[v].push_back({u,w});
    }
    cout<<(bellmanford(1)?"YES":"NO")<<endl;
}

SPFA O(nm) queue维护

cpp 复制代码
struct edge{int v,w;};
vector<edge>e[N];
int d[N],vis[N],cnt[N],pre[N];//距离
int n,m;
//O(nm)
bool spfa(int s){
    memset(d,0x3f,sizeof d);
    memset(cnt,0,sizeof cnt);
    memset(vis,0,sizeof vis);
    memset(pre,0,sizeof pre);
    queue<int>q;
    d[s]=0,vis[s]=1;
    q.push(s);
    while(q.size()){
        int now=q.front();q.pop();
        vis[now]=0;//出队 可反复进队出队
        for(auto ed:e[now]){
            int v=ed.v,w=ed.w;
            if(d[v]>d[now]+w){
                d[v]=d[now]+w;
                cnt[v]=cnt[now]+1;
                pre[v]=now;//记录前驱点
                if(cnt[v]>=n)return true;//这条路上边数有重复点 就有负边
                if(!vis[v])q.push(v),vis[v]=1;//v不在队内 就放进去
            }
        }
    }
    return false;
}
void dfs_path(int now,int s){
    if(now==s){//递归到起点
        cout<<now<<endl;
        return;
    }
    dfs_path(pre[now],s);
    cout<<now<<endl;
}
void solve(){
    cin>>n>>m;
    forr(i,1,n)e[i].clear();
    forr(i,1,m){
        int u,v,w;cin>>u>>v>>w;
        e[u].push_back({v,w});
        if(w>=0)e[v].push_back({u,w});
    }
    cout<<(spfa(1)?"YES":"NO")<<endl;
}

多源最短路

Floyd 动态规划思想 O(n3)

使用邻接矩阵

"插点"的思想

路径输出

Johnson 虚拟原点0 O(nmlogm)

一次SPFA,n次HD

P5905

cpp 复制代码
const int N=3e3+10,M=6e3+10,mod=998244353,inf=1e9;
struct edge{int v,w;};
vector<edge>e[N];
int h[N],d[N],vis[N],cnt[N];
int n,m;
void spfa(int s){
    forr(i,0,n)h[i]=inf;
    memset(cnt,0,sizeof cnt);
    memset(vis,0,sizeof vis);
    queue<int>q;
    h[s]=0,vis[s]=1;
    q.push(s);
    while(q.size()){
        int now=q.front();q.pop();
        vis[now]=0;//出队 可反复进队出队
        for(auto ed:e[now]){
            int v=ed.v,w=ed.w;
            if(h[v]>h[now]+w){
                h[v]=h[now]+w;
                cnt[v]=cnt[now]+1;
                if(cnt[v]>n){
                    cout<<"-1\n";
                    exit(0);
                }
                if(!vis[v])q.push(v),vis[v]=1;//v不在队内 就放进去
            }
        }
    }
}
void dij(int s){
    forr(i,0,n)d[i]=inf;//都初始化为inf 注意now初始为0 d[0]=inf
    memset(vis,0,sizeof vis);
    priority_queue<pair<int,int>>q;
    d[s]=0;q.push(make_pair(0,s));
    while (q.size())
    {
        auto t=q.top();q.pop();
        int now=t.second;

        if(vis[now])continue;//把之前插入优先队列的遍历过的点跳过
        vis[now]=1;

        for(auto ed:e[now]){//用now做跳板更新相邻的边 松弛操作
            int v=ed.v,w=ed.w;
            if(d[v]>d[now]+w){
                d[v]=d[now]+w;
                q.push(make_pair(-d[v],v));//把更新过的点扔进堆里 d[v]越小 -d[v]越大 利用负号构建小根堆
            }
        }
    }
}
void solve(){
    cin>>n>>m;
    forr(i,1,m){
        int u,v,w;cin>>u>>v>>w;
        e[u].push_back({v,w});
    }
    forr(i,1,n){
        e[0].push_back({i,0});
    }
    spfa(0);//h[i]是0到i点的最短路
    forr(i,1,n){
        for(auto &ed:e[i]){
            ed.w+=h[i]-h[ed.v];//修改边权 加头去尾
        }
    }
    forr(i,1,n){
        dij(i);//以每个点为源跑dij d[j]是i到j的最短路
        int ans=0;
        forr(j,1,n){
            if(d[j]==inf)ans+=j*inf;
            else ans+=j*(d[j]+h[j]-h[i]);//还原权值
        }
        cout<<ans<<endl;
    }
}

总结

相关推荐
JuneXcy1 小时前
循环高级(1)
c语言·开发语言·算法
Ka1Yan2 小时前
什么是策略模式?策略模式能带来什么?——策略模式深度解析:从概念本质到Java实战的全维度指南
java·开发语言·数据结构·算法·面试·bash·策略模式
绝无仅有3 小时前
Go Timer 面试指南:常见问题及答案解析
后端·算法·架构
地平线开发者4 小时前
开发者说|H-RDT:基于人类操作数据的跨本体机器人学习
算法·自动驾驶
biuyyyxxx5 小时前
Excel数组学习笔记
笔记·学习·算法
南莺莺6 小时前
//Q是一个队列,S是一个空栈,实现将队列中的元素逆置的算法。
数据结构·算法·链表·
闻缺陷则喜何志丹6 小时前
【分治法 BFS 质因数分解】P12255 [蓝桥杯 2024 国 Java B] 园丁|普及+
c++·算法·蓝桥杯·宽度优先·质因数分解·分治法
寒冬没有雪6 小时前
按对角线进行矩阵排序
c++·算法
Huangichin6 小时前
C++基础算法——贪心算法
c++·算法·贪心算法
sssvangen6 小时前
宝石组合(蓝桥杯)
算法·蓝桥杯·调和计数法