zoukankan      html  css  js  c++  java
  • 图论专题笔记

    写在前面

    目录

    一、最短路

    二、最小生成树

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

    四、基环树

    五、负环与差分约束

    六、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

  • 相关阅读:
    webpack基础
    LeetCode232. 用栈实现队列做题笔记
    mysql 时间加减一个月
    leetcode 1381. 设计一个支持增量操作的栈 思路与算法
    LeetCode 141. 环形链表 做题笔记
    leetcode 707. 设计链表 做题笔记
    leetcode 876. 链表的中间结点 做题笔记
    leetcode 143. 重排链表 做题笔记
    leetcode 1365. 有多少小于当前数字的数字 做题笔记
    LeetCode1360. 日期之间隔几天 做题笔记
  • 原文地址:https://www.cnblogs.com/THWZF/p/10372005.html
Copyright © 2011-2022 走看看