1.二叉树的遍历与重构
先前一直对二叉树的问题一直很害怕,经常涉及到二叉树的前中后遍历与重构问题,比如:
- 给定前序遍历序列和中序遍历序列,要你求出后序遍历序列,或者给定中序遍历序列和后序遍历序列,要你求出前序遍历序列;
- 其次,如果只给一个指向根节点的结点指针,要你利用递归法或者甚至迭代法完整无误的白板写出相应的代码可能就有些压力了;
- 再有甚者,给定前序遍历序列和中序遍历序列,要你重构该二叉树并返回指向根结点的结点指针。
1.1二叉树的遍历
由于二叉树问题涉及到结构体与结构体指针问题,凡是涉及到指针操作的都相对比较头疼。处理二叉树遍历与重构问题,其一个核心的知识点就是必须得明白前中后序遍历的原理。所谓的前中后遍历,无非就是根结点的访问次序问题。其中
- 前序遍历:根结点—>左孩子结点—>右孩子结点
- 中序遍历:左孩子结点—>根结点—>右孩子结点
- 后序遍历:左孩子结点—>右孩子结点—>根结点
首先,让我们来看看前中后序遍历。如前面所述,二叉树的遍历方法有两种,第一种是递归算法,第二种是迭代算法。
其中,递归算法的思想就是:从根结点开始,如果该结点指针非空,就一直递归(自身调用)下去,直到遇到空结点指针为止终止递归调用。Ps:递归算法,从形式结构上看是非常简洁的,但里面的递归调用涉及很多临时变量的,自身会利用栈资源对函数调用中断产生的变量进行压栈和出栈操作,如果递归的终止条件没有考虑好或者递归次数太大,很可能会导致深度递归导致有限的栈资源被消耗完,出现栈资源溢出。
//递归算法 伪代码 以前序遍历为例
function(pointer)
if(pointer!=nullptr)
pointer->value;
function(pointer->leftchild);
function(pointer->rightchild);
其次,迭代算法的主要思想也和递归类似,只是它不像递归算法在自身函数调用的时候自行对函数调用中断产生的临时变量进行压栈处理,可能得自己根据需要保留结点信息。下面以前序遍历的迭代算法为例,给出相应的代码。该博文最后给出了完整的测试用例!
void OutputPreOrder(TreeNode* root){
if(root==nullptr)
return;
stack<TreeNode*> s;//用于存储结点信息
s.push(root);
TreeNode* node=nullptr;
while(!s.empty()){
node=s.top();
s.pop();
cout<<node->val<<' ';
//如果右结点不为空,先将右结点压栈,然后再压左结点,等出栈的时候左结点先出栈。
if(node->right!=nullptr)
s.push(node->right);
if(node->left!=nullptr)
s.push(node->left);
}
}
1.2二叉树的重构
(以前序遍历和中序遍历重构二叉树为例)
要点:在二叉树的前序遍历序列中,第一个数字总是树的根节点的值。但在中序遍历序列中,根节点的值大都在序列的中间,<