求二叉树中任意两个节点的最近公共祖先也称为LCA问题(Lowest Common Ancestor)。
二叉查找树
如果该二叉树是二叉查找树,那么求解LCA十分简单。
基本思想为:从树根开始,该节点的值为t,如果t大于t1和t2,说明t1和t2都位于t的左侧,所以它们的共同祖先必定在t的左子树中,从t.left开始搜索;如果t小于t1和t2,说明t1和t2都位于t的右侧,那么从t.right开始搜索;如果t1<t< t2,说明t1和t2位于t的两侧,那么该节点t为公共祖先。
如果t1是t2的祖先,那么应该返回t1的父节点;同理,如果t2是t1的祖先,应该返回t2的父节点。
public int query(Node t1, Node t2, Node t) {
int left = t1.value;
int right = t2.value;
Node parent = null;
if (left > right) {
int temp = left;
left = right;
right = temp;
}
while (true) {
if (t.value < left) {
parent = t;
t = t.right;
} else if (t.value > right) {
parent = t;
t = t.left;
} else if (t.value == left || t.value == right) {
return parent.value;
} else {
return t.value;
}
}
}
其中,parent用于处理t1是t2的祖先(或t2是t1的祖先)的情况。
一般的二叉树
如果二叉树不是二叉查找树该怎么办呢?
1. 离线算法(Tarjan)
利用并查集优越的时空复杂度,可以实现O(n+q)的算法,q是查询次数。
Tarjan算法基于深度优先搜索。对于新搜索到的一个结点u,先创建由u构成的集合,再对u的每颗子树进行搜索,每搜索完一棵子树,这时候子树中所有的结点的最近公共祖先就是u了。
void LCA(int parent) //从根节点开始
{
p[parent] = parent;
ancestor[findSet(parent)] = parent;
for(int i = index[parent]; i != -1; i = e[i].next)
{
LCA(e[i].to);
Union(parent,e[i].to);
ancestor[findSet(parent)] = parent;
}
vis[parent] = true;
if(parent == a && vis[b]) //要先将所有查询记录下来,这里只有一个查询:a与b的LCA
printf("%d\n",ancestor[findSet(b)]);
else if(parent == b && vis[a])
printf("%d\n",ancestor[findSet(a)]);
}
2. 在线算法(RMQ)
一个O(nlog2n)的预处理,O(1)的查询。
以下面一棵树为例:
(1)
/ \
(2) (7)
/ \ \
(3) (4) (8)
/ \
(5) (6)
step1:
按深度遍历树,记录访问顺序和相应的深度(2*n-1),及每个节点第一次出现的位置。
结点的访问顺序为:1 2 3 2 4 5 4 6 4 2 1 7 8 7
相应的深度为: 0 1 2 1 2 3 2 3 2 1 0 1 2 1 0
结点1-8第一次出现的次序为:1 2 3 5 6 8 12 13
step2:
查询3和6的公共祖先,考虑3和6第一次出现的位置为3和8,即寻找序列2 1 2 3 2 3中的最小值,最小值为1,对应的点位2,则3与6的最近公共祖先为2。
step3:
则对于给出的任意两个结点,找出它们第一次出现的位置i,j(i<j),在深度序列中查询最小值的下标k,depth[k]即为所求。显然,查询多次深度序列中的最小值的下标,自然而然就想到了RMQ。
public class LCA {
private final int MAX = 10;
private int[] dfs = new int[2*MAX];
private int[] depth = new int[2*MAX];
private int[][] f;
private int[] call = new int[MAX];
private int len = 0;
public void track(Node t, int d) {
if (t == null)
return;
dfs[len] = t.value;
depth[len] = d;
call[t.value-1] = len;
len++;
track2(t.left, d+1);
if (t.left != null) {
dfs[len] = t.value;
depth[len] = d;
len++;
}
track2(t.right, d+1);
if (t.right != null) {
dfs[len] = t.value;
depth[len] = d;
len++;
}
}
public void rmq() {
int count = 1;
while ((1 << count) <= len)
count++;
f = new int[len][count];
count--;
for (int i = 0; i < len; i++) {
f[i][0] = i;
}
for (int j = 1; (1 << j) <= len; j++) {
for (int i = 0; i+(1<<j)-1 < len; i++) {
f[i][j] = depth[f[i][j-1]] < depth[f[i+(1<<j-1)][j-1]] ?
f[i][j-1] : f[i+(1<<j-1)][j-1];
}
}
}
public int query(Node t1, Node t2) {
int start = call[t1.value-1];
int end = call[t2.value-1];
if(start > end) {
int temp = start;
start = end;
end = temp;
}
int count = 1;
while ((1 << count) <= end - start + 1)
count++;
count--;
int result = depth[f[start][count]] < depth[f[end-(1<<count)+1][count]] ?
f[start][count] : f[end-(1<<count)+1][count];
if (dfs[result] == t1.value || dfs[result] == t2.value) {
int temp = depth[result];
while (depth[result] >= temp)
result--;
}
return dfs[result];
}
public static void main(String[] args) {
Node n3 = new Node(3);
Node n5 = new Node(5);
Node n6 = new Node(6);
Node n4 = new Node(4, n5, n6);
Node n2 = new Node(2, n3, n4);
Node n8 = new Node(8);
Node n7 = new Node(7, null, n8);
Node n1 = new Node(1, n2, n7);
LCA l = new LCA();
l.track(n1, 0);
l.rmq();
System.out.println(l.query(n5, n4));
}
}
class Node {
int value;
Node left;
Node right;
public Node(int value, Node left, Node right) {
this.value = value;
this.left = left;
this.right = right;
}
public Node(int value) {
this.value = value;
this.left = null;
this.right = null;
}
}
3. 后序遍历
基本思想:如果这两个节点不在一条线上(即这两个节点不存在一个节点是另一个节点的祖先的情况),则它们必定分别在所求节点A的左子树和右子树上,后序遍历到第一个满足这个条件的节点就是所要求的节点A。否则,当这两个节点在一条线上,所求节点A则是这两个节点中深度最低的节点的父节点。
bool lca(Node *root, int va, int vb, Node *&result, Node* parent)
{
// left/right 左/右子树是否含有要判断的两节点之一
bool left = false, right = false;
if (!result && root->left) left = lca(root->left,va,vb,result,root);
if (!result && root->right) right = lca(root->right,va,vb,result,root);
// mid 当前节点是否是要判断的两节点之一
bool mid = false;
if (root->data == va || root->data == vb) mid=true;
if (!result && int(left + right + mid) == 2) {
if (mid) result = parent;
else result = root;
}
return left | mid | right ;
}
Node *query(Node *root,int va, int vb)
{
if (root == NULL) return NULL;
Node *result = NULL;
lca(root, va, vb,result, NULL);
return result;
}