二叉树的后序遍历的非递归实现
对于二叉树的后序遍历可以以递归实现比较简单,也可以以迭代(非递归)实现。本文主要讲解如何实现二叉树后序遍历的非递归实现。
方法一:使用两个栈
对于二叉树遍历的非递归实现,一般都是要使用栈来进行作为辅助的空间。
申请两个栈s1, s2。后序遍历的步骤如下:
- 申请一个栈记为s1, 然后初始将根节点进行入栈s1
- 从s1中弹出结点记做为cur, 然后将cur的不为空的左右孩子进行入栈s1。将弹出的cur进行入栈s2。
- 不断重复上面的步骤1和步骤2,直到s1为空
- 从s2中弹出元素并打印,打印序列就是后序遍历的顺序。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if(!root) return res;
stack<TreeNode*> s1;
stack<TreeNode*> s2;
s1.push(root);
while(!s1.empty()){
TreeNode* peek = s1.top();
s1.pop();
s2.push(peek);
if(peek->left) s1.push(peek->left);
if(peek->right) s1.push(peek->right);
}
while(!s2.empty()){
res.push_back(s2.top()->val);
s2.pop();
}
return res;
}
};
方法二:使用一个栈
第一种实现:
本实现类似树的非递归的前序遍历,只是将左右孩子在入栈的时候的顺序进行调换一个,最后对收集的序列再进行逆置就行了。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> s;
vector<int> res;
if(root==NULL) return res;
s.push(root);
while(!s.empty()){
TreeNode* peek = s.top();
res.push_back(peek->val);
s.pop();
if(peek->left!=NULL) s.push(peek->left);
if(peek->right!=NULL) s.push(peek->right);
}
reverse(res.begin(), res.end());
return res;
}
};
第二种实现:
上面的这个方法是借助一个栈进行实现,但是需要最终还需要逆置一下。下面的这个方法仍然是使用一个栈进行实现,但是不再需要最终再逆置。
维护两个变量,一个变量是cur表示的是最近一次弹出并打印的结点,peek表示栈的栈顶的结点,初始的时候cur为根结点,peek为空。
具体的步骤如下:
-
申请一个栈,将头结点压入栈中,维护两个变量,一个变量是cur表示的是最近一次弹出并打印的结点,peek表示栈的栈顶的结点,初始的时候cur为根结点,peek为空。
-
每次都令peek为栈顶的结点,但是不弹出,此时分为以下三种情况:
-1.1如果peek的左孩子不为空,并且cur不等于peek的左孩子也不等于peek的右孩子,则把peek的左孩子压入栈中。
-1.2如果情况一不成立,并且peek的右孩子不为空,并且cur不等于peek
的右孩子,则把右孩子压入栈中
-1.3 如果条件情况1和情况2都不成立,说明peek的左右孩子都已经打印完毕,那么就将当前的peek进行弹出打印即可。 -
重复步骤2,直到栈为空。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if(!root) return res;
stack<TreeNode*> s;
s.push(root);
TreeNode* cur = root;
TreeNode* peek = NULL;
while(!s.empty()){
peek = s.top();
if(peek->left && cur!=peek->left && cur!=peek->right){
s.push(peek->left);
}else if(peek->right && cur!=peek->right){
s.push(peek->right);
}else{
res.push_back(peek->val);
s.pop();
cur = peek;
}
}
return res;
}
};
第三种实现:
对于上面的这个使用一个栈的实现,其思路本质也是先不断将当前的左子树全部入栈,因此给出下面一种更加普适的非递归后序遍历,该方法也可以适用于前序和中序的非递归。主要思路如下:
- 声明一个栈,初始的时候,栈中不存放元素,让cur指针指向当前遍历的结点,如果栈不为空,或者当前遍历的结点cur不为空的话,就一直将cur进行入栈,并且更新cur为cur的左孩子。否则如果当前的结点为空,但是栈非空,while循环仍然可以继续,那么说明最近一次遍历的节点h的所有的左孩子以及左子孙都已经入栈,那么取出当前的栈顶元素,就是最下方的左子孙,该左子孙的左孩子为空,判断他的右孩子,如果为空或者等于h,那么就说明h的左孩子和右孩子都已经遍历过,那么就该遍历当前的栈顶元素,并更新h为当前的栈顶元素,并出栈。否则更新当前的cur为cur的右孩子。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if(!root) return res;
stack<TreeNode*> s;
TreeNode* cur = root;
TreeNode* pre = NULL;//用来记录上一个已经结点
while(!s.empty() || cur){
while(cur){
s.push(cur);
cur = cur->left;
}
cur = s.top();
//下面的判断是说明,右子树当前的cur的右子树已经遍历完了,可以进行遍历当前的结点了
if(!cur->right || cur->right==pre){
res.push_back(cur->val);
s.pop();
pre = cur;
cur = NULL;//这一步很重要,这一步能够使得在遍历完当前的cur以及其左右子树之后能够进一步往树的上方回溯
}else{
cur = cur->right;
}
}
return res;
}
};
因此主要的模板如下:
while(栈非空 || cur非空){//当cur为空但是,栈不为空的时候,其实就是往二叉树的上方进行回溯的过程
if(cur 非空){
}
else{
}
}
类似的前序遍历如下:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
//考虑使用非递归实现二叉树的中序遍历
stack<TreeNode*> s;
TreeNode* cur = root;
while(!s.empty() || cur){
while(cur){
res.push_back(cur->val);
s.push(cur);
cur = cur->left;
}
TreeNode* peek = s.top();
s.pop();
cur = peek->right;
}
return res;
}
};
类似的中序遍历的结果如下:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
//考虑使用非递归实现二叉树的中序遍历
stack<TreeNode*> s;
TreeNode* cur = root;
while(!s.empty() || cur){
while(cur){
s.push(cur);
cur = cur->left;
}
//此时已经把当前根节点的所有的左孩子都已经入栈
TreeNode* peek = s.top();
s.pop();
//取出当前的栈顶元素
res.push_back(peek->val);
cur = peek->right;
}
return res;
}
};
总结
综上,二叉树的前序、中序、后序的三种非递归实现,其实都可以使用普适的方法进行求解,其基本的思路都是将某一个未访问结点的左孩子不断入栈。