问题定义:
输入两棵二叉树A和B,判断B是不是A的子结构。
思路:
要查找树A中是否存在和树B结构一样的子结构,我们可以分成两步:第一步在树A中找到和B的根节点的值一样的节点R,第二步再判断A中以R为根节点的子树是不是包含和树B一样的结构。(关于建树部分可以参考:http://blog.csdn.net/lalor/article/details/7618120)
程序如下:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
struct BinaryTreeNode
{
int m_nValue;
struct BinaryTreeNode *m_pLeft;
struct BinaryTreeNode *m_pRight;
};
//后序遍历
void Traverse( BinaryTreeNode * root);
BinaryTreeNode* Construct(int *preorder, int *inorder, int lenght);
BinaryTreeNode* ConstructCore(int *startPreorder, int *endPreorder, int *startInorder, int *endInorder);
bool HasSubTree(BinaryTreeNode *parent, BinaryTreeNode *child);
int main(int argc, char* argv[])
{
int preOrder[] = {5, 4, 8, 9, 6, 8, 9, 2};
int inOrder[] = {9, 8, 6, 4, 5, 9, 8, 2};
int preOrder2[] = {8, 9, 2};
int inOrder2[] = {9, 8, 2 };
BinaryTreeNode *parent = Construct(preOrder, inOrder, sizeof(inOrder) / sizeof(inOrder[0]));
BinaryTreeNode *child = Construct(preOrder2, inOrder2, sizeof(inOrder2) / sizeof(inOrder2[0]));
cout << "A树的后序遍历的结果:" << endl;
Traverse(parent);
cout << endl;
cout << "B树的后序遍历的结果:" << endl;
Traverse(child);
cout << endl;
if (HasSubTree(parent, child))
{
cout << "B 是 A 的一棵子树" << endl;
}
else
{
cout << "B 不是 A 的一棵子树" << endl;
}
return 0;
}
BinaryTreeNode* Construct(int *preorder, int *inorder, int lenght)
{
if (preorder == NULL || inorder == NULL || lenght <= 0)
{
return NULL;
}
return ConstructCore(preorder, preorder + lenght - 1, inorder, inorder + lenght - 1);
}
BinaryTreeNode* ConstructCore(int *startPreorder, int *endPreorder, int *startInorder, int *endInorder)
{
int rootValue = startPreorder[0];
BinaryTreeNode *root = new BinaryTreeNode();
root->m_nValue = rootValue;
root->m_pLeft = root->m_pRight = NULL;
if (startPreorder == endPreorder)
{//先序遍历已经结束了,那这个时候一定是插入最后一个节点,则应该满足下面的if语句,否则输入的数据有误
if (startInorder == endInorder && *startPreorder == *startInorder)
{
return root;
}
else
{
cout << "Invalid input" << endl;
exit(-1);
}
}
int *rootInorder = startInorder;
while (rootInorder <= endInorder && *rootInorder != rootValue)
{
++rootInorder;
}
if (rootInorder <= endInorder && *rootInorder != rootValue)
{
cout << "Invalid input" << endl;
exit(-1);
}
int leftLength = rootInorder - startInorder;
int *leftPreorderEnd = startPreorder + leftLength;
if (leftLength > 0)
{
root->m_pLeft = ConstructCore(startPreorder + 1, leftPreorderEnd, startInorder, rootInorder - 1);
}
if (leftLength < endPreorder - startPreorder)
{
root->m_pRight = ConstructCore(leftPreorderEnd + 1, endPreorder, rootInorder + 1, endInorder);
}
return root;
}
void Traverse( BinaryTreeNode * root)
{
if (root == NULL)
{
return;
}
else
{
Traverse(root->m_pRight);
cout << root->m_nValue << " ";
Traverse(root->m_pLeft);
}
}
bool HasSubTree(BinaryTreeNode *parent, BinaryTreeNode *child)
{
//如何child=NULL,则说明child已经到达叶子了,无论parent是否到达一个叶子节点,都应该返回真
if (child == NULL)
{
return true;
}
//首先,如果程序执行到这里,则child一定不是NULL,如果parent=NULL,则说明parent不包含child
if (parent == NULL)
{
return false;
}
if (parent->m_nValue == child->m_nValue)
{//当if条件满足的时候,则继续递归判断它们的左子树和右子树是否相等,只有两者相等才返回真
return (HasSubTree(parent->m_pLeft, child->m_pLeft) && HasSubTree(parent->m_pRight, child->m_pRight));
}
else
{//if
return (HasSubTree(parent->m_pLeft, child) || HasSubTree(parent->m_pRight, child));
}
}
ps:以上程序是我看到这道题目以后自己先思考,想出来的一个方法,与下面的方法类似,不过感觉我的方法更简洁一点。
参考资料:
http://zhedahht.blog.163.com/blog/static/25411174201011445550396/
题目:二叉树的结点定义如下:
struct TreeNode
{
int m_nValue;
TreeNode* m_pLeft;
TreeNode* m_pRight;
};
输入两棵二叉树A和B,判断树B是不是A的子结构。
例如,下图中的两棵树A和B,由于A中有一部分子树的结构和B是一样的,因此B就是A的子结构。
1 8
/ \ / \
8 7 9 2
/ \
9 2
/ \
4 7
分析:这是2010年微软校园招聘时的一道题目。二叉树一直是微软面试题中经常出现的数据结构。对微软有兴趣的读者一定要重点关注二叉树。
回到这个题目的本身。要查找树A中是否存在和树B结构一样的子树,我们可以分为两步:第一步在树A中找到和B的根结点的值一样的结点N,第二步再判断树A中以N为根结点的子树是不是包括和树B一样的结构。
第一步在树A中查找与根结点的值一样的结点。这实际上就是树的遍历。对二叉树这种数据结构熟悉的读者自然知道我们可以用递归的方法去遍历,也可以用循环的方法去遍历。由于递归的代码实现比较简洁,面试时如果没有特别要求,我们通常都会采用递归的方式。下面是参考代码:
bool HasSubtree(TreeNode* pTreeHead1, TreeNode* pTreeHead2)
{
if((pTreeHead1 == NULL && pTreeHead2 != NULL) ||
(pTreeHead1 != NULL && pTreeHead2 == NULL))
return false;
if(pTreeHead1 == NULL && pTreeHead2 == NULL)
return true;
return HasSubtreeCore(pTreeHead1, pTreeHead2);
}
bool HasSubtreeCore(TreeNode* pTreeHead1, TreeNode* pTreeHead2)
{
bool result = false;
if(pTreeHead1->m_nValue == pTreeHead2->m_nValue)
{
result = DoesTree1HaveAllNodesOfTree2(pTreeHead1, pTreeHead2);
}
if(!result && pTreeHead1->m_pLeft != NULL)
result = HasSubtreeCore(pTreeHead1->m_pLeft, pTreeHead2);
if(!result && pTreeHead1->m_pRight != NULL)
result = HasSubtreeCore(pTreeHead1->m_pRight, pTreeHead2);
return result;
}
在上述代码中,我们递归调用hasSubtreeCore遍历二叉树A。如果发现某一结点的值和树B的头结点的值相同,则调用DoesTree1HaveAllNodeOfTree2,做第二步判断。
在面试的时候,我们一定要注意边界条件的检查,即检查空指针。当树A或树B为空的时候,定义相应的输出。如果没有检查并做相应的处理,程序非常容易崩溃,这是面试时非常忌讳的事情。由于没有必要在每一次递归中做边界检查(每一次递归都做检查,增加了不必要的时间开销),上述代码只在HasSubtree中作了边界检查后,在HasSubtreeCore中作递归遍历。
接下来考虑第二步,判断以树A中以N为根结点的子树是不是和树B具有相同的结构。同样,我们也可以用递归的思路来考虑:如果结点N的值和树B的根结点不相同,则以N为根结点的子树和树B肯定不具有相同的结点;如果他们的值相同,则递归地判断他们的各自的左右结点的值是不是相同。递归的终止条件是我们到达了树A或者树B的叶结点。参考代码如下:
bool DoesTree1HaveAllNodesOfTree2(TreeNode* pTreeHead1, TreeNode* pTreeHead2)
{
if(pTreeHead2 == NULL)
return true;
if(pTreeHead1 == NULL)
return false;
if(pTreeHead1->m_nValue != pTreeHead2->m_nValue)
return false;
return DoesTree1HaveAllNodesOfTree2(pTreeHead1->m_pLeft, pTreeHead2->m_pLeft) &&
DoesTree1HaveAllNodesOfTree2(pTreeHead1->m_pRight, pTreeHead2->m_pRight);
}
本文已经收录到《剑指Offer——名企面试官精讲典型编程题》一书中,有改动,书中的分析讲解更加详细。欢迎关注。