最短路

最短路

单源最短路:

边权为正:

朴素版 \(dijkstra\)

复杂度 \(O(n^2)\)

思想:

循环 \(n\) 次,每次找到还没有被标记过的 \(dist\) 值最小的节点 \(u\)

\(u\) 来更新其他节点的 \(dist\) :

\(dist[j]=\min(dist[j],dist[u]+g[u][j])\)

标记节点 \(u\)

int dijkstra()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;//初始化

    for(int i=0;i<n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
            if(!st[j]&&(t==-1||dist[t]>dist[j]))
            t=j;

        st[t]=true;

        for(int j=1;j<=n;j++)
        dist[j]=min(dist[j],dist[t]+g[t][j]);
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    else return dist[n];
}

堆优化版 \(dijkstra\)

复杂度 \(O(m \log n)\)

将朴素版中“找到还没有被标记过的 \(dist\) 值最小的节点 \(u\) ”的操作堆优化

int dijkstra()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    priority_queue<PII,vector<PII>,greater<PII> > heap;//小根堆
    heap.push({0,1});

    while(heap.size())
    {
        PII 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]>dist[ver]+w[i])
            {
                dist[j]=dist[ver]+w[i];
                heap.push({dist[j],j});//如果被更新就加入队列中
            }
        }
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    else return dist[n];
}

边权为负:

\(dijkstra\) 不能处理负边权

spfa

spfa求最短路

复杂度: 最坏 \(O(VE)\)

思路:

保证队列中无重复元素

每次用被更新过的节点去更新其他节点

int spfa()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    queue<int>q;
    q.push(1);
    st[1]=true;//入队
    while(q.size()){
        int u=q.front();
        q.pop();
        st[u]=false;//出队,删除标记
        for(int i=h[u];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[u]+w[i]){
                dist[j]=dist[u]+w[i];
                if(!st[j]){//如果队列中没有该节点,且该节点被更新过,则入队
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return dist[n];
}

spfa 判断负环

\(cnt[j]\) : 从 \(1\)\(j\) 的最短路经过的点数(除节点 \(1\) 外)

如果 \(cnt[j] >=n\) : 最短路一定至少重复经过一个点,说明存在环。因为其满足最短路,所以环一定是负环

如果

bool spfa()
{
    queue<int>q;
    for(int i=1;i<=n;i++){
        q.push(i);
        st[i]=true;// 图可能不连通
    }
    while(q.size()){
        int u=q.front();
        q.pop();
        st[u]=false;
        for(int i=h[u];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[u]+w[i]){
                dist[j]=dist[u]+w[i];
                cnt[j]=cnt[u]+1;//转移cnt
                if(cnt[j]>=n)return true;
                if(!st[j]){
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return false;
}

有边数限制的最短路(bellman_ford)

\(1\) 号点到 \(n\) 号点的最多经过 \(k\) 条边的最短距离

可能存在负环

复杂度: \(O(km)\)

循环 \(k\) 次,每次用每条边更新 \(dist\)

void bellman_ford()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;

    for(int i=0;i<k;i++)
    {
        memcpy(backup,dist,sizeof dist);//要用前一转态更新,先保存前一转态(避免单次重复更新)

        for(int j=0;j<m;j++)
        {
            int a=edge[j].a,b=edge[j].b,w=edge[j].w;
            if(dist[b]>backup[a]+w) dist[b]=backup[a]+w;
        }
    }
}

多源最短路floyd

复杂度 \(O(n^3)\)

用二维数组存图,注意重边的影响。

void floyd()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++){
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
}
posted @ 2023-10-12 21:58  lza0v0  阅读(51)  评论(0)    收藏  举报