056.最短路算法

链式前向星

  • 空间复杂度 O(N + M)
const int N=1e5+5;
const int M=1e5+5;//无向图要用2倍的M

int tot;
int head[N];
int nex[M];
int to[M];
int weight[M];

void built(int n){
    tot=1;
    for(int i=1;i<=n;++i){
        head[i]=0;
    }
}
void addEdge(int u,int v,int w){
    nex[tot]=head[u];
    to[tot]=v;
    weight[tot]=w;
    head[u]=tot++;
}
//遍历
for(int ei=head[u];ei;ei=nex[ei]){
    int v=to[ei];
    int w=weight[ei];
}

Floyd

  • 得到图中任意两点的最点距离

  • 可处理负边,不能处理负环

  • 时间复杂度 O(N ^ 3) (N为节点数)

  • 能处理的数据规模小,一般用邻接矩阵

模板

luogu P2910

const int N=105;
const int INF=0x3f3f3f3f;

int dis[N][N];

void built(int n){
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            dis[i][j]=INF;
        }
    }
}
void floyd(int n){
    for(int k=1;k<=n;++k){
        for(int i=1;i<=n;++i){
            for(int j=1;j<=n;++j){
                if(dis[i][k]!=INF&&dis[k][j]!=INF&&dis[i][j]>dis[i][k]+dis[k][j]){
                    dis[i][j]=dis[i][k]+dis[k][j];
                }
            }
        }
    }
}

Dijkstra

  • 给定起点,得到从起点到每个点的最短距离

  • 不能处理负边

模板

luogu P4779

普通堆实现

  • 时间复杂度 O(M * logM) (M为边数)
const int N=1e5+5;
const int INF=0x3f3f3f3f;

struct st{
    int cur;
    int cost;
};
struct e{
    int v,w;
};
vector<e>gra[N];
bool vis[N];
int dis[N];

void built(int n){
    for(int i=1;i<=n;++i){
        gra[i].clear();
        dis[i]=INF;
        vis[i]=0;
    }
}
void dijkstra(int s){
    auto cmp=[](st a,st b){return a.cost>b.cost;};
    priority_queue<st,vector<st>,decltype(cmp)>pq(cmp);
    dis[s]=0;
    pq.push({s,0});
    while(pq.size()){
        auto [u,c]=pq.top();
        pq.pop();
        if(vis[u]==0){
            vis[u]=1;
            for(auto [v,w]:gra[u]){
                int nc=c+w;
                if(dis[v]>nc){
                    dis[v]=nc;
                    pq.push({v,nc});
                }
            }
        }
    }
}

反向索引堆实现

  • 时间复杂度 O(M * logN) (M为边数,N为节点数)
const int N=1e5+5;
const int M=2e5+5;
const int INF=0x3f3f3f3f;
int dis[N];

//链式前向星建图
int tot;
int head[N];
int nex[M];
int to[M];
int val[M];

//手写堆
int heap[N];
int siz;
int tag[N];
//tag=-1 : 没有进过堆 (vis=0)
//tag=-2 : 已出堆结算 (vis=1)
//tag>=0 : 反向索引,指出在堆里的位置

void built(int n){
    tot=1;
    siz=0;
    for(int i=1;i<=n;++i){
        dis[i]=INF;
        head[i]=0;
        tag[i]=-1;
    }
}
void addEdge(int u,int v,int w){
    nex[tot]=head[u];
    to[tot]=v;
    val[tot]=w;
    head[u]=tot++;
}
void swap(int i,int j){
    int t=heap[i];
    heap[i]=heap[j];
    heap[j]=t;
    tag[heap[i]]=i;
    tag[heap[j]]=j;
}
void up(int i){
    while(dis[heap[(i-1)/2]]>dis[heap[i]]){
        swap(i,(i-1)/2);
        i=(i-1)/2;
    }
}
void down(int i){
    int l=i*2+1;
    while(l<siz){
        int best=l+1<siz&&dis[heap[l+1]]<dis[heap[l]]?l+1:l;
        if(dis[heap[i]]<=dis[heap[best]])return;
        swap(best,i);
        i=best;
        l=2*i+1;
    }
}
int pop(){
    int u=heap[0];
    swap(0,--siz);
    down(0);
    tag[u]=-2;
    return u;
}
void push(int v,int c){
    if(tag[v]==-2)return;
    if(tag[v]==-1){
        heap[siz]=v;
        tag[v]=siz++;
        dis[v]=c;
    }
    else dis[v]=min(dis[v],c);
    up(tag[v]);
}
void dijkstra(int s){
    push(s,0);
    while(siz){
        int u=pop();
        for(int ei=head[u];ei;ei=nex[ei]){
            push(to[ei],dis[u]+val[ei]);
        }
    }
}

Astar

  • 给定起点,给定终点,求两点间最短距离

  • Dijkstra + 预估函数 实现启发式搜索

  • Astar需要允许走回头路,相比Dijkstra删除vis[]机制

  • 在堆中根据 从起点到当前点的距离 + 从当前点到终点的预估距离 进行排序

  • 常用于网格图寻路,对实现预估函数友好

  • 常用于抽象状态压缩

  • 启发性算法讨论复杂度意义不大

预估函数

  1. 要求 当前点到终点的预估距离 <= 当前点到终点的实际最短距离

  2. 满足1的情况下,预估距离与实际距离越接近,启发性越好

