二叉树递归进阶【下】
之前的前中后序遍历是递归实现的,都很简单,下面以题代练一下
非递归实现前、中、后序遍历
1. 二叉树的前序遍历
题目描述:144. 二叉树的前序遍历
题目描述
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
顺藤摸瓜
题目解析就不需要了,这前中后序谁都懂,关键是达到非递归的思想,已解决可能产生的栈溢出问题
要用非递归走前序,以前我们分成左子树和右子树根来看待,现在我们将其分为左路节点和左路节点上的右子树
利用栈我们可以存储左子树节点,然后用循环访问他的左路节点上的右子树
抽丝剥茧
先把所有的左子树节点放到栈中,然后依次访问当前cur的右子树节点,凡是访问到右子树节点,我就重新来cur去访问左路节点,直到cur走向空之后,pop掉stack中的top值,拿到下一个左路节点,这样的话我们利用栈的特性模拟了递归的操作,巧妙利用循环完成前序
Situation | 操作 | 结束条件 |
---|---|---|
大循环 | 前序遍历所有节点 | cur和栈均走到空 |
左路节点循环 | 左路节点走到底,依次push 进 stack | 左路全进,cur指向空 |
右路节点 | 栈中拿一个左路节点,访问右路节点 | / |
右路->左路 | 右路节点为根,访问自己的左路 | 左路全进,cur指向空 |
手到拈来
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> ret;
TreeNode *cur= root;
while(cur || !st.empty())//cur指向谁就表示开始前序访问这棵树
{
//1.访问左树节点,左路节点入栈
while(cur)
{
ret.push_back(cur->val);
st.push(cur);
cur=cur->left;
}
//2.依次取出左路节点的右子树出来访问
TreeNode* top = st.top();
st.pop();
//子问题形式访问右子树
cur=top->right;
}
return ret;
}
2. 二叉树的中序遍历
题目描述:94. 二叉树的中序遍历
抽丝剥茧
仿照前序遍历,我们可以实现中序遍历,中序类似的不就是先访问左子树,再访问左子树的右路
手到拈来
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> ret;
TreeNode *cur= root;
while(cur || !st.empty())//cur指向谁就表示开始前序访问这棵树
{
//1.访问左树节点,左路节点入栈
while(cur)
{
st.push(cur);
cur=cur->left;
}
//2.依次取出左路节点取出来时表示左子树已经访问完了
//访问右子树
TreeNode* top = st.top();
st.pop();
ret.push_back(top->val);
cur=top->right;
}
return ret;
}
#3. 二叉树的后序遍历
题目描述:145. 二叉树的后序遍历
抽丝剥茧
还是一样的,思考一下,父节点应该是至少要被访问多次,入栈之后从左子树回来有一次,再去右子树访问,直到第二次访问回到父亲节点的时候才能push_back进入vector
因此可以借助一个办法来识别第一次和第二次访问,如果第二次访问了说明上一个访问的节点就是右子树
那么只需要有一个prev指针,指向上一个被pop掉的节点就可以了
手到拈来
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> ret;
TreeNode *cur= root;
TreeNode* prev= nullptr;
while(cur || !st.empty())//cur指向谁就表示开始前序访问这棵树
{
//1.访问左树节点,左路节点入栈,但是暂时不能访问
while(cur)
{
st.push(cur);
cur=cur->left;
}
//2.去到一个栈顶元素,左路节点已经访问完了,去到了之后不能pop
//如果右路已经访问完了才可以访问当前父亲
TreeNode* top = st.top();
//3.要访问根节点要么右干脆是空的,或者是prev指向right
if(top->right==nullptr || top->right == prev)
{
st.pop();
ret.push_back(top->val);
prev=top;//prev指向这个访问过的节点
}
else
cur=top->right;
//否则访问右边,//vector存的是value,不是节点,所以设置一个指针
}
return ret;
}
4. 从前序与中序遍历序列构造二叉树
题目链接:105. 从前序与中序遍历序列构造二叉树
题目描述
顺藤摸瓜
给了前序和中序,而且题目说没有重复数字,那我们就可以构建一棵树了
我们一般可以通过前序确定根,然后通过中序确定左右子树的范围,然后递归确定左子树和右子树
抽丝剥茧
开始分析代码怎么写,首先这道题目还是使用递归,递归就要考虑递归参数
递归参数
按照题目给的递归参数肯定是不够的,我们发现对于前序我们所需要的是找根,那就是一个指针就可以了,但是对于中序可以发现其实我们要的是区间,所以所需要两个指针,也就是如下,我们整一个子函数,用来递归
_TreeNode* buildTree(vector<int>& preorder, int pi,vector<int>& inorder,int begin,int end) {
}
同时我们要注意因为我们需要的是同一个pi在里面++移动,如果不加的话上一层的++不会影响下一层,所以说还是要传引用
_TreeNode* buildTree(vector<int>& preorder, int& pi,vector<int>& inorder,int begin,int end) {
}
4.3.2 代码思路
先用前序创建根,然后在中序中去找到相同的根节点,这样就能够划分左右区间
这里我们需要确定返回条件
if(in_begin> in_end)
return nullptr;
注意不能取等于号,不然9就没有在该进的地方进去,等于表示还有一个值
手到拈来
TreeNode* _buildTree(vector<int>& preorder, int& pi,
vector<int>& inorder,int in_begin,int in_end) {
if(in_begin> in_end)
return nullptr;
//前序创建根
TreeNode*root= new TreeNode(preorder[pi]);
++pi;
//确定根在中序的位置
int rooti = in_begin;
while(rooti<= in_end)
{
if(root->val == inorder[rooti])
break;
else
rooti++;
}
//[int_begin,rooti-1]rooti[rooti+1,in_end]
root->left=_buildTree(preorder,pi,inorder,in_begin,rooti-1);
root->right=_buildTree(preorder,pi,inorder,rooti+1,in_end);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.size()==0)
return nullptr;
int i=0;
return _buildTree(preorder,i,inorder,0,inorder.size()-1);
}
5. 从中序与后序遍历序列构造二叉树
题目链接:106. 从中序与后序遍历序列构造二叉树
题目描述
顺藤摸瓜
这道题目我们将使用与之前完全相同的思路,只需要修改一下前序与后序之间的不同即可,前序的时候给出的vector是左子树右子树根,现在后序的话是左子树右子树根
我们还是可以通过后序确定根,然后通过中序确定左右子树的范围,然后递归确定左子树和右子树
抽丝剥茧
同样的写一个子函数
TreeNode* _buildTree(vector<int>& inorder, int in_begin,int in_end, vector<int>& postorder,int& pi) {
}
然后当我们在中序找到根节点之后,按照–pi,在后序中应该是先遇到右子树,在遇到左子树,所以我们应该先往右树递归,再往左树递归
//[int_begin,rooti-1]rooti[rooti+1,in_end]
root->right=_buildTree(inorder,rooti+1,in_end,postorder,pi);
root->left=_buildTree(inorder,in_begin,rooti-1,postorder,pi);
手到拈来
TreeNode* _buildTree(vector<int>& inorder, int in_begin,int in_end,
vector<int>& postorder,int& pi) {
if(in_begin> in_end)
return nullptr;
//后序创建根,根是最后一个
TreeNode*root=new TreeNode(postorder[pi]);
--pi;
//确定根在中序的位置
int rooti = in_begin;
while(rooti<= in_end)
{
if(root->val == inorder[rooti])
break;
else
rooti++;
}
//[int_begin,rooti-1]rooti[rooti+1,in_end]
root->right=_buildTree(inorder,rooti+1,in_end,postorder,pi);
root->left=_buildTree(inorder,in_begin,rooti-1,postorder,pi);
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
int i=postorder.size()-1;
return _buildTree(inorder,0,i,postorder,i);
}