最短路径问题

多源最短路

\(floyd\)

\(floyd\) 是一种用 \(dp\) 的思想求多源最短路的方法

定义

\(f_{i,j,k}\) -> 表示以 \(i\) 为起点, \(j\) 为终点,只能够经过前 \(k\) 个点的最短路

实现

\(f_{i,j,k}=min\{f_{i,k,k-1}+f_{k,j,k-1}\}\)
时间复杂度 \(O(n^3)\),空间复杂度 \(O(n^3)\)

for (int k=1;k<=n;k++)
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            f[i][j][k]=min(f[i][j][k],f[i][k][k-1]+f[k][j][k-1]);

应为 \(f_{i,j,k}\) 只和 \(f_{i,k,k-1},f_{k,j,k-1}\) 有关,所以可以把第三维去掉
时间复杂度 \(O(n^3)\),空间复杂度 \(O(n^2)\)

for (int k=1;k<=n;k++)
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

小结

\(floyd\) 支持负边权,写起来很方便,是一个不错的求解多源最短路的方法

单源最短路

\(dijkstra\)

\(dijkstra\) 是一种利用贪心求解的单元最短路的算法,不能求负权

定义

\(s\) -> 起点

\(dis_i\) -> 从 \(s\)\(i\) 的最短路

\(vis_i\) -> 点 \(i\) 是否已经拓展过

实现

一开始考虑拓展 \(s\), 寻找 \(s\) 最近的点 \(k_0\) 的最短路,将 \(k0\) 扩展,找到 \(s\)\(k_0\) 能够到达的最近的点 \(k1\),将 \(k_1\) 扩展,寻找能够到达的最近的点 \(k_2\),扩展,寻找,总共 \(n-1\) 次,时间复杂度 \(O(n^2+e)\)

void dijkstra(int s){
    for (int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
    dis[s]=0;
    for (int it=1;it<=n;it++){
        int mind=inf,p=-1;
        for (int i=1;i<=n;i++) if (!vis[i]&&dis[i]<mind) mind=dis[i],p=i;
        vis[p]=1;
        for (int i=0;i<e[p].size();i++){
            int v=e[p][i].first,w=e[p][i].second;
            if (dis[v]>dis[p]+w) dis[v]=dis[p]+w;
        }
    }
}

对于稀疏图,\(dijkstra\) 还有堆优化,每一次向小根堆中,如果一个节点被更新了,就删除原先节点的位置,然后插入更新后的该节点,但因为遍历没一条边的的时候更新是 \(O(elogn)\),所以总的时间复杂度就是 \(O((n+m)logn)\)

void dijkstra(){
    for (int i=1;i<=n;i++) dis[i]=INF,vis[i]=0;
    dis[s]=0; S.insert(make_pair(dis[s],s));
    for (int i=1;i<=n;i++){
        int u=S.begin()->second; S.erase(make_pair(dis[u],u)); vis[u]=1;
        for (vector <pii> :: iterator j=g[u].begin();j!=g[u].end();j++){
            int v=j->first,w=j->second;
            if (dis[u]+w<dis[v]){
                S.erase(make_pair(dis[v],v));
                dis[v]=dis[u]+w;
                S.insert(make_pair(dis[v],v));
            }
        }
    }
}

小结

\(dijkstra\) 是一个稳定的求最短路的方法,唯一的缺点就是不能求负权

\(spfa\)

\(spfa\) 是一种与利用队列求最短路的方法,可以求负权

定义

\(s\) -> 起点

\(Q\) -> 循环队列

\(dis_i\) -> 从 \(s\)\(i\) 的最短路

\(vis_i\) -> 点 \(i\) 在不在队列中

实现

\(s\) 进队,通过 \(s\) 拓展,将相邻的所有的 \(dis\) 更新,入队,进行松弛操作,时间复杂度 \(O(ke)\),\(k\) 是常数,大约在 \(2\)\(5\) 左右

void spfa(int s){
    for (int i=1;i<=n;i++) dis[i]=INF,vis[i]=0;
    int H=0,T=1;
    vis[s]=1,dis[s]=0,Q[1]=s;
    while (H!=T){
        int x=Q[++T%=N]; vis[x]=1;
        for (int i=0;i<e[x].size();i++){
            int v=e[x][i].first,w[x][i].second;
            if (dis[x]+w<dis[v]){
                dis[v]=dis[x]+w;
                if (!vis[v]){
                    vis[v]=1,Q[++T%=N]=v;
                    if (dis[Q[T]]<dis[Q[(H+1)%N]])swap(Q[T],Q[(H+1)%MAXN]);
                }
            }
        }
    }
}

小结

\(spfa\) 也是一个比较好的单源最短路的求法,就是不怎么稳定,有些图跑的慢,有的图跑的快,网格图可以卡住 \(spfa\)

posted @ 2018-03-20 17:11 xay5421 阅读(...) 评论(...) 编辑 收藏