《程序员面试金典》(第六版)习题:仅为记录一下以加强印象,不为商业用途,如有侵权请联系删除。以下源码和解释参考了书中源码以及解释。
算法递归访问当前节点的左右子树中的节点,当子树中含有节点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;
}
}
改进后的算法与之前的算法流程一样,只不过返回值除了节点的指针外还有一个布尔变量来表明该节点是不是寻找的两个节点的第一个共同祖先。其实也可以通过先检查节点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;
}