博客参考自《剑指offer》
1、重建二叉树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
分析:在二叉树的前序遍历中,第一个数字总是树的根结点的值,中序遍历中,根结点的值在序列的中间,左子树的结点位于根结点的左边,右子树的结点位于根结点的右边。前序遍历中的第一个数字就是根节点。扫描中序遍历序列,就能确定根节点的位置。根据中序遍历的特点,就能知道根节点左边就是左子树,右边是右子树。这样在前序遍历中也可以知道哪些是左子树,哪些是右子树。接下来就可以通过递归完成。
BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder, int* endInorder);
BinaryTreeNode* Construct(int* preorder, int* inorder, int length)
{
if(preorder == NULL || inorder == NULL || length <= 0)
return NULL;
return ConstructCore(preorder, preorder + length - 1,
inorder, inorder + length - 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(startInorder == endInorder && *startPreorder == *startInorder)
return root;
else
throw exception("Invalid input.");
}
// 在中序遍历中找到根结点的值
int* rootInorder = startInorder;
while(rootInorder < endInorder && *rootInorder != rootValue)
++ rootInorder;
if(rootInorder == endInorder && *rootInorder != rootValue)
throw exception("Invalid input.");
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;
}
2、二叉树的深度
题目:输入一个二叉树的根节点,求该树的深度。
分析:从根节点到叶节点依次经过的结点(含根节点和叶节点)形成树的一条路径,最长路径的长度为树的深度。树的深度等于其左右子树深度的较大者加1
int TreeDepth(BinaryTreeNode* pRoot)
{
if(pRoot == NULL)
return 0;
int nLeft = TreeDepth(pRoot->m_pLeft);
int nRight = TreeDepth(pRoot->m_pRight);
return (nLeft > nRight) ? (nLeft + 1) : (nRight + 1);
}
3、树的子结构
题目:输入两棵二叉树A和B,判断B是不是A的子结构。
分析:第一步在树A中找到和B的根节点的值一样的结点R。第二步再判断树A中以R为根节点的子树是不是包含和树B一样的结构
bool HasSubtree(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2);
bool DoesTree1HaveTree2(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2);
bool HasSubtree(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2)
{
bool result = false;
if(pRoot1 != NULL && pRoot2 != NULL)
{
if(pRoot1->m_nValue == pRoot2->m_nValue)
result = DoesTree1HaveTree2(pRoot1, pRoot2);
if(!result)
result = HasSubtree(pRoot1->m_pLeft, pRoot2);
if(!result)
result = HasSubtree(pRoot1->m_pRight, pRoot2);
}
return result;
}
bool DoesTree1HaveTree2(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2)
{
if(pRoot2 == NULL)
return true;
if(pRoot1 == NULL)
return false;
if(pRoot1->m_nValue != pRoot2->m_nValue)
return false;
return DoesTree1HaveTree2(pRoot1->m_pLeft, pRoot2->m_pLeft) &&
DoesTree1HaveTree2(pRoot1->m_pRight, pRoot2->m_pRight);
}
4、二叉树中和为某一值的路径
题目:输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径
分析:从树的根节点开始往下一直到叶结点所经过的结点形成一条路径。当用前序遍历的方式访问到某一个结点时,我们把该结点添加到路径上,并累加该结点的值,如果该结点为叶结点并且路径中结点值的和刚好等于输入的整数,则当前的路径符合要求,则打印出来。如果当前结点不是叶结点,则继续访问它的子节点。当前结点访问结束后,递归函数将自动回到它的父结点。因此我们在函数退出之前要在路径上删除当前结点并减去当前结点的值,以确保返回父节点时路径刚好是从根节点到父节点的路径。
void FindPath(BinaryTreeNode* pRoot, int expectedSum, std::vector<int>& path, int& currentSum);
void FindPath(BinaryTreeNode* pRoot, int expectedSum)
{
if(pRoot == NULL)
return;
std::vector<int> path;
int currentSum = 0;
FindPath(pRoot, expectedSum, path, currentSum);
}
void FindPath
(
BinaryTreeNode* pRoot,
int expectedSum,
std::vector<int>& path,
int& currentSum
)
{
currentSum += pRoot->m_nValue;
path.push_back(pRoot->m_nValue);
// 如果是叶结点,并且路径上结点的和等于输入的值
// 打印出这条路径
bool isLeaf = pRoot->m_pLeft == NULL && pRoot->m_pRight == NULL;
if(currentSum == expectedSum && isLeaf)
{
printf("A path is found: ");
std::vector<int>::iterator iter = path.begin();
for(; iter != path.end(); ++ iter)
printf("%d\t", *iter);
printf("\n");
}
// 如果不是叶结点,则遍历它的子结点
if(pRoot->m_pLeft != NULL)
FindPath(pRoot->m_pLeft, expectedSum, path, currentSum);
if(pRoot->m_pRight != NULL)
FindPath(pRoot->m_pRight, expectedSum, path, currentSum);
// 在返回到父结点之前,在路径上删除当前结点,
// 并在currentSum中减去当前结点的值
currentSum -= pRoot->m_nValue;
path.pop_back();
}
5、从上往下打印二叉树
题目:从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印。
分析:每一次打印一个结点的时候,如果该结点有子结点,则把该结点的子节点放到一个队列的末尾,接下来到队列的头部取出最早进入队列的结点,重复前面的打印操作,直至队列中所有的结点都打印出来。
void PrintFromTopToBottom(BinaryTreeNode* pRoot)
{
if(pRoot == NULL)
return;
std::deque<BinaryTreeNode *> dequeTreeNode;
dequeTreeNode.push_back(pRoot);
while(dequeTreeNode.size())
{
BinaryTreeNode *pNode = dequeTreeNode.front();
dequeTreeNode.pop_front();
printf("%d ", pNode->m_nValue);
if(pNode->m_pLeft)
dequeTreeNode.push_back(pNode->m_pLeft);
if(pNode->m_pRight)
dequeTreeNode.push_back(pNode->m_pRight);
}
}