zoukankan      html  css  js  c++  java
  • DFS 树

    声明 本文部分内容来自 Codeforces 上的一篇博客,侵删。

    DFS 是一种常见的图遍历方法。

    考虑 无向图 的遍历过程:我们访问一个节点,遍历它的所有相邻节点,如果没有访问则去访问。不难发现每个节点只会被访问一次,也即这些节点和所有访问到的边可以构成一棵树,我们称这棵树为 DFS 树。访问过的边称为生成边(span edge),没有访问的称为后向边(back edge)。

    DFS 树

    仔细观察后向边,我们可以发现一些性质:

    • 每条后向边只会连接祖先和子孙,不会有兄弟相连。

    这个很好证明,如果有一条边连接了兄弟,那么在 DFS 的过程中一定会先访问到其中一个兄弟,然后通过这条边访问另一个兄弟,而不是退回祖先处再去访问。也即这条边一定是生成边。

    • 每条后向边对应一个环,每个环也对应一条后向边。

    证明也是很显然的,通过观察 DFS 树的样子即可完成。

    有了这些性质 DFS 树可以干什么呢?最经典的应用就是 Tarjan 的使用 low 数组寻找割点的算法了。

    我们考虑什么情况下 非根 节点 (u) 是割点:

    • 当且仅当存在一个儿子,使得儿子节点的子树中不存在任何一条后向边连接 (u) 的祖先,或者说没有一条后向边跨过(passes over)(u)

    证明是显然的,不再赘述。

    具体实现中,我们令 (dfn[u]) 表示 (u) 是第 (dfn[u]) 个被访问到的,(low[u]) 为 DFS 树上 (u) 的儿子(包含 (u) 自己)可以访问到的所有节点中 (dfn) 最小的。那么对于节点 (u),进行以下操作:

    procedure DFS(u)
        ind <- ind + 1
        dfn[u] <- low[u] <- ind
    
        childnum <- 0
        for v 可以被 u 访问
            if 访问过 v then low[u] <- min(low[u], dfn[v])
            else
                DFS(v)
                low[u] <- min(low[u], low[v])
                if low[v] = dfn[u] then childnum <- childnum + 1
                end if
            end if
        end for
        
        if childnum >= 1
            u 为割点
        end if
    end procedure
    

    注意 我们不只访问儿子节点,所以不需要特判父亲

    而对于根节点,我们需要其有两个以上儿子。在具体实现中可以令 (childnum = -1)

    给出 C++ 实现

    洛谷板子题

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn = 2E+4 + 5;
    const int maxm = 1E+5 + 5;
    
    int n, m, root;
    int tot, first[maxn];
    int ind, cnt, p[maxn];
    int dfn[maxn], low[maxn];
    struct Edge {
    	int to, next;
    } e[maxm * 2];
    
    inline void Add(int x, int y)
    {
    	e[++tot] = { y, first[x] };
    	first[x] = tot;
    }
    
    void DFS(int u)
    {
    	dfn[u] = low[u] = ++ind;
    	
    	int childnum = 0;
    	if(u == root) childnum = -1;
    	
    	for(int i = first[u]; i; i = e[i].next) {
    		int v = e[i].to;
    		
    		if(!dfn[v]) {
    			DFS(v), low[u] = min(low[u], low[v]);
    			if(low[v] == dfn[u]) ++childnum;
    		}
    		else low[u] = min(low[u], dfn[v]);
    	}
    	
    	if(childnum >= 1) p[++cnt] = u;
    }
    
    int main()
    {
    	scanf("%d%d", &n, &m);
    	for(int i = 1; i <= m; ++i) {
    		int x, y;
    		
    		scanf("%d%d", &x, &y);
    		Add(x, y), Add(y, x);
    	}
    	
    	for(int i = 1; i <= n; ++i)
    		if(!dfn[i]) root = i, DFS(i);
    	
    	sort(p + 1, p + cnt + 1);
    	
    	printf("%d
    ", cnt);
    	for(int i = 1; i <= cnt; ++i)
    		printf("%d
    ", p[i]);
    }
    

    而事实上,DFS 树还可以处理如无向图改成有向图,二分图划分之类的东西,特别是处理仙人掌时更是得心应手。

  • 相关阅读:
    java坏境内存不够用 大量占用swap 临时加swap
    磁盘分区
    简述raid0,raid1,raid5,raid10 的工作原理及特点
    给用户提权
    用户的环境变量被删除了
    定时任务
    linux权限
    kafka部署
    数据仓库
    kylin
  • 原文地址:https://www.cnblogs.com/whx1003/p/12635387.html
Copyright © 2011-2022 走看看