树中两个节点最低公共祖先系列算法

前言

最近刷到剑指offer 50题,树中两个结点的公共祖先,感觉这类题目对于不同性质的树会有不同的解法,而且也比较综合,在此就总结一下这类型的题目。

树为二叉搜索树

二叉搜索树的概念只要大家学过数据结构应该都是清楚的,也就是排序过的树。对于树上的每一个结点,其左子树的结点都小于父结点,其右子树的结点都大于父结点。那么我们解这道题的基本思想如下:

1)从根结点开始遍历,让其分别和输入的两个结点做比较。
2)如果当前结点比两个结点的值都大,那么最低公共祖先应该位于当前结点的左子树中。
3)如果当前结点比两个结点的值都小,那么最低公共祖先应该位于当前结点的右子树中。
4)如果当前结点在两个结点的值之间,那么最低公共祖先就是当前结点。

代码如下:

TreeNode* SameNode(TreeNode* pRoot,TreeNode *pNode1,TreeNode *pNode2){
    if(pRoot==NULL||pNode1==NULL||pNode2==NULL)
        return NULL;
    int big=(pNode1->val>pNode2->val)?pNode1->val:pNode2->val;
    int small=(pNode1->val<pNode2->val)?pNode1->val:pNode2->val;
    TreeNode *res;
    if(big==small){
        cout<<"Two Node should different "<<endl;
        return res;
    }
    if((small<pRoot->val)&&(big>pRoot->val)){
        return pRoot;
    }else if((small<pRoot->val)&&(big<pRoot->val)){
        res=SameNode(pRoot->left,pNode1,pNode2);
    }else if((small>pRoot->val)&&(big>pRoot->val)){
        res=SameNode(pRoot->right,pNode1,pNode2);
    }
    return res;
}

树为普通二叉树,有父指针

这里写图片描述

如果树中的每个结点,除根结点外,都有一个指向父结点的指针,那么这个问题就可以转换成求两个链表的第一个公共结点。例如假设树结点中指向父结点的指针是parent, 那么从树的每一个叶子结点开始都有一个由指针parent串起来的链表,这些链表的尾指针都是树的根结点。输入两个结点,那么这两个结点位于两个链表上,他们的最低公共祖先也就是两个链表的第一个公共结点。如输入D跟F,D所在的链表为D-B-A, F所在链表为F-C-A,所以最低公共祖先为A。代码如下:

int GetLength(TreeNode *pNode) {
    int len = 0;
    TreeNode *p = pNode;
    while (p != NULL) {
        len++;
        p = p->parent;
    }
    return len;
}
TreeNode *walkstep(TreeNode *pNode, int step) {
    while (step--) {
        pNode = pNode->parent;
    }
    return pNode;
}
TreeNode* SameNode(TreeNode* pRoot, TreeNode *pNode1, TreeNode *pNode2) {
    if (pRoot == NULL || pNode1 == NULL || pNode2 == NULL)
        return NULL;
    int n1 = GetLength(pNode1);
    int n2 = GetLength(pNode2);
    if (n1>n2) {
        pNode1 = walkstep(pNode1, n1 - n2);
    }
    else {
        pNode2 = walkstep(pNode2, n2 - n1);
    }
    if (pNode1 == pNode2) {
        return pNode1->parent;
    }
    while (pNode1 != NULL) {
        if (pNode1 == pNode2) {
            return pNode1;
        }
        else {
            pNode1 = pNode1->parent;
            pNode2 = pNode2->parent;
        }
    }
    return NULL;
}

树为普通二叉树,无父指针

这里写图片描述

在这样的情况下,我们可以首先利用前序遍历来得到根结点到某个结点的路径。当遍历两个得到输入结点的两条路径,问题转换成了求两个路径构成的链表的最后公共结点。代码如下:

#include <iostream>
#include <vector>
using namespace std;
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int val) :val(val), left(NULL), right(NULL) {}
};
bool GetNodePath(TreeNode* pHead, TreeNode* pNode, vector<TreeNode*>& path)
{
    if (pHead == pNode)
        return true;

    path.push_back(pHead);

    bool found = false;
    if (pHead->left != NULL)
        found = GetNodePath(pHead->left, pNode, path);
    if (!found && pHead->right)
        found = GetNodePath(pHead->right, pNode, path);
    if (!found)
        path.pop_back();
    return found;
}
TreeNode *GetLastCommonNode(const vector<TreeNode*> &path1, const vector<TreeNode*> &path2) {
    vector<TreeNode*>::const_iterator it1 = path1.begin();
    vector<TreeNode*>::const_iterator it2 = path2.begin();
    TreeNode *plast = NULL;

    while (it1 != path1.end() && it2 != path2.end()) {
        if (*it1 == *it2) {
            plast = *it1;
        }
        it1++;
        it2++;
    }
    return plast;
}
TreeNode* SameNode(TreeNode* pRoot, TreeNode *pNode1, TreeNode *pNode2) {
    if (pRoot == NULL || pNode1 == NULL || pNode2 == NULL)
        return NULL;
    vector<TreeNode*> path1;
    GetNodePath(pRoot, pNode1, path1);

    vector<TreeNode*> path2;
    GetNodePath(pRoot, pNode2, path2);


    return GetLastCommonNode(path1, path2);
}

int main() {

    /*
    // 
    //              4
    //            /   \
    //           2     6
    //         /  \   / \
    //        1   3   5  7
    */
    TreeNode *n1 = new TreeNode(1);
    TreeNode *n2 = new TreeNode(2);
    TreeNode *n3 = new TreeNode(3);
    TreeNode *n4 = new TreeNode(4);
    TreeNode *n5 = new TreeNode(5);
    TreeNode *n6 = new TreeNode(6);
    TreeNode *n7 = new TreeNode(7);
    n4->left = n2;
    n4->right = n6;
    n2->left = n1;
    n2->right = n3;
    n6->left = n5;
    n6->right = n7;
    TreeNode *res = SameNode(n4, n1, n3);
    cout << res->val << endl;

        //         8
    //        /
    //       9
    //      /
    //     10
    //    /
    //   11
    //  /
    // 12
    TreeNode *n8 = new TreeNode(8);
    TreeNode *n9 = new TreeNode(9);
    TreeNode *n10 = new TreeNode(10);
    TreeNode *n11 = new TreeNode(11);
    TreeNode *n12 = new TreeNode(12);

    n8->left = n9;
    n9->left = n10;
    n10->left = n11;
    n11->left = n12;

    TreeNode *res1 = SameNode(n8, n9, n10);
    cout << res1->val << endl;
}

总结

在求树的相关问题中,我们在找不到思路的时候,可以尝试着让树转换成链表的相关问题,再尝试着去解决,说不定这里会有喷火龙!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值