常用预估方法(二维矩阵为例)

  • 曼哈顿距离
int f1(int i,int j,int x,int y){
    return abs(x-i)+abs(y-j);
}
  • 欧氏距离
//向下取整
int f2(int i,int j,int x,int y){
    return sqrt((x-i)*(x-i)+(y-j)*(y-j));
}
  • 对角线距离
int f3(int i,int j,int x,int y){
    return max(abs(x-i),abs(y-j));
}

例题:高尔夫砍树

leercode 675

class Solution {
    int n,m;
    struct tree{
        int h,x,y;
    };
    struct st{
        int x,y,cost,key;
    };
    vector<int>mo{-1,0,1,0,-1};
    int f(int i,int j,int x,int y){
        return abs(x-i)+abs(y-j);
    }
    int Astar(int sx,int sy,int ex,int ey,vector<vector<int>>& g){
        auto cmp=[](st a,st b){return a.key>b.key;};
        priority_queue<st,vector<st>,decltype(cmp)>pq(cmp);
        vector<vector<int>>dis(n,vector<int>(m,0x3f3f3f3f));
        dis[sx][sy]=0;
        pq.push({sx,sy,0,0+f(sx,sy,ex,ey)});
        while(pq.size()){
            auto [x,y,c,k]=pq.top();
            pq.pop();
            if(x==ex&&y==ey)return c;
            for(int i=0;i<4;++i){
                int nx=x+mo[i];
                int ny=y+mo[i+1];
                if(nx<0||ny<0||nx>=n||ny>=m||g[nx][ny]==0)continue;
                int nc=c+1;
                if(dis[nx][ny]>nc){
                    dis[nx][ny]=nc;
                    int nk=nc+f(nx,ny,ex,ey);
                    pq.push({nx,ny,nc,nk});
                }
            }
        }
        return -1;
    }
public:
    int cutOffTree(vector<vector<int>>& g) {
        n=g.size();
        m=g[0].size();
        vector<tree>trees;
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                if(g[i][j]>1){
                    trees.push_back({g[i][j],i,j});
                }
            }
        }
        sort(trees.begin(),trees.end(),[](tree a,tree b){return a.h<b.h;});
        int ans=0;
        int i=0,j=0;
        for(auto [_,x,y]:trees){
            int t=Astar(i,j,x,y,g);
            if(t==-1)return -1;
            ans+=t;
            i=x;
            j=y;
        }
        return ans;
    }
};

Bellman-Frod 与 SPFA

  • 给定起点,得到从起点到每个点的最短距离

  • 可以处理负边,可以检查负环

模板

luogu P3371

Bellman-Frod

  • 时间复杂度 O(M * N) (M为边数,N为节点数)
const int N=10005;
const int INF=0x3f3f3f3f;

struct e{
    int v,w;
};
vector<e>gra[N];
int dis[N];

void built(int n){
    for(int i=1;i<=n;++i){
        gra[i].clear();
        dis[i]=INF;
    }
}
void bellman_frod(int s,int n){
    dis[s]=0;
    for(int i=1;i<n;++i){
        for(int u=1;u<=n;++u){
            if(dis[u]==INF)continue;
            for(auto [v,w]:gra[u]){
                if(dis[v]>dis[u]+w){
                    dis[v]=dis[u]+w;
                }
            }
        }
    }
}

SPFA

  • 时间复杂度还是 O(M * N) 只有常数优化,但效果明显

  • 用于费用流

const int N=10005;
const int INF=0x3f3f3f3f;

struct e{
    int v,w;
};
vector<e>gra[N];
int dis[N];
bool in[N];

void built(int n){
    for(int i=1;i<=n;++i){
        gra[i].clear();
        in[i]=0;
        dis[i]=INF;
    }
}
void spfa(int s){
    queue<int>q;
    dis[s]=0;
    q.push(s);
    in[s]=1;
    while(q.size()){
        int u=q.front();
        q.pop();
        in[u]=0;
        for(auto [v,w]:gra[u]){
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                if(in[v]==0){
                    q.push(v);
                    in[v]=1;
                }
            }
        }
    }
}

检查负环

luogu P3385

一共有 n 个节点,若无负环,每个节点最多松弛 n-1

添加一个 cnt[] 用来记数,记录每个节点松弛了多少次

若发现某个节点松弛次数大于 n-1 ,说明有负环

若规定从唯一起点开始,直接跑spfa即可

若要检查整张图有无负环,添加一个虚拟起点s

这个起点s连接所有的其它节点,边权为0

//记录每个节点的松弛次数
int cnt[N];
//有负环返回 1 ,否则返回 0 
bool spfa(int s,int n){
    queue<int>q;
    q.push(s);
    in[s]=1;
    cnt[s]=1;
    dis[s]=0;
    while(q.size()){
        int u=q.front();
        q.pop();
        in[u]=0;
        for(auto [w,v]:gra[u]){
            if(dis[v]>dis[u]+w){
                if(cnt[v]++==n)return 1;
                dis[v]=dis[u]+w;
                if(!in[v]){
                    q.push(v);
                    in[v]=1;
                }
            }
        }
    }
    return 0;
}
posted @ 2026-01-21 16:38  射杀百头  阅读(1)  评论(0)    收藏  举报