问题
在前面的文章二叉树两结点最低公共祖先结点(一) 中有探讨过这个问题,不过本文考虑的是另外一种二叉树,该二叉树每个结点包含一个指向父结点的指针,根结点的父结点为NULL。其结构如下:
struct node {
int data;
struct node* left;
struct node* right;
struct node* parent;
};
同样,该二叉树不一定是二叉搜索树(BST)。在前面文章中的自底向上的方法,可以在O(N)的时间之内找到LCA,不过由于本文中的二叉树有指向父结点的指针,所以并不用递归实现,求解应该更加简单。给定一棵二叉树如下:
_______3______ / \ ___5__ ___1__ / \ / \ 6 _2_ 0 8 / \ 7 4
基本解法
可以从两个结点的位置开始向上回溯到根结点,最终这两个结点会合并成一条路径。可以使用一个hash_set来记录已经访问过的结点,如果在回溯的过程中访问到一个已经访问过的结点,则该结点一定是LCA,直接返回即可。该方法由于使用hash_set,因此需要额外的存储空间。时间复杂度为O(h),h为二叉树的高度。
typedef struct node Node;
Node *LCA(Node *root, Node *p, Node *q) {
hash_set<Node *> visited;
while (p || q) {
if (p) {
if (!visited.insert(p).second)
return p; // 如果插入p不成功,表示p已经访问过,找到了LCA
p = p->parent;
}
if (q) {
if (!visited.insert(q).second)
return q; // 如果插入q不成功,表示q已经访问过,找到了LCA
q = q->parent;
}
}
return NULL;
}
更好的解法
由于每个结点都有指向父结点的指针,因此可以求出这两个结点的高度差dh。可以看到,在回溯的过程中离根结点近的结点总是领先离根结点远的结点的dh步。这样,可以让离根结点远的(更深的结点)先走dh步,然后两个结点一起走,最终一定会在某一个结点相遇,则相遇的结点为LCA。如果不相遇,表示这两个结点不在同一棵树中,则返回NULL(由于我们假定了两个结点都在二叉树中,所以这种情况不可能出现)。
int getHeight(Node *p) {
int height = 0;
while (p) {
height++;
p = p->parent;
}
return height;
}
// 因为root->parent= NULL,所以参数中不需要传递root
Node *LCA(Node *p, Node *q) {
int h1 = getHeight(p);
int h2 = getHeight(q);
// 交换高度大小
if (h1 > h2) {
swap(h1, h2);
swap(p, q);
}
//保证h2>=h1
int dh = h2 - h1;
for (int h = 0; h < dh; h++)
q = q->parent;
while (p && q) {
if (p == q) return p;
p = p->parent;
q = q->parent;
}
return NULL; // p和q不在同一棵树中,这种情况在本题中不会出现。
}