1)向上标记法:
从x向上走到根节点,并标记所有经过的节点。
从y向上走到根节点,当第一次遇到已标记的节点时,就找到了LCA(x,y).
2)树上倍增法:
[1]先将两个点跳到同一层
[2]让两个点同时往上跳,一直跳到它们的最近公共祖先的下一层。
树上倍增法是一个很重要的算法。除了求LCA之外,他在很多问题中都有广泛应用。设F[x,k]表示x的2^k倍祖先,即从x走向根节点走2^k步到达的节点。特别地,若该节点不存在,则令F[x,y]=0。F[x,0]就是x的父节点。除此之外,任意k属于[1,logn],F[x,k]=F[F[x,k-1],k-1]。
这类似于一个动态规划的过程,“阶段”就是节点的深度。因此,我们可以对树进行广度优先遍历,按照层次顺序,在节点入队之前,计算它在F数组中相应的值。
以上部分处理是预处理,时间复杂度是O(nlogn),之后可以多次对不同的x,y计算LCA,每次询问的时间复杂度为O(logn)
基于F数组的计算LCA(x,y)分为以下几步。
1.设d[x]表示x的深度。不妨设d[x]>=d[y](否则可交换x,y)
2.用二进制拆分思想,把x向上调整到于y同一深度。具体来说,就是依次尝试从x向上走k=2^(logn),……,2^1,2^0步,检查到达节点是否比y深。每次检查中,若是,则令x=F[x,y]。
3.若此时x==y,说明已经找到了LCA,LCA就等于y。
4.用二进制思想拆分,把x,y同时向上调整,并保持深度一致且二者不会相同。具体来说,就是一次尝试把x,y同时向上走k=2^(logn),……,2^1,2^0步,若F[x,k]!=F[y,k](即仍未相会),则令x=F[x,k],y=F[y,k]。
5.此时x,y必定只差一步就相会了,它们的父节点F[x,0]就是LCA。
3)LCA的Tarjan算法
Tarjan算法本质上是使用并查集对“向上标记法”的优化。它是一个离线算法,需要把m个询问一次性读入,统一计算,最后统一输出。时间复杂度O(n+m)。
在深度遍历的任意时刻,树中的节点分为三类。
1.已经访问完毕并且回溯的节点。在这些节点上标记一个整数2.
2.已经开始递归,但尚未回溯的节点。这些节点就是当前正在访问的节点x以及x的祖先。在这些节点上标记一个整数1.
3.尚未访问的节点。这些节点没有标记。
对于正在访问的节点x,它到根节点的路径已经标记为1。若y是已经访问完毕并且回溯的节点,则LCA(x,y)就是从y向上走到根,第一个遇到的标记为1的节点。
可以利用并查集进行优化,当一个节点获得整数2的标记时,把它所在的集合合并到它的父节点所在的集合中(合并时它的父节点标记一定为1,且单独构成一个集合)。
这相当于每个完成回溯的节点都有一个指针指向它的父节点,只需查询y所在集合代表元素(并查集的get操作),就等价于从y向上一直走到一个开始递归但尚未回溯的节点(具有标记1),即LCA(x,y)。
在回溯点x之前,扫描所有与x相关的所有询问,若询问中的另一个点y的标记为2,就知道了该询问的回答应该是y在并查集中的代表元素(并查集中find(y)函数的结果)
1171. 距离(离线求LCA:tarjan算法)-优快云博客
————————————————————————————————————————
参考至算法进阶竞赛指南。