2.3.4 树
树是一种在实际编程中经常会用到的数据结构,它的逻辑很简单,除根节点外每个节点只有一个父节点,根节点没有负节点。除叶节点外所有节点都有一个或多个子节点,叶节点没有子节点,父节点和子节点之间用指针链接。由于树的操作涉及到大量指针,因此与树有关的面试题都不太容易,当面试官想考查应聘者复杂指针操作情况下写代码的能力,他往往会想到与树有关的面试题。
面试时提到的树,大部分是二叉树。所谓二叉树是树的一种特殊情况,在二叉树中每个节点只能有两个子节点。在二叉树中最重要的操作莫过于遍历,即按照某种顺序访问树中的所有节点,通常树有如下几种遍历方式:
(1)前序遍历:根节点-》左子节点-》右子节点,10->6->4->8->14->12->16;
(2)中序遍历:左子节点-》根节点-》右子节点,4->6->8->10->12->14->16;
(3)后序遍历:左子节点-》右子节点-》根节点,4-》8->6->12->16->14->10;
遍历方式是根据根节点的访问来命名的:
10
6 14
4 8 12 16
这三种遍历都有递归和循环两种不同的实现方法,每种遍历的递归实现都比循环实现要简洁的多。很多面试官喜欢直接或间接考查遍历(详见面试题26“树的子结构"、面试题34”二叉树中和为某一值的路径“、面试题55”二叉树的深度“的具体代码实现,面试题7”重建二叉树“、面试题33”二叉搜索树的后序遍历序列“也是考查对遍历特点的理解。
(1)宽度优先遍历:先访问树的第一层节点,再访问树的第二层节点。。。一直到访问到最下面一层节点。同一层节点,从左到右访问。我们可以对包括二叉树在内的所有树进行宽度优先遍历,例子中按照宽度优先遍历,顺序是10->6->14->4->8->12->16.
面试题32”从上到下打印二叉树“就是在考查宽度优先遍历。
二叉树有很多特例,比如二叉搜索树。在二叉搜索树中,左子节点总是小于或等于根节点,右子节点总是大于或等于根节点。我们可以平均在O(logn)时间内根据数值在二叉搜索树中找到一个节点。二叉搜索树的面试题有很多,如面试题36”二叉搜索树与双向链表“,面试题68”树中两个节点的最低公共祖先".
二叉树的另外两个特例是堆和红黑树。堆分为最大堆和最小堆,在最大堆中根节点的值最大,在最小堆中的根节点的值最小。快速找最大值和最小值可以用堆来解决。红黑树,把树中的节点定义为红黑两种颜色,并通过规则确保从根节点到叶节点最长路径的长度不会超过最短路径的两倍。在C++ 的STL中,set,multiset,map,multimap等数据结构都是基于红黑树实现的。与堆和红黑树相关的面试题,请参考面试题40“最小的k个数”。
面试题7:重建二叉树
题目:输入某二叉树前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不包含重复的数字,例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并输出根节点。
在函数ConstructCore中,我们先根据前序遍历序列的第一个数字创建根节点,接下来在中序遍历序列中找到跟节点的位置,这样就能确定左右子树节点的数量,在前序遍历序列和中序遍历序列中划分了左右子树节点的值之后,我们就可以递归地调用函数ConstructCore去分别构建它的左右子树。
测试用例:
(1)普通二叉树(完全二叉树,不完全二叉树);
(2)特殊二叉树(所有节点都没有右子节点的二叉树,所有节点都没有左子节点的二叉树,只有一个节点的二叉树);
(3)特殊输入测试(二叉树根节点为nullptr,输入的前序遍历序列和中序遍历序列不匹配)。
本题考点:
(1)对二叉树的前序遍历序列和中序遍历序列的理解程度;
(2)考查应聘者分析复杂问题的能力,用递归的方式解决。
代码:
#include <exception>
#include <cstdio>
struct BinaryTreeNode
{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};
BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder,
int* endInorder);
BinaryTreeNode* Construct(int* preorder, int* inorder, int length)
{
if(preorder == nullptr || inorder == nullptr || length <= 0)
return nullptr;
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 = nullptr;
if(startPreorder == endPreorder)
{
if(startInorder == endInorder && *startPreorder == *startInorder)
return root;
else
throw std::exception("Invalid input.");
}
int* rootInorder = startInorder;
while(rootInorder <= endInorder && *rootInorder != rootValue)
++ rootInorder;
if(rootInorder == endInorder && *rootInorder != rootValue)
throw std::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;
}
void Test(char* testName, int* preorder, int* inorder, int length)
{
if(testName != nullptr)
printf("%s begins:\n", testName);
printf("The preorder sequence is : ");
for(int i = 0; i < length; ++i)
printf("%d ", preorder[i]);
printf("\n");
printf("The inorder sequence is : ");
for(int i = 0; i < length; ++ i)
printf("%d ", inorder[i]);
printf("\n");
try
{
BinaryTreeNode* root = Construct(preorder, inorder, length);
PrintTree(root);
DestroyTree(root);
}
catch(std::exception& exception)
{
printf("Invalid Input.\n");
}
}
void Test1()
{
const int length = 8;
int preorder[length] = {1, 2, 4, 7, 3, 5, 6, 8};
int inorder[length] = {4, 7, 2, 1, 5, 3, 8, 6};
Test("Test1", preorder, inorder, length);
}
void Test2()
{
const int length = 5;
int preorder[length] = { 1, 2, 3, 4, 5};
int inorder[length] = {5, 4, 3, 2, 1};
Test("Test2", preorder, inorder, length);
}
void Test3()
{
const int length = 5;
int preorder[length] = {1, 2, 3, 4, 5};
int inorder[length] = {1, 2, 3, 4, 5};
Test("Test3", preorder, inorder, length);
}
void Test4()
{
const int length = 1;
int preorder[length] = {1};
int inorder[length] = {1};
Test("Test4", preorder, inorder, length);
}
void Test5()
{
const int length = 7;
int preorder[length] = {1, 2, 4, 5, 3, 6, 7};
int inorder[length] = {4, 2, 5, 1, 6, 3, 7};
Test("Test5", preorder, inorder, length);
}
void Test6()
{
Test("Test6", nullptr, nullptr, 0);
}
void Test7()
{
const int length = 7;
int preorder[length] = {1, 2, 4, 5, 3, 6, 7};
int inorder[length] = {4, 2, 8, 1, 6, 3, 7};
Test("Test7: for unmatched input", preorder, inorder, length);
}
int main(int argc, char* argv[])
{
Test1();
Test2();
Test3();
Test4();
Test5();
Test6();
Test7();
return 0;
}