题目:题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2, 4, 7, 3, 5, 6, 8}和中序遍历序列{4, 7, 2, 1, 5, 3, 8, 6},则重建出图2.6所示的二叉树并输出它的头结点。
1. 解题思路
要构建一颗二叉树,一定要有中序遍历,再加上先序或者后序遍历,然后按照:
(1) 先序遍历中第一个元素即为根节点root;
(2) 在中序遍历中查找根节点root的位置,将根节点左边的划分为左子树left,右边的为右子树right;
(3) 如果left不空,则令root->left = left,同理,right不空则令root->right = right;
(4) 重复(1)(2)(3),建立左子树和右子树。
整个过程是一个递归的过程,在“牛客网”上的ACE代码:
TreeNode * reConstructBinaryTree(std::vector<int> pre, std::vector<int> vin)
{
// 空的,或为叶节点,或为一颗空树
if (pre.empty() || vin.empty())
return nullptr;
// 先序遍历中的第一个元素就是根节点
TreeNode* root = new TreeNode(pre.front());
// 只有一个节点了
if (pre.size() == 1 && pre.front() == vin.front())
return root;
// 根节点在中序遍历中的位置
std::vector<int>::iterator rootIndex = std::find(vin.begin(), vin.end(), root->val);
// 如果在中序遍历序列中找不到,则抛出异常
if (rootIndex == vin.end() && *rootIndex != root->val)
return nullptr;
// 计算左子树中节点总个数
int leftCount = rootIndex - vin.begin();
// 计算右子树中节点总个数
// ATTENTION:vin.end()指向的是最后一个元素的后一个位置,所以要再减1
int rightCount = vin.end() - rootIndex - 1;
if (leftCount > 0)
{
// std::vector<int> vec(v.begin(), v.end()),这里不包括v.end()这个元素,也即vec = [v.begin(), v.end());故末尾要加1
std::vector<int> newPreLeft(pre.begin() + 1, pre.begin() + leftCount + 1);
std::vector<int> newInLeft(vin.begin(), rootIndex);
root->left = reConstructBinaryTree(newPreLeft, newInLeft);
}
if (rightCount > 0)
{
std::vector<int> newPreRight(pre.begin() + leftCount + 1, pre.end());
std::vector<int> newInRight(rootIndex + 1, vin.end());
root->right = reConstructBinaryTree(newPreRight, newInRight);
}
return root;
}
再加一个测试程序:
// 中序遍历,通过v_inOrderPrint返回遍历结果
void Solution::PrintTree(TreeNode * pRoot, std::vector<int>& v_inOrderPrint)
{
if (nullptr == pRoot)
return;
PrintTree(pRoot->left, v_inOrderPrint);
v_inOrderPrint.push_back(pRoot->val);
PrintTree(pRoot->right, v_inOrderPrint);
}
// test
int main()
{
std::vector<int> preorder{ 1, 2, 4, 7, 3, 5, 6, 8 };
std::vector<int> inorder = { 4, 7, 2, 1, 5, 3, 8, 6 };
Solution* s = new Solution();
TreeNode* root = s->reConstructBinaryTree(preorder, inorder);
std::vector<int> v_output;// 存放中序遍历的结果
s->PrintTree(root, v_output);
//for (auto& v : v_output)
// printf("%d ", v);
if (v_output == inorder)
printf("Pass!");
else
printf("Failed!");
return 0;
}
2. 知识笔记
- v.end()所指的位置: 最后一个元素的后一个位置,比如有一个vector数组std::vector< int > v{4, 7, 2, 1, 5, 3, 8, 6}的存储结构示意图如下表:
v.begin() = v[0] = 4, v.end()是v[7] = 6的后一个位置,相当于v[8]。v下标 0 1 2 3 4 5 6 7 v值 4 7 2 1 5 3 8 6 - const在不同地方的含义:(摘自《Effective C++(第三版)》“条款03”)
char greeting[] = "hello";
char* p = greeting; // non-const pointer, non-const data
const char* p = greeting; // non-const pointer, const data
char const *p = greeting; // 同const char* p
char* const p = greeting; // const pointer, non-const data
const char* const p = greeting; // const pointer, const data
// 类似T* const,即const pointer, non-const data
const std::vector<int>::iterator iter = v.begin();
// 类似const T*,即non-const pointer, const data
std::vector<int>::const_iterator cIter = v.begin();
总结:如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量(左对象,右指针)。对于迭代器,把iterator看作是星号,刚好与前面的相反(这个是最容易记错的。。。)
再看看const在函数的不同位置处的含义:
class TextBlock{
public:
// Constructor
explicit TextBlock(std::string& _text): text(_text) {}
// 返回的是一个const类型的std::size_t对象
const std::size_t GetTextLength();
// 返回的是一个const类型的std::size_t对象
// 最后的const修饰的是调用这个函数的对象
const std::size_t GetTextLength() const;
private:
std::string text;
};
其中const std::size_t GetTextLength()
返回的是一个const类型的std::size_t对象;而const std::size_t GetTextLength() const
返回类型与前者相同,最后的const
修饰的是调用该函数的对象,也就意味着在这个函数体内部,不允许更改成员变量的值。试想一个这样的情况:文本块只存放一行的数据(81个字符),而对超出的部分进行舍弃,根据这个场景我们来实现一下上面的函数:
const std::size_t MAX_LENGTH = 81; // 一行的长度
const std::size_t TextBlock::GetTextLength()
{
if(text.length() > MAX_LENGTH)
text = text.substr(0, MAX_LENGTH);
return text.length();
}
这样没问题,但是当我们实现const std::size_t TextBlock::GetTextLength() const
函数时,就会有错误了:在该函数体内不允许对成员变量text
进行修改。要想让程序通过编译,需要将成员变量text
的类型变成mutable
就可以了,也就是mutable std::string text
。
OK,上面解释了函数末尾带const
和不带const
的区别,下面再解决一个问题:代码复用。现在有两个新的成员函数:const char& operator[] (std::size_t pos) const
和char& operator[] (std::size_t pos)
,这两个函数的函数体内不需要做任何修改,但是要做一大堆的验证,而这一大堆验证在两个函数内都需要,为避免重复写这段代码,可以这样解决:
const char& TextBlock::operator[](std::size_t pos) const
{
// ...
// 一大堆验证
// ...
return text[pos];
}
char& TextBlock::operator[](std::size_t pos)
{
return const_cast<char&>( // 将返回类型的const属性转除
static_cast<const TextBlock&>(*this)[pos] // 为*this加上const,调用上面那个函数
);
}
注意,上面调用的顺序不要乱了(变量调用常量const,因为常量安全性高)!!!