如何在树上做 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 2
和2 0
。所以当我在2
开始我的dfs
时,我将节点0
添加到我的堆栈中,这是错误的。
我尝试了另一种方法,仅插入仅给定的边,但这也不起作用,因为在示例中我将插入 3 2
,这是从 3
到节点 2
的边,所以当我在节点 2
开始我的 dfs
我将无法到达节点 3
。
我觉得问题很简单,但我错过了一些重要的想法!
【问题讨论】:
【参考方案1】:由于您的图是有根树,您可以进行以下预处理。
-
从根(顶点 #0)启动 DFS;
对于每个顶点 u,存储其 父级 v。这意味着如果您沿着从根到 u 的最短路径行进,则该路径上的倒数第二个顶点将是 v。请注意,在任何一棵树中,从一个顶点到另一个顶点都只有一条最短路径。假设您有一个数组
parent
,根据上述定义,parent[u] = v
和parent[0] = -1
。
您可以通过注意计算parent
数组,如果您执行s.push(g[v][i])
,则v
是i
的父级(否则您将首先访问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? (不一定是二进制)的主要内容,如果未能解决你的问题,请参考以下文章