1.题目
输入两棵二叉树A和B,判断B是不是A的子结构。二叉树结点的定义如下:
struct BinaryTreeNode
{
double m_dbValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};
如图中的两棵二叉树,由于A中有一部分子树的结构和B是一样的,因此B是A的子结构。
2.就题论题
和链表相比,树中的指针操作更多也更复杂,因此与树相关的问题通常会比链表的要难。如果想加大面试的难度,则树的题目是很多面试官的选择。面对大量的指针操作,我们要更加小新,否则一不留神就会在代码中留下隐患。
现在回到这道题目本身。要查找树A中是否存在和树B结构一样树,我们可以分成两步:第一步,在树A中找到和树B的根结点的值一样的结点R;第二步,判断树A中以R为根结点的子树是不是包含和树B一样的结构。
以上面的两棵树为例来详细分析这个过程。首先试着在树A中找到值为8(树B的根结点的值)的结点。从树A的根结点开始遍历,我们发现它的根结点的值就是8。接着判断树A的根结点下面的子树是不是含有和树B一样的结构,如图二所示。在树A中,根结点的左子节点的值是8,而树B的根结点的左子节点是9,对应的结点不同。
因此我们仍然需要遍历树A,接着查找值为8的结点。我们在树的第二层中找到了一个值为8的结点,然后进行第二步判断,即判断这个结点下面的子树是否含有和树B一样的结构,如图三所示。于是我们遍历这个结点下面的子树,先后得到两个子节点9和2,这和树B的结构完全相同。此时我们在树A中找到了一棵和树B的结构一样的子树,因此树B是树A的子结构。
第一步在树A中查找与根结点的值一样的结点,这实际就是树的遍历。对二叉树这种数据结构熟悉的读者自然知道可以用递归的方法去建立,也可以用循环的方法去遍历。由于递归的代码实现比较简洁,面试时如果没有特别要求,那么我们通常会采用递归的方式。下面是参考代码:
bool HasSubtree(BinaryTreeNode* pRoot1,BinaryTreeNode* pRoot2)
{
bool result=false;
if(pRoot!=nullpter&&pRoot2!=nullptr)
{
if(Equal(pRoot1->m_dbValue,pRoot2->m_dbValue))
result=DoesTree1HaveTree2(pRoot1,pRoot2);
if(!result)
result=HashSubtree(pRoot1->m_pLeft,pRoot2);
if(!result)
result=HasSubtree(pRoot1->m_pRight,pRoot2);
}
}
在面试的时候,我们一定要注意边界条件的检查,即检查空指针。当树A或树B为空的时候,定义相应的输出。如果没有检查并进行相应的处理,则程序非常容易崩溃,这是面试时非常忌讳的事情。
在上述代码中,我们递归调用HasSubtree遍历二叉树A。如果发现某一结点的值和树B的头接单的值相同,则调用DoesTree1HaveTree2,进行第二步判断。
第二步是判断树A中以R为根结点的子树是不是和树B具有相同的结构。同样,我们也可以用递归的思路来考虑:如果结点R的值树B的根结点不相同,则以R为根结点的子树和树B肯定不具有相同的结点;如果他们的值相同,则递归判断他们各自的左右结点的值是不是相同。递归的终止条件是我们达到了树A或者树B的叶结点。参考代码如下:
bool DoesTree1HaveTree2(BinaryTreeNode* pRoot1,BinaryTreeNode* pRoot2)
{
if(pRoot2==nullptr)
return true;
if(pRoot1==nullptr)
return true;
if(!Equal(pRoot->m_dbValue,pRoot2->m_dbValue))
return false;
return DoesTree1HaveTree2(pRoot1->m_pLeft,pRoot2->m_pLeft)&&DoesTree1HaveTree2(pRoot1->m_pRight,pRoot2->mpRight);
}
我们注意到上述代码有多处判断一个指针是不是nullptr,这样做是为了避免视图访问空指针而造成程序崩溃,同时也设置了递归调用的退出条件。在写遍历树的代码的时候一定要高度警惕,在每一处需要访问地址的时候都要问自己这个地址有没有可能是nullptr、如果是nullptr则该怎么处理。
面试小提示:
与二叉树相关的代码有大量的指针操作,在每次使用指针的时候,我们都要问自己这个指针有没有可能是nullptr,如果是nullptr则该怎么处理。
为了确保自己所写的代码完整、正确,在写出代码之后,应聘者至少要用几个测试用例来检验自己的程序:树A和树B的头结点有一个或者两个都是空指针;在树A和树B中所有结点都只有左子节点或者右子结点;树A和树B的结点中含有分叉。只有这样才能写出让面试官满意的鲁棒代码。
一个细节值得我们注意:本题中结点中值的类型为double。在判断两个结点的值是不是相等时,不能直接写pRoot1->m_dbValue==pRoot2->m_dbValue,这是因为在计算机内表示小数时(包括float和double型小数)都有误差。判断两个小数是否相等,只能判断他们之差的绝对值是不是在一个很小的范围内。如果两个数相差很小,就可以认为他们相等。这就是我们定义函数Equal的原因。
bool Equal(double num1,double num2)
{
if((num1-num2>-0.0000001)&&(num1-num2<0.0000001))
return true;
else
return false;
}