我很难理解Tarjan的最近公共祖先算法,有人能用例子来解释一下吗?
在DFS搜索后,我陷入了困境,这个算法到底是做什么的?
我的解释将基于上面发布的维基百科链接 :).
我假设您已经了解了算法中使用的并查集数据结构。 (如果没有,请阅读“算法导论”中的相关内容)。
基本思想是每次算法访问节点x
时,其所有后代节点的祖先都将是该节点x
。
因此,要找到两个节点(u,v)
的最近公共祖先(LCA)r
,有两种情况:
节点u
是节点v
的子节点(反之亦然),这种情况很明显。
节点u
是节点r
的第i个分支,节点v
是节点r
的第j个分支(i < j),所以在访问节点u
后,算法回溯到节点r
,它是两个节点的祖先,将节点u
的祖先标记为r
,并继续访问节点v
。
当它访问节点v
时,因为u
已经被标记为访问过(黑色),所以答案将是r
。希望您明白了!
我将使用CP-Algorithms的代码进行解释:
void dfs(int v)
{
visited[v] = true;
ancestor[v] = v;
for (int u : adj[v]) {
if (!visited[u]) {
dfs(u);
union_sets(v, u);
ancestor[find_set(v)] = v;
}
}
for (int other_node : queries[v]) {
if (visited[other_node])
cout << "LCA of " << v << " and " << other_node
<< " is " << ancestor[find_set(other_node)] << ".\n";
}
}
让我们概述算法的证明。
引理1:对于每个顶点v及其父节点p,在我们从p访问v并将v与p合并后,p和根v的所有子树中的所有顶点(即p和所有v的后代,包括v)将位于由p表示的一个不相交集中(即祖先[不相交集的根]是p)。
证明:假设树的高度为h。然后按照顶点高度进行归纳,从叶节点开始。
引理2:对于每个顶点v,在我们将其标记为已访问之前,以下陈述是正确的:
每个v的父节点pi将在一个不相交集中,该集合包含pi和pi已经完成访问的pi子树中的所有顶点。
到目前为止,每个已访问的顶点都在这些不相交集之一中。
证明:我们通过归纳法进行证明。对于根节点(高度为0的唯一顶点),该语句是空洞的真实性,因为它没有父节点。现在假设该语句对于所有高度为k的节点(k≥0)都成立,且v是一个高度为k+1的节点。让p成为v的父节点。在p访问v之前,假设它已经访问了它的子节点c1、c2、…、cn。由引理1可知,p和根节点c1、c2、…、cn的子树中的所有节点都在一个由p表示的不相交集合中。此外,在我们访问p之后的所有新访问的节点都是这个不相交集合中的节点。由于p的高度为k,我们可以使用归纳假设得出结论:v确实满足1和2。
现在我们准备证明算法。
声明:对于每个查询(u,v),该算法输出u和v的最低公共祖先。
证明:不失一般性,假设我们在DFS中访问u之前访问v。那么v要么是u的后代,要么不是。
如果v是u的后代,根据引理1,我们知道u和v在由u表示的一个不相交集合中,这意味着ancestor[find_set(v)]是u,即正确答案。
u
时,你如何知道节点r
是节点u
和节点v
的祖先?我不明白你如何知道回溯到哪里才能找到节点r
。 - lolololol ol