Tarjan最不常见的祖先算法解释

时间:2013-10-09 03:03:20

标签: algorithm tree

我很难理解tarjan最不常见的祖先算法,有人可以通过一个例子来解释我吗?....我在DFS搜索后被卡住了算法做了什么?

2 个答案:

答案 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,在将其标记为已访问之前,以下语句是正确的:

  1. 所有v的父级pi将位于一个不相交的集合中,该集合恰好包含pi和pi所访问的子树中的所有顶点。

  2. 到目前为止,每个访问过的顶点都在这些不相交的集合中。

证明:我们通过归纳进行。该语句对根(唯一的高度为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的父级,这是一个矛盾。