【总结】最短路/最小生成树

  • 前言
  • 最短路
    • Floyd
    • Djkstra
    • SPFA
  • 最小生成树(MST)
    • kruskal
    • Prim
    • Boruvka

前言

    本蒟蒻在考试因为最短路写丑正解写TLE之后,突然发现自己根本不了解最短路QWQ。于是有了这篇博客。下面简单讲一下每种算法和一些例题。


最短路

Floyd

    一种看似鸡肋实则巧妙且极其有用的 O(N3) O ( N 3 ) 算法。
    采用邻接表形式,关键在于,对图上某两点之间距离,枚举图上每一个点(包括这两个点)作为中转站更新最短距离。
    适用于:求连通图上任意两点之间的最短距离。有环有负边权都可以。
    代码:

inline void floyd()
    for(int k=1;k<=n;k++)
     for(int i=1;i<=n;i++)
      for(int j=1;jif(dis[i][k]+dis[k][j]//注意:枚举中转站的循环要在最外层
//枚举的k超过i的时候这个dis还不满足最小

    例题:NOIP2016-换教室 & 题解


Djkstra

    Djkstra是一种求单源最短路的贪心(动态规划)法,运用了集合的思想,我们先将源点加入这个空集合,然后每次不断加入元素,每一次加入元素的时候,我们选择离这个集合(包括集合里的所有元素)的最近的点,这样保证了每次都得到了这个元素离源点的最近距离。因为如果对于这个结点有更短距离,它应该在前几次操作中就被加入了(所以可以发现Djkstra的过程中dis是递增的)。
    用邻接矩阵是 O(N2) O ( N 2 ) 的,一般用优先队列优化(今天就是这个打T了)。应该可以处理有环图和负边权的情况。
    具体代码以后可能会贴。毕竟我现在还没有充分了解这个算法。


SPFA

    SPFA是一种无法证明复杂度的玄学求单源最短路算法。运用BFS(队列)的手段,先将源点加入空队列。不断弹出队首,更新队首连接的元素的距离(指的是离源点的距离),如果某个元素恰能被更新,且不在队列中,就把它加进队列。
    由于复杂度玄学,所以姿势♂对了,一般都跑得很快,而且代码也好打,是求单源最短路的最佳选择,但SPFA不支持有负边权环的图。
    代码如下:

inline void SPFA(){
dis[s]=0;Q[1]=s;vis[s]=0;//s->源点
int l=0,r=1;
    while(lint x=Q[++l];
        for(R int i=head[x];i;i=nxt[i]){
            int e=to[i];
              if(dis[e]>dis[x]+w[i]){
                dis[e]=dis[x]+w[i];
                if(!vis[e]){
                    vis[e]=1;
                    Q[++r]=e;
                }
              } 
        }
        vis[x]=0;
    }
}
//用的链式前向星

最小生成树(MST)

kruskal

    先将边权按从小倒大排,用并查集路径压缩实现。我们从边权小的开始考虑,若该边连接的两元素不在一个并查集内,就把它们合并,答案加上这条边权。这样操作直到所有元素在同一个并查集内,此时便得到了最小生成树边权之和。
    时间复杂度在边稀疏的时候比较支持。
    模板:最小生成树
代码:

#include
using namespace std;
const int N=5e3+10;
const int M=1e7+10;
typedef long long ll;

int n,m,f[N],sum=1;
ll ans=0;
struct P{
    int fr,to,w;
    bool operator <(const P& u)const{
      return winline int read(){
    char ch=getchar();int x=0;
    while(ch<'0' || ch>'9') ch=getchar();
    while(ch>='0' && ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x;
}

inline int F(int x)
{
    return x==f[x]? x:f[x]=F(f[x]);
}

int main(){
     n=read();m=read();
     for(int i=1;i<=n;i++) f[i]=i;
     for(int i=0;ifor(int i=0;iint u=t[i].fr,v=t[i].to;
        int uu=F(u),vv=F(v);
        if(uu==vv) continue;
        else{
            f[uu]=vv;ans+=t[i].w;sum++;
        }
     }
     if(sumprintf("orz\n");
     else 
     printf("%lld\n",ans);
     return 0;
}

Prim

    Prim算法类似于Djkstra的思想,我们以集合操作。将树根加入空集合中,每次枚举这个集合向外连接的最小边权值,把这个边连接的集合外的点加入集合。不断操作直到所有结点都在这个集合中。
    Prim比较支持点少边多的情况。
    也可能以后贴代码。


Boruvka

    本蒟蒻也不清楚,好像是Prim和Kruskal的结合版。
    贴篇讲解: [Boruvka算法 曼哈顿距离最大生成树] 省选模拟赛 4 C. 树树树 mst
    (虽然好像没什么用)

完结撒花~~~

你可能感兴趣的:(最短路,最小生成树)