我很难理解tarjan最不常见的祖先算法,有人可以通过一个例子来解释我吗?....我在DFS搜索后被卡住了算法做了什么?
答案 0 :(得分:9)
我的解释将基于上面发布的维基百科link :)。
我假设你已经知道在算法中使用的联合不相交结构。 (如果没有,请阅读它,你可以在“算法简介”中找到它。)
基本思想是每次算法访问节点x
时,其所有后代的祖先都将是该节点x
。
为了找到两个节点r
的最小共同祖先(LCA)(u,v)
,将会有两种情况:
节点u
是节点v
的子节点(反之亦然),这种情况很明显。
节点u
是第i个分支,v
是节点r
的第j个分支(i< j),因此在访问节点u
之后,算法回溯到节点r
,它是两个节点的祖先,将节点u
的祖先标记为r
并转到访问节点v
。
目前它访问节点v
,因为u
已标记为已访问(黑色),因此答案为r
。希望你明白!
答案 1 :(得分:0)
我将解释如何使用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并与p联合v后,p和根v的子树中的所有顶点(即p和所有后代) v中的v,包括v)将在一个由p表示的不相交集中(即ancester [不相交集的根]为p)。
证明:假设树的高度为h。然后从叶节点开始对顶点高度进行归纳。
引理2:对于每个顶点v,在将其标记为已访问之前,以下语句是正确的:
所有v的父级pi将位于一个不相交的集合中,该集合恰好包含pi和pi所访问的子树中的所有顶点。
到目前为止,每个访问过的顶点都在这些不相交的集合中。
证明:我们通过归纳进行。该语句对根(唯一的高度为0的顶点)无意义,因为它没有父级。现在假设该语句对于k≥0的每个高度k的顶点都成立,并且假设v是高度k + 1的顶点。令p为v的父代。在p访问v之前,假设它已经访问了其子c1,c2,...,cn。通过引理1,我们可以归纳证明p和根c1,c2,...,cn的子树中的所有顶点都在一个由p表示的不相交集中。此外,唯一新访问的顶点是此不相交集中的顶点。由于p的高度为k,因此我们可以使用归纳假设得出v确实满足1和2的结论。
我们现在准备证明算法。
声明:对于每个查询(u,v),算法都会输出u和v的最低共同祖先。
证明:在不失一般性的前提下,假设我们在访问DFS中的v之前先访问u。那么v是u的后代,或者v不是u的后代。如果v是u的后裔,那么根据引理1,我们知道u和v在一个由u表示的不相交集中,这意味着ancestor [find_set(v)]是u,这是正确的答案。如果v不是u的后代,那么根据引理2,我们知道u必须位于不相交的集合之一中,其中每个在我们标记v时都由v的父代表示。如果让p为表示顶点u处于不相交集的,同样由引理2知道u在p的访问子树中,因此在p的后代中。在访问完所有v的子代后,这些值没有改变,因此p确实是u和v的共同祖先。要看到p是最小的祖先,假设q是p的子代,其中v是后代(即从v返回根,q是到达p之前的最后一个父级。假设矛盾,你也是q的后代。然后由引理2,u既在p表示的不交集中,又在q表示的不交集中,所以这个不交集包含两个v的父级,这是一个矛盾。