4.8 首个共同祖先

     《程序员面试金典》(第六版)习题:仅为记录一下以加强印象,不为商业用途,如有侵权请联系删除。以下源码和解释参考了书中源码以及解释。
     算法递归访问当前节点的左右子树中的节点,当子树中含有节点p时返回节点p(此时子树中没有节点q),当子树中含有节点q时返回节点q(此时子树中没有节点p),如果节点p和节点q都不在当前节点的子树中则返回空指针。如果分别在当前节点的左右子树中找到了节点p和q则返回当前节点。一个例子以及递归过程如图1所示。

//最优化解法
class BinaryNode
{
private:
    int data;
    BinaryNode* left;
    BinaryNode* right;
public:
    BinaryNode(int value = 0, BinaryNode* pointer1 = nullptr, BinaryNode* pointer2 = nullptr)
    {
        data = value;
        left = pointer1;
        right = pointer2;
    }
    BinaryNode* getLeft()
    {
        return left;
    }
    BinaryNode* getRight()
    {
        return right;
    }
    int getData()
    {
        return data;
    }
};

BinaryNode* commonAncestor(BinaryNode* root, BinaryNode* p, BinaryNode* q)
{
    if (root == nullptr)
        return nullptr;//遇到根节点
    if (root == p && root == q)
        return root;
    BinaryNode* x = commonAncestor(root->getLeft(), p, q);
    if (x != nullptr && x != p && x != q)
        return x; //进入到这里说明已经在左子树中找到共同的祖先
    BinaryNode* y = commonAncestor(root->getRight(), p, q);
    if (y != nullptr && y != p && y != q)
        return y;//进入到这里说明已经在右子树中找到共同的祖先
    if (x != nullptr && y != nullptr)
    {
        return root; //进入到这里说明已经在不同子树中分别找到p和q
    }
    else if (root == p || root == q)
    {
        return root;//进入到这里说明root为要寻找的节点中的一个节点
    }
    else
    {
        //如果root不为要寻找的节点中的一个节点则返回在子树中找到的要寻找的节点中的节点护或者返回空节点
        return x == nullptr ? y : x;
    }
}
 
图1.
     以上没有说明要寻找的两个节点中一个节点是另一个节点的子节点时的情况,这时以上算法也可以返回正确的值。如图2所示。但是当我们要寻找的两个节点中一个在树中而另一个不在树中时,这时算法应该返回空指针,但是以上算法返回的是指向在树中的节点的指针。以下代码是针对这种情况的改进。
 
图2.

     改进后的算法与之前的算法流程一样,只不过返回值除了节点的指针外还有一个布尔变量来表明该节点是不是寻找的两个节点的第一个共同祖先。其实也可以通过先检查节点p和q是不是都在树中来解决这个问题。

//改进的优化算法
class Result
{
    private:
        BinaryNode* node;
        bool isAncestor;
    public:
        Result(BinaryNode* value1=nullptr, bool value2=false)
        {
            node = value1;
            isAncestor=value2;
        }
        BinaryNode* getNode()
        {
            return node;
        }
        bool getIsAncestor()
        {
            return isAncestor;
        }
};
BinaryNode* commonAncestor(BinaryNode* root, BinaryNode* p, BinaryNode* q)
{
    Result* r = commonAncestorHelper(root, p, q);
    if (r->getIsAncestor())
    {
        return r->getNode();
    }
    return nullptr;
}
Result* commonAncestorHelper(BinaryNode* root, BinaryNode* p, BinaryNode* q)
{
    if (root == nullptr)
        return new Result(nullptr,false);//遇到根节点
    if (root == p && root == q)
        return new Result(root, true);
    Result* x = commonAncestorHelper(root->getLeft(), p, q);
    if (x->getIsAncestor())
        return x; //进入到这里说明已经在左子树中找到共同的祖先
    Result* y = commonAncestorHelper(root->getRight(), p, q);
    if (y->getIsAncestor())
        return y;//进入到这里说明已经在右子树中找到共同的祖先
    if (x->getNode() != nullptr && y->getNode() != nullptr)
    {
        return new Result(root,true); //进入到这里说明已经在不同子树中分别找到p和q
    }
    else if (root == p || root == q)
    {
        //进入到这里说明root为要寻找的节点中的一个节点,这里也考虑到了要寻找的两个节点中一个为另一个节点的子节点的情况。
        bool value = (x->getNode() != nullptr || y->getNode() != nullptr);
        return new Result(root,value);
    }
    else
    {
        //如果root不为要寻找的节点中的一个节点则返回在子树中找到的要寻找的节点中的节点护或者返回空节点
        return new Result(x->getNode() != nullptr ? x->getNode() : y->getNode());
    }
}

     这里的算法首先确保节点p和q都在树中。然后检查节点p和q是否在同一个子树中。如果节点p和q都在一个子树中(左子树或右子树),则递归访问对应的子树。如果发现p和q分别分布在不同的子树中,则根节点为它们的共同祖先。

