如何在树上做 DFS? (不一定是二进制)

Posted

技术标签:

【中文标题】如何在树上做 DFS? (不一定是二进制)【英文标题】:How to do a DFS on a tree? (not necessarily binary) 【发布时间】:2016-01-23 03:36:01 【问题描述】:

我有一棵 n 节点树(标记为 0 to n)。我使用了两个向量来保存边缘信息。

typedef std::vector<std::vector<int>> graph;

输入是n-1 形式的边:

0 1
1 2
2 3
and so on 

我被告知节点0 始终是根节点。 我使用以下方法扫描边缘:

for (int i = 0; i < n-1; i++) 
    scanf("%d %d", &a, &b);
    g[a].push_back(b);
    g[b].push_back(a); // the second approach doesn't use this line

这是我的简单 dfs:

void dfs(graph &g, int v) 
    std::vector<int> visited; // I don't use a visited array for the second approach
    for (int i = 0; i < g.size(); i++) 
        visited.push_back(0);
    
    std::stack<int> s;
    std::set<int> t;
    s.push(v);
    while (!s.empty()) 
        int i = s.top(); s.pop();
        // do stuff
        for (int i = 0; i < g[v].size(); i++) 
            if (!visited[i]) 
                visited[i] = true;
                s.push(g[v][i]);
            
        
    

例如,假设我们有 4 个节点和以下边:

0 1
0 2
3 2
2 4

假设我对从2 开始的子树感兴趣。上述方法不起作用,因为我插入了无向边0 22 0。所以当我在2 开始我的dfs 时,我将节点0 添加到我的堆栈中,这是错误的。

我尝试了另一种方法,仅插入仅给定的边,但这也不起作用,因为在示例中我将插入 3 2,这是从 3 到节点 2 的边,所以当我在节点 2 开始我的 dfs 我将无法到达节点 3

我觉得问题很简单,但我错过了一些重要的想法!

【问题讨论】:

【参考方案1】:

由于您的图是有根树,您可以进行以下预处理。

    从根(顶点 #0)启动 DFS; 对于每个顶点 u,存储其 父级 v。这意味着如果您沿着从根到 u 的最短路径行进,则该路径上的倒数第二个顶点将是 v。请注意,在任何一棵树中,从一个顶点到另一个顶点都只有一条最短路径。假设您有一个数组parent,根据上述定义,parent[u] = vparent[0] = -1。 您可以通过注意计算parent 数组,如果您执行s.push(g[v][i]),则vi 的父级(否则您将首先访问i); 由于parent[v]是全局根(顶点0)最短路径上的前一个顶点,它也是任意顶点x最短路径上的前一个顶点,其中包含v 在其子树中。

现在,当您想对顶点 u 的子树进行 DFS 时,您可以像现在一样进行 DFS,但 不要访问任何顶点的父节点。说,如果你想做s.push(v),而parent[u] = v,不要这样做。这样你就永远不会离开 u 的子树。

其实知道parent,你就可以摆脱你的visited数组了。当你用顶点v“做事”时,v的唯一已经被访问过的邻居是parent[v]。此属性不依赖于您要遍历其子树的初始顶点。 DFS 代码如下所示(假设您已完成预处理以获得 parent:

  void dfs(graph &g, vector<int> &parent, int v) 
    std::stack<int> s;
    s.push(v);
    while (!s.empty()) 
        int v = s.top(); s.pop(); // By the way, you have mistake here: int i = s.top().
        // do stuff
        for (int i = 0; i < g[v].size(); i++) 
            if (parent[v] != g[v][i]) 
                s.push(g[v][i]);
            
        
    
  

PS 这种方法在某种程度上类似于您的第二种方法:它只处理从根到子树的边。但它没有缺陷,例如在您的示例中,“3 2”是错误的方向,因为您通过从 root 执行 DFS 从算法上推断方向。

【讨论】:

以上是关于如何在树上做 DFS? (不一定是二进制)的主要内容,如果未能解决你的问题,请参考以下文章

CF1076E(dfs+树上差分)

2018/3/10

UVa 818Cutting Chains (暴力dfs+位运算+二进制法)

HDU 4582 DFS spanning tree

bzoj 1023

POJ-3279.Fliptile(二进制状态压缩 + dfs) 子集生成