• 图论专题笔记


    写在前面

    目录

    一、最短路

    二、最小生成树

    三、树的直径与最近公共祖先

    四、基环树

    五、负环与差分约束

    六、Tarjan算法与无向图连通性

    七、Tarjan算法与有向图连通性

    八、二分图的匹配

    九、二分图的覆盖与独立集

    十、网络流初步

    题目完成进度

    0/10

     


     

    一、最短路

    单源最短路径

    以下皆默认1号节点为起点

    $dijkstra$算法

    算法流程如下:

    1.初始化$dist[1]=0$,其余节点的$dist$值为无穷大

    2.找出一个未被标记的,$dist[x]$最小的节点$x$,然后标记节点$x$

    3.扫描节点$x$的所有出边$(x,y,z)$,$dist[y]=min(dist[y],dist[x]+z)$

    4.重复2、3两步,直到所有节点都被标记

    $dijkstra$算法基于贪心思想,只适用于所有边的长度都是非负数的边

    算法复杂度为$O(n^2)$,如果用二叉堆对$dist$数组进行维护,用$O(log n)$的时间获取最小值并从堆中删除,用$O(log n)$的时间执行一条边的扩展和更新,最终可在$O(mlog n)$的时间内实现$dijkstra$算法。

    $Bellman-Ford$算法和$SPFA$算法

    若对于图中的某一条边$(x,y,z)$,有$dist[y]le dist[x]+z$,则称该边满足三角形不等式。若所有边都满足三角形不等式,则$dist$数组就是所求最短路。

    首先说一下基于迭代思想的$Bellman-Ford$算法,算法流程如下:

    1.扫描所有边$(x,y,z)$,若$dist[y]le dist[x]+z$,则用$dist[x]+z$更新$dist[y]$

    2.重复上述步骤,知道没有更新操作发生。

    时间复杂度为$O(nm)$

    $SPFA$算法实际上是“队列优化的$Bellman-Ford$算法”,算法流程如下:

    1.建立一个队列,最初队列中只含有起点1

    2.取出队头节点$x$,扫描所有出边$(x,y,z)$,若可以更新则更新,同时如果$y$不在队列中,则把$y$入队

    3.重复第2步,直到队列为空

    在任意时刻,队列中都保存了待扩展的节点,每次入队相当于完成了一次$dist$数组的更新操作,使其满足三角形不等式。一个节点可能会入队、出队多次。最终,图中节点收敛到全部满足三角形不等式的状态。这个队列避免了$Bellman-Ford$算法中对不需要扩展的节点的冗余扫描,在稀疏图上运行效率较高,为$O(km)$级别,其中$k$是一个小常数。但在稠密图或特殊构造的网格图上,该算法仍可能退化为$O(nm)$。(所以$SPFA$容易被卡)

    $Bellman-Ford$算法和$SPFA$算法在有长度为负数的边的图中也能正常工作,只不过时间复杂度会进一步增加。有一个$SLF$优化策略,基于双端队列的思想,在每次更新$dist[y]$之后,把$dist[y]$与当前队头节点(是$x$出队后,队头的那个节点)的$dist$值进行比较。若$dist[y]$更小,则从队头把$y$入队,否则仍从队尾入队。

    如果图中不存在长度为负数的边,那么类似于优先队列$bfs$,我们也可以用二叉堆对$SPFA$算法进行优化,堆代替了一般的队列,用于保存待扩展的节点,每次取出“当前距离最小”的节点(堆顶)进行扩展,节点第一次从堆中被取出时,就得到了该点的最短路。与堆优化$dijkstra$算法的流程一致,这两种做法的思想殊途同归,都是非负权图上$O(mlog n)$的单源最短路径算法。

    例题——

    poj3662 Telephone Lines

    Luogu P1073 最优贸易

    bzoj2200 道路与航线

    任意两点间的最短路

    我们可以把每个点作为起点,求解$n$次单源最短路问题。不过在任意两点间最短路问题中,图一般比较稠密。这里讲一讲$O(N^3)$的$Floyd$算法。

    $Floyd$算法

    咕咕咕咕

    传递闭包

    在交际网络中,给定若干个元素和若干对二元关系,且关系具有传递性。“通过传递性推导出尽量多的元素之间的关系”的问题被称为传递闭包。

    建立临接矩阵$d$,$d[i][j]=1$表示$i$与$j$有关系,$d[i][j]=0$表示$i$与$j$没有关系,特别的,$d[i][i]$始终为1。使用$Floyd$算法可以解决传递闭包问题

     

    bool d[N][N];
    int n,m;
    int main(){
        cin>>n>>m;
        for(int i=1;i<=n;i++) d[i][i]=1;
        for(int i=1;i<=m;i++){
            int x,y;
            cin>>x>>y;
            d[x][y]=d[y][x]=1;
        }
        for(int k=1;k<=n;k++)
            for(int i=1;i<=n;i++)
                for(int j=1;j<=n;j++)
                    d[i][j]|=d[i][k]&d[k][j];
        return 0;
    }

     

    例题——

    poj1094 Sorting It All Out

    poj1734 Sightseeing trip

    poj3613 Cow Relays

    go back

     


     

    二、最小生成树

    昂讲一下$Kruskal$算法和$Prim$算法

    $Kruskal$算法

    在任意时刻,$Kruskal$算法从剩余的边中选出一条权值最小的,并且这条边的两个端点不连通,图中节点的连通情况可以用并查集维护。算法流程如下:

    1.建立并查集,每个点各自构成一个集合

    2.把所有边按权值从小到大排序,依次扫描每条边$(x,y,z)$

    3.若$x,y$属于同一集合(连通),则忽略这条边,继续扫描下一条

    4.否则,合并$x,y$所在的集合,并把$z$累加到答案中

    5.所有边扫描完成后,第4步中处理过的边就构成最小生成树

    时间复杂度为$O(mlog m)$

    $Prim$算法

    最初,$Prim$算法仅确定1号节点属于最小生成树。在任意时刻,设已经确定属于最小生成树的节点集合$T$,剩余节点集合为$S$。$Prim$算法找到$z$最小的边$(x,y,z)$满足$xin T,yin S$,然后把$y$从集合$S$中删除,加入到集合$T$,并把$z$累加到答案中。

    具体来说,可以维护数组$d$:若$xin S$,则$d[x]$表示节点$x$与集合$T$中的接地那之间权值最小边的权值。若$xin T$,则$d[x]$表示就等于$x$被加入$T$时选出的最小边的权值。

    类比$dijkstra$算法,同一个数组标记节点是否属于$T$。每次从未标记的节点中选出$d$值最小的,将其标记(新加入$T$),同时扫描所有出边,更新另一个端点的$d$值。最后,最小生成树的权值总和就是$sum_{i=2}^{n}d[i]$

    算法的时间复杂度为$O(n^2)$,可以用二叉堆优化到$O(mlog n)$,$Prim$算法主要用于稠密图,尤其是完全图的最小生成树的求解。

    int a[N][N],d[N],n,m,ans;
    bool v[N];
    void prim(){
        memset(d,0x3f,sizeof(d));
        memset(v,0,sizeof(v));
        d[1]=0;
        for(int i=1;i<n;i++){
            int x=0;
            for(int j=1;j<=n;j++)
                if(!v[j]&&(x==0||d[j]<d[x])) x=j;
            v[x]=1;
            for(int j=1;j<=n;j++)
                if(!v[j]) d[j]=min(d[j],a[x][j]);
        }
    }
    int main(){
        cin>>n>>m;
        memset(a,0x3f,sizeof(a));
        for(int i=1;i<=n;i++) a[i][i]=0;
        for(int i=1;i<=m;i++){
            int x,y,z;
            cin>>x>>y>>z;
            a[y][x]=a[x][y]=min(a[x][y],z);
        }
        prim();
        for(int i=2;i<=b;i++) ans+=d[i];
        cout<<ans<<endl;
        return 0;
    }

    例题——

    TUVJ1391 走廊泼水节

    poj1639 Picnic Planning

    poj2728 最优比率生成树

    黑暗城堡

    go back

     


     

    三、树的直径与最近公共祖先

     

    go back

     


     

    四、基环树

     

    go back

     


     

    五、负环与差分约束

     

    go back

     


     

    六、Tarjan算法与无向图连通性

    无向图的割点与桥

    给定无向连通图$G=(V,E)$

     

     

    go back

     


     

    七、Tarjan算法与有向图连通性

     

    go back

     


     

    八、二分图的匹配

     

    go back

     


     

    九、二分图的覆盖与独立集

     

    go back

     


     

    十、网络流初步

     

    go back

  • 相关阅读:
    shiro整合springmvc
    HashMap中的位运算
    jedis的scan操作要注意cursor数据类型
    DispatcherServlet的url-pattern尽量不要配置为"/*"
    解决阿里云ECS下kubeadm部署k8s无法指定公网IP(作废)
    win10下使用mklink命令给C盘软件搬家
    Ubuntu管理软件源
    C++ 自增、自减运算符的重载和性能分析
    C++ 流插入"<<"和流提取">>"运算符的重载
    C++ 手把手教你实现可变长的数组
  • 原文地址:https://www.cnblogs.com/THWZF/p/10372005.html
Copyright © 2020-2023  润新知