tarjan算法(求最近公共祖先)

本文讲解用于求解最近公共祖先Least Common Ancestor, LCA的tarjan算法

首先看最近公共祖先问题对一棵给定的有根树为了得知两点之间最短的路径显然应当先从一点运动到它们的最近公共祖先再直接运动到另一点如果每条边只能经过一次那么这就是唯一的路径这时就需要确定这两点的最近公共祖先对小规模的数据可以使用暴力算法比如对一个点的所有祖先进行dfs找到最近的能到达另一点的那个祖先但是这个算法本身效率就很低下更不用提大规模数据的情形

解决这一类问题有多种算法可分为在线算法和离线算法两种在线离线是针对大规模数据的说法在线算法是指对每个询问即节点对即时处理的算法离线算法是指将所有询问存储起来以后统一处理的算法在线和离线会造成复杂度的很大差异通常来讲离线算法应当更快一些因为离线情形掌握了更多的信息这里要讲的tarjan算法是一种离线算法复杂度为$O(n+p)$其中$n$为树的节点数$p$为询问数

考虑优化上述的dfs上述的dfs显然有许多浪费因为上述过程中的所有节点只需要一次dfs就可以遍历完而上述过程中dfs的次数却与深度成正比这显然是没有很好地利用dfs的信息仔细考虑dfs的过程发现遍历在最近祖先处是一棵一棵子树进行的也就是先遍历了含有节点1的子树再遍历含有节点2的子树假设1比2先遍历到我们需要找到这两棵子树分叉的地方而不考虑分叉处上面的情形那么可以考虑这样做针对当前的状态v为遍历过的每个节点u打上一个标记记录u和v从哪里开始分叉如果这一操作可以在遍历到u时就做到那么问题就解决了显然初始值刚遍历到u时的标记是这个节点直接的父亲当遍历完他父亲的所有子树后仍没有找到节点2时应当返回它父亲的父亲继续搜索那么该节点的标记也都应当变成他的父亲的父亲这看起来需要再进行一次dfs从而效率和刚才一样低但是可以用并查集解决这一问题现在的过程变成遍历到某一结点时将他和他父亲合并在并查集中成为父亲的子树当他父亲遍历完所有子树之后将他父亲和他父亲的父亲合并…为了知道某个节点的标记分叉处只需要查找该节点在并查集中的根节点