bool cover(BinaryNode* root, BinaryNode*p)
{
    if (root == nullptr)
        return false;
    if (root == p)
        return true;
    return cover(root->getLeft(),p) || cover(root->getRight(),p);
}
BinaryNode* commonAncestorHelper(BinaryNode* root, BinaryNode* p, BinaryNode* q)
{
    if (root == nullptr|| root == p || root == q )
        return root;

    bool pIsOnLeft =cover(root->getLeft(),p) ;
    bool qIsOnLeft = cover(root->getRight(), p);


    if (pIsOnLeft != qIsOnLeft)
        return root;

    BinaryNode* childSide = pIsOnLeft ? root->getLeft(): root->getRight();
    return commonAncestorHelper(childSide, p, q);
}
BinaryNode* commonAncestor(BinaryNode* root, BinaryNode* p, BinaryNode* q)
{
    //确保节点p和q都在树中
    if (!cover(root, p) || !cover(root, q))
    {
        return nullptr;
    }
    return commonAncestorHelper(root,p,q);
}

     以上算法假定节点没有指向父节点的指针,以下算法假定节点中有指向父节点的指针。以下算法首先解决节点p或节点q不在树中以及节点p或节点q为节点q或节点p的子节点的情况。然后算法获取节点p的兄弟节点以及父节点并检查节点q是否在节点p的兄弟节点中,如果在,则此时的父节点即为所求。否则当前父节点的兄弟节点为当前兄弟节点,当前父节点的父节点为当前父节点。迭代以上过程直到遇到节点q是在节点p的兄弟节点中,此时父节点即为所求。

class BinaryNode
{
private:
    int data;
    BinaryNode* father;
    BinaryNode* left;
    BinaryNode* right;
public:
    BinaryNode(int value = 0, BinaryNode* pointer1 = nullptr, BinaryNode* pointer2 = nullptr, BinaryNode* pointer3 = nullptr)
    {
        data = value;
        left = pointer1;
        right = pointer2;
        father = pointer3;
    }
    BinaryNode* getLeft()
    {
        return left;
    }
    BinaryNode* getRight()
    {
        return right;
    }
    BinaryNode* getFather()
    {
        return father;
    }
};

bool cover(BinaryNode* root, BinaryNode* p)
{
    if (root == nullptr)
        return false;
    if (root == p)
        return true;
    return cover(root->getLeft(), p) || cover(root->getRight(), p);
}

BinaryNode* getSibling(BinaryNode* node)
{
    if (node == nullptr || node->getFather() == nullptr)
        return nullptr;
    BinaryNode* parent = node->getFather();
    return parent->getLeft() == node ? parent->getRight() : parent->getLeft();
}

BinaryNode* commonAncestor(BinaryNode* root, BinaryNode* p, BinaryNode* q)
{
    if (!cover(root, p) || !cover(root, q))
    {
        return nullptr;
    }
    else if (cover(p, q))
    {
        return p;
    }
    else if (cover(q, p))
    {
        return q;
    }
    BinaryNode* sibling = getSibling(p);
    BinaryNode* parent = p->getFather();
    while (!cover(sibling,q))
    {
        sibling = getSibling(parent);
        parent = parent->getFather();

    }
    return parent;
}

     该算法也是假定节点中有指向父节点的指针。算法首先检测节点p和节点q的深度,然后将深度较深的节点向上迭代节点p和节点q的深度差的数值个节点。然后这两个节点同时向父节点迭代并检查它们的父节点是否为同一个节点,如果为同一个节点或者其中一个为空节点则返回。该算法的时间复杂度为 O ( d ) O(d) O(d),d为深度较深的那个节点的深度。时间复杂度为 O ( 1 ) O(1) O(1)

int depth(BinaryNode* p)
{
    int depth = 0;
    while (p != nullptr)
    {
        p = p->getFather();
        depth++;
    }
    return depth;
}

BinaryNode* goUp(BinaryNode* node,int depth)
{
    while (depth > 0 && node != nullptr)
    {
        node = node->getFather();
        depth--;
    }
    return node;
}

BinaryNode* commonAncestor(BinaryNode* p, BinaryNode* q)
{
    int delta = depth(p) - depth(q);
    BinaryNode* first = delta > 0 ? q : p;
    BinaryNode* second= delta > 0 ? p : q;
    second = goUp(second,abs(delta));

    while (first!=second && first!=nullptr && second!=nullptr)
    {
        first = first->getFather();
        second = second->getFather();
    }
    return first==nullptr || second==nullptr?nullptr:first;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qqssss121dfd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值