Lowest Common Ancestor
给定一棵树与树中的两个节点,如何找出这两个节点的最近公共祖先。最近公共祖先的定义为,对于一个有根树 T 与其中的两个节点 p , q ,p 与 q 的最近公共祖先为一个节点 x ,满足以 x 为根的子树同时包含了节点 p 与 q ,且 x 的深度尽可能的深。
朴素做法: DFS
type RefNode = RefCell<TreeNode>;
type RcNode = Rc<RefNode>;
type PtrNode = *const RefNode;
type MaybeNode = Option<RcNode>;
enum Res {
Both(MaybeRcNode), // LCA
Only1,
Only2,
Neither,
}
fn dfs(root: MaybeRcNode, node1: RcNode, node2: RcNode) -> Res {
if let Some(root) = root {
let left = root.borrow_mut().left.take();
let right = root.borrow_mut().right.take();
match (root == node1, root == node2, dfs(left, node1, node2), dfs(right, node1, node2)) {
(_, _, Both(ans), _) | (_, _, _, Both(ans)) => Both(ans),
(true, true, _, _) => Both(Some(root)),
(_, _, Only1, Only2) | (_, _, Only2, Only1) => Both(Some(root)),
(true, _, Only2, _) | (true, _, _, Only2) => Both(Some(root)),
(_, true, Only1, _) | (_, true, _, Only1) => Both(Some(root)),
(true, _, Only1, _) | (true, _, _, Only1) => Only1,
(_, true, Only2, _) | (_, true, _, Only2) => Only2,
(_, _, Only1, Only1) => Only1,
(_, _, Only2, Only2) => Only2,
_ => Neither,
}
} else {
Res::Neither
}
}
多次查询
上面的做法对于单次查询比较有效,只需要将树中的所有节点遍历一遍就可以找到最近公共祖先。但是如果我们将要对同一棵树进行多次的 LCA 查询,这种做法的效率就无法接受了。我们可不可以只遍历一次树,利用树的信息来寻找 LCA 呢?
通过一次遍历,从树中我们可以得到任意一个节点的深度与父亲节点信息。因此我们可以通过下面这个步骤来寻找 LCA :
- 将两个节点指针中,深度较大的指针向上移动至同一个深度。
- 比较两个节点指针,如果相同,则这个节点就是 LCA ,否则跳至第三步。
- 将两个节点指针分别移动到他们的父节点。
let mut depth: HashMap<PtrNode, usize> = HashMap::new();
let mut parent: HashMap<PtrNode, RcNode> = HashMap::new();
build_info(root.clone(), None, &mut depth, &mut parent); // fn build_info(root, parent_node: MaybeNode, depth, parent);
let depth1 = depth[&(node1.as_ptr() as *const _)];
let depth2 = depth[&(node2.as_ptr() as *const _)];
// Make sure depth1 <= depth2
if depth2 < depth1 {
std::mem::swap(&mut depth1, &mut depth2);
std::mem::swap(&mut node1, &mut node2);
}
// Raise node1
let d = depth2 - depth1;
for _ in 0..d {
node2 = parent[&(node2.as_ptr() as *const _)];
}
// Raise both until equal
while node1 != node2 {
node1 = parent[&(node1.as_ptr() as *const _)].clone();
node2 = parent[&(node2.as_ptr() as *const _)].clone();
}
// LCA
return node1;
倍增
在上面这个过程中,我们发现后续的 Raise 过程的时间复杂度与深度有关。当二叉树退化为链表时,时间复杂度退化为整个树的节点数目,不算十分理想。我们是否可以加速 Raise 过程呢?
我们不妨修改一下 parent 的定义,将其通过索引节点指针得到的结果变成一个列表(而不是直接父节点),第 i 个位置的元素表示这个节点的第 2^0 个父节点。这样的定义满足如下的性质:
- parent(parent(node, i-1), i-1) == parent(node, i)
- 交换律 parent(parent(node, i), j) == parent(parent(node, j), i)
第一条性质我们可以使用在构建 parent 表中。对于任意一个节点的 parent 列表,我们可以通过下面的过程计算。
- 将这个节点的直接父节点作为它的 parent 列表的第 0 元素
- let i = 1
- 使用性质一,
parent[node][i] = parent[parent[node][i-1]][i-1]
,如果此处的索引超出界限则跳出循环 - i += 1
- 跳转到第三步
在构建好 parent 表之后,可以通过这张表将节点指针上升任意 2 的整数次幂高度。我们接下来考虑,如何通过这个表,将节点指针上升任意高度 d 。首先,对于任意一个正整数 d ,我们可以将其转换为一个关于 2 的多项式。
d = (a_n)2^n + (a_(n-1))2^(n-1) + ... + (c_0)2^0
c_i = 0 | 1
这意味着我们可以通过 n 次跳跃就可以将节点指针提升任意高度。同时由于性质二交换律,我们可以任意组织跳跃的顺序。
// raise height d
let i = 0;
while d > 0 {
node = parent[&(node.as_ptr() as *const)][i]; // Assume element exists
i += 1;
d >>= 1;
}
这一段代码可以代替将较低的节点指针提升到相同高度的部分。
剩下的将两个节点提升到 LCA 的过程也可以通过新的 parent 表进行优化——我们不再需要将节点指针逐次提升一个高度,而是跳转到 parent 表中一个恰好没有使节点指针相同的高度。当没有一个高度可以使两个指针不同时,则父节点就是 LCA 。
完整代码
type RefNode = RefCell<TreeNode>;
type RcNode = Rc<RefNode>;
type PtrNode = *const RefNode;
type MaybeNode = Option<RcNode>;
pub fn lowest_common_ancestor(root: Option<Rc<RefCell<TreeNode>>>, p: Option<Rc<RefCell<TreeNode>>>, q: Option<Rc<RefCell<TreeNode>>>) -> Option<Rc<RefCell<TreeNode>>> {
use std::collections::HashMap;
fn build_parent(root: MaybeNode, depth: usize, parent: MaybeNode, parents: &mut HashMap<PtrNode, Vec<RcNode>>, depths: &mut HashMap<PtrNode, usize>) {
if let Some(root) = root {
depths.insert(&(root.as_ptr() as *const _), depth);
if let Some(parent) = parent {
let mut ps: Vec<RcNode> = vec![parent.clone()];
let mut i = 0;
loop {
if let Some(p) = parents
.get(&(ps[i].as_ptr() as *const _))
.and_then(|pv| pv.get(i))
{
ps.push(p.clone());
i += 1;
} else {
break;
}
}
parents.insert(root.as_ptr() as *const _, ps);
}
let left = root.borrow_mut().left.take();
let right = root.borrow_mut().right.take();
let (pdl, qdl) = build_parent(left, depth + 1, Some(root.clone()), parents, depths);
let (pdr, qdr) = build_parent(right, depth + 1, Some(root.clone()), parents, depths);
}
}
if let (Some(mut p), Some(mut q)) = (p, q) {
let mut parents = HashMap::new();
let mut depths = HashMap::new();
build_parent(root.clone(), 0, None, &mut parents, &mut depths);
if let (Some(mut pd), Some(mut qd)) = (depths.get(&(p.as_ptr() as *const _)), depths.get(&(q.as_ptr() as *const _))) {
// Make sure that pd <= qd
if pd > qd {
std::mem::swap(&mut p, &mut q);
std::mem::swap(&mut pd, &mut qd);
}
// Jump to the same level
let mut i = 0;
let mut d = qd - pd;
while d > 0 {
if d & 1 == 1 {
q = parents[&(q.as_ptr() as *const _)][i].clone();
println!("+{i}");
}
i += 1;
d >>= 1;
}
// Jump as far as they can, until find the LCA
while p != q {
// p_depth == q_depth, so p_len == q_len
let len = parents[&(p.as_ptr() as *const _)].len();
for i in (0..len).rev() {
let pp = parents[&(p.as_ptr() as *const _)][i].clone();
let qp = parents[&(q.as_ptr() as *const _)][i].clone();
if pp != qp || i == 0 {
p = pp;
q = qp;
}
}
}
// LCA
Some(p)
} else {
None
}
} else {
None
}
}