最短路

Bellman-Ford
最短路中一定不含环(因为含有负环的最短路不存在,零环和正环可以除去),所以从起点到终点的最短路经过的边数不超过\(n-1\),所以一定可以通过\(n-1\)轮松弛得到最短路,每一轮松弛用所有边进行更新
如果第\(n\)次松弛依然有起点到某个顶点的最短路被更新,说明从起点可以到达一个负环,最短路不存在
可以增加一个优化,如果在某轮松弛中起点到任何顶点的最短路都没有被更新,那么不需要进行下一轮松弛,直接退出
时间复杂度\(O(nm)\),其中\(n\)为点数,\(m\)为边数

const int inf=0x3f,maxn=110,maxm=10010;
int n,m,dis[maxn];

struct Edge{
    int u,v,w;
}edge[maxm];

void Bellman_Ford(int s){
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    int check;
    for(int i=1;i<=n;i++){
        check=0;
        for(int j=1;j<=m;j++){
            int u=edge[j].u,v=edge[j].v,w=edge[j].w;
            if(dis[v]>dis[u]+w){
                dis[v]=min(dis[v],dis[u]+w);
                check=1;
            }
        }
        if(!check) break;
    }
}

SPFA
SPFA是经过队列优化的Bellman-Ford算法,在Bellman-Ford的某轮松弛中,如果起点到某个顶点的距离没有被更新,那么在下一轮中,就不需要从这个顶点出发去松弛其他顶点了
所以通过一个先进先出的队列来保存松弛过的顶点,每次取出队首顶点\(u\),并且枚举从\(u\)出发的所有边\((u, v)\),如果\(dis[u]+w(u,v)<dis[v]\),则更新\(dis[v]\),然后判断顶点\(v\)是否已经在队列中,如果不在就将顶点\(v\)插入队尾。这样不断从队列中取出队首顶点来进行松弛操作,直至队列为空
任意顶点被更新的次数一定小于\(n\),如果某个顶点第\(n\)次被更新,则说明从起点到终点的路径中存在负环
SPFA算法在最坏的情况下,时间复杂度和Bellman-Ford算法一样,为\(O(nm)\)。但是一般不会达到这个上界,一般的期望时间复杂度为\(O(km)\),其中\(k\)为常数

const int inf=0x3f3f3f3f;
int dis[maxn];
bool inq[maxn];
queue<int> q;

void spfa(int s){
    for(int i=1;i<=n;i++){
        dis[i]=(i==s)?0:inf;
        inq[i]=(i==s);
    }
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        inq[u]=false;
        for(int i=head[u];~i;i=nxt[i]){
            int v=to[i];
            int w=weight[i];
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                if(!inq[v]){
                    q.push(v);
                    inq[v]=true;
                }
            }
        }
    }
}

Dijkstra
Dijkstra算法每次从最短距离已经确定的顶点开始松弛,最短距离已经确定的点就是当前\(dis[i]\)最小的点
如果如中存在负边权,则无法确定某个顶点是否已经取到最短距离,所以Dijkstra算法无法处理含有负边权的图
每次通过遍历所有顶点找到\(dis[i]\)最小的顶点,时间复杂度\(O(n^2+m)\)

const int maxn=110,inf=0x3f3f3f3f;
int dis[maxn],book[maxn];

void Dijkstra(int s){
    for(int i=1;i<=n;i++) dis[i]=(i==s)?0:inf;
    memset(book,0,sizeof(book));
    book[s]=1;
    for(int i=head[s];~i;i=nxt[i]){
        int v=to[i],w=weight[i];
        dis[v]=min(dis[v],dis[s]+w);
    }
    for(int k=1;k<n;k++){
        int m=inf,id;
        for(int i=1;i<=n;i++){
            if(!book[i]){
                if(m>dis[i]){
                    m=dis[i];
                    id=i;
                }
            }
        }
        book[id]=1;
        for(int i=head[id];~i;i=nxt[i]){
            int v=to[i],w=weight[i];
            dis[v]=min(dis[v],dis[id]+w);
        }
    }
}

Dijkstra堆优化
在查找\(dis[i]\)最小的顶点时,可以使用优先队列优化
时间复杂度\(O(m\log n)\)

const int maxn=110,inf=0x3f3f3f3f;
int dis[maxn],book[maxn];

void Dijkstra(int s){
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    memset(book,0,sizeof(book));
    priority_queue<PII> pq;
    pq.push({0,s});
    while(!pq.empty()){
        PII p=pq.top();
        pq.pop();
        int u=p.second;
        if(book[u]) continue;
        book[u]=1;
        for(int i=head[u];~i;i=nxt[i]){
            int v=to[i];
            int w=weight[i];
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                pq.push({-dis[v],v});
            }
        }
    }
}

不使用book数组,直接判断当前从优先队列中取出的点是否已经无用

const int maxn=110,inf=0x3f3f3f3f;
int dis[maxn];

void Dijkstra(int s){
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    priority_queue<PII> pq;
    pq.push({0,s});
    while(!pq.empty()){
        PII p=pq.top();
        pq.pop();
        int d=p.first;
        int u=p.second;
        if(dis[u]<d) continue;
        for(int i=head[u];~i;i=nxt[i]){
            int v=to[i];
            int w=weight[i];
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                pq.push({-dis[v],v});
            }
        }
    }
}

Floyd
计算任意两点间的最短路,基于动态规划,设\(dis[k][i][j]\)表示只允许经过\([1,k]\)中转的情况下,\(i\)\(j\)之间的最短路。所以有两种情况:
1.如果最短路经过\(k\),则\(dis[k][i][j]=dis[k-1][i][k]+dis[k-1][k][j]\)
2.如果最短路不经过\(k\),则\(dis[k][i][j]=dis[k-1][i][j]\)
于是有状态转移方程:\(dis[k][i][j]=min(dis[k-1][i][j],dis[k-1][i][k]+dis[k-1][k][j])\)
时间复杂度\(O(n^3)\),滚动数组优化之后,空间复杂度\(O(n^2)\)

const int maxn=110;
int dis[maxn][maxn];

void Floyd(){
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==j || i==k || k==j) continue;
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
            }
        }
    }
}
posted @ 2020-08-23 20:21  fxq1304  阅读(96)  评论(0编辑  收藏  举报