题目
二叉树结点的定义如下:
- struct node {
- int data;
- struct node* left;
- struct node* right;
- };
给定二叉树中的两个结点,输出这两个结点的最低公共祖先结点(LCA)。注意,该二叉树不一定是二叉搜索树。
比如给定的二叉树如下所示,则可以知道结点1和5的最低公共祖先结点为5,结点4和5的最低公共祖先结点为5。
_______3______ / \ ___5__ ___1__ / \ / \ 6 _2 0 8 / \ 7 4
解法
1)自顶向下的方法
我们可以从根结点出发,判断当前结点的左右子树是否包含这两个结点。如果左子树包含两个结点,则它们的最低公共祖先结点也一定在左子树中。如果右子树包含两个结点,则它们的最低公共祖先结点也一定在右子树中。如果一个结点在左子树,而另一个结点在右子树中,则当前结点就是它们的最低公共祖先结点。根据该思路写出代码如下,注意这里已经假定p和q是二叉树中的结点。
- /*查找二叉树中两个结点最低公共祖先结点*/
- struct node* LCA(struct node *root, struct node *p, struct node *q)
- {
- if (hasNode(root->left, p) && hasNode(root->left, q)) //p和q都在左子树中
- return LCA(root->left, p, q);
- if (hasNode(root->right, p) && hasNode(root->right, q)) //p和q都在右子树中
- return LCA(root->right, p, q);
- return root; //p和q一个在左子树,一个在右子树中,直接返回root
- }
- /*判断root为根的树是否包含结点p*/
- bool hasNode(struct node* root, struct node* p)
- {
- if (!root) return false;
- if (root == p)
- return true;
- return hasNode(root->left, p) || hasNode(root->right, p);
- }
由于我们对每个结点都调用了hasNode函数,而该函数类似于遍历二叉树,复杂度为O(N),所以总的时间复杂度为O(N^2),我们期望找到一个更好的方法。
2)自底向上的方法
由于自顶向下的方法需要重复遍历结点,使用自底向上的方法可以避免这种情况。
自底向上遍历结点,一旦遇到结点等于p或者q,则将其向上传递给它的父结点。父结点会判断它的左右子树是否都包含其中一个结点,如果是,则父结点一定是这两个节点p和q的LCA,传递父结点到root。如果不是,我们向上传递其中的包含结点p或者q的子结点,或者NULL(如果子结点不包含任何一个)。该方法时间复杂度为O(N)。
- typedef struct node Node;
- Node *LCA(Node *root, Node *p, Node *q) {
- if (!root) return NULL;
- if (root == p || root == q) return root;
- Node *L = LCA(root->left, p, q);
- Node *R = LCA(root->right, p, q);
- if (L && R) return root; // 如果p和q位于不同的子树
- return L ? L : R; //p和q在相同的子树,或者p和q不在子树中
- }
3)公共路径法
还有一个方法就是依次得到从根结点到结点p和q的路径,找出它们路径中的最后一个公共结点即是它们的LCA。该算法在何海涛先生的博客中有详细描述,这里就不赘述,详见http://zhedahht.blog.163.com/blog/static/25411174201081263815813/,该方法复杂度也为O(N),不过需要额外的空间存储路径,当然第2种方法中递归也是需要栈空间的,不过整体来看第2种方法简洁但是不是很好理解,而公共路径法则更加易懂。