二叉树系列(2)前序中序后序遍历的递归和非递归实现
2013-09-01 23:31
330 查看
递归地对二叉树进行遍历的做法比较好理解。有一颗树,长成这个样子。
下面分别是对它进行三序遍历的结果:
前序递归
中序递归
后序递归
递归的写法非常直观好理解。比较困难的是非递归的写法。而且,比较不对称的结果是,三种顺序的非递归写法的难度是不同的。容易程度是,前序>中序>后续。
先从最容易的非递归前序遍历开始。基本思路是,维护一个栈。对于当前节点,输出它,然后依次入栈当前节点的右儿子,左儿子,然后弹栈。不断进行这个过程直到栈空。
中序非递归的基本思路和前序非递归的基本思路类似,也要借助栈,不过,中序非递归需要把当前节点的左儿子,左孙子,左重孙......等一路左到叶结点的所有左节点全部压栈之后,再弹栈,再把弹栈的栈顶的右儿子入栈。
另外一种写法
后序遍历的非递归写法有一部分和中序遍历的非递归写法类似,它们都会先入栈当前节点的所有左后代。后序遍历更困难的一点是,当出栈一个节点时,需要判断这是否是它的第一次出栈,如果是第一次出栈,说明当前节点的有节点还没有被访问,所以当前节点不宜访问,于是重新进栈,并且把它的右儿子进栈。如果是第二次出栈,则说明已经访问了它的右儿子了,因此可以访问当前节点了。实现的方法参考了这里,如下。
第一种后序遍历的非递归实现,基于preorder前序遍历进行修改。
第二种后序遍历的非递归实现,基于inorder 中序遍历进行修改。
第三种后序遍历的非递归实现,用一个变量来表示当前元素是否是第2次出现在栈顶,如果是第2次出现则可以弹出,否则说明右孩子还没有访问到,当前元素应该继续入栈。
完整的头文件和源文件在下一篇中给出。
下面分别是对它进行三序遍历的结果:
前序递归
//前序递归 void pre_order(link t, void (*visit)(link)) { if(!t) return; visit(t); pre_order(t->l, visit); pre_order(t->r, visit); }前序遍历输出结果
中序递归
//中序递归 void in_order(link t, void (*visit)(link)) { if(!t) return; in_order(t->l, visit); visit(t); in_order(t->r, visit); }中序遍历输出结果
后序递归
//后序递归 void post_order(link t, void (*visit)(link)) { if(!t) return; post_order(t->l, visit); post_order(t->r, visit); visit(t); }后序遍历输出结果
递归的写法非常直观好理解。比较困难的是非递归的写法。而且,比较不对称的结果是,三种顺序的非递归写法的难度是不同的。容易程度是,前序>中序>后续。
先从最容易的非递归前序遍历开始。基本思路是,维护一个栈。对于当前节点,输出它,然后依次入栈当前节点的右儿子,左儿子,然后弹栈。不断进行这个过程直到栈空。
//前序非递归 void pre_order_2(link t, void (*visit)(link)) { if(t==NULL) return; std::stack<link> s; link p = t; s.push(p); while(!s.empty()) { p = s.top(); s.pop(); visit(p); if(p->r != NULL) s.push(p->r); if(p->l != NULL) s.push(p->l); } }
中序非递归的基本思路和前序非递归的基本思路类似,也要借助栈,不过,中序非递归需要把当前节点的左儿子,左孙子,左重孙......等一路左到叶结点的所有左节点全部压栈之后,再弹栈,再把弹栈的栈顶的右儿子入栈。
//中序非递归 void in_order_2(link t, void (*visit)(link)) { std::stack<link> s; link p=t; while( p!=NULL || !s.empty())// p!=NULL这个条件是为了兼容第1次循环的条件设置。 { while(p!=NULL) { s.push(p); p=p->l; } //找到当前节点的最深最深最深的左儿子 if(!s.empty()) { p=s.top(); visit(p); s.pop(); p=p->r; } } }
另外一种写法
class Solution { //Leetcode的题目 public: vector<int> inorderTraversal(TreeNode *root) { vector<int> res_vec; if(root==NULL) return res_vec; TreeNode* cur_node=root; std::stack<TreeNode*> stack_left; while(cur_node!=NULL){ stack_left.push(cur_node); cur_node=cur_node->left; } while(!stack_left.empty()){ cur_node=stack_left.top(); res_vec.push_back(cur_node->val); cur_node=cur_node->right; stack_left.pop(); while (cur_node!=NULL) { stack_left.push(cur_node); cur_node=cur_node->left; } } return res_vec; } };
后序遍历的非递归写法有一部分和中序遍历的非递归写法类似,它们都会先入栈当前节点的所有左后代。后序遍历更困难的一点是,当出栈一个节点时,需要判断这是否是它的第一次出栈,如果是第一次出栈,说明当前节点的有节点还没有被访问,所以当前节点不宜访问,于是重新进栈,并且把它的右儿子进栈。如果是第二次出栈,则说明已经访问了它的右儿子了,因此可以访问当前节点了。实现的方法参考了这里,如下。
第一种后序遍历的非递归实现,基于preorder前序遍历进行修改。
class Solution { //第一种,基于前序遍历进行修改 public: vector<int> postorderTraversal(TreeNode *root) { vector<int> res; stack<TreeNode*> node_stack; if(root==NULL) return res; node_stack.push(root); TreeNode* cur=root; TreeNode* pre=NULL; while(!node_stack.empty()){ cur=node_stack.top(); if((cur->left==NULL&&cur->right==NULL)||((pre!=NULL)&&(pre==cur->left || pre==cur->right))) { //为什么基于前序遍历改后序遍历,这里是 (pre==cur->left || pre==cur->right) 这两个条件? //因为栈的构建顺序依次是cur, cur->right, cur->left,所以pre可能是左子树或右子树。 //如果基于中序遍历改后序遍历,则条件变为了(cur->right == pre),因为中序遍历的栈的构建顺序是cur的左儿子,左左儿子,左左左儿子,cur->right。这个顺序说明pre只能是cur的右子树。 res.push_back(cur->val); node_stack.pop(); pre=cur; }else{ if(cur->right!=NULL) node_stack.push(cur->right); if(cur->left!=NULL) node_stack.push(cur->left); } } return res; } };
第二种后序遍历的非递归实现,基于inorder 中序遍历进行修改。
class Solution { //第二种,基于中序遍历进行修改 public: vector<int> postorderTraversal(TreeNode *root) { stack<TreeNode*> node_stack; vector<int> result; TreeNode* p = root; TreeNode* cur = NULL; TreeNode* pre = NULL; while (p != NULL) { node_stack.push(p); p=p->left; } while (!node_stack.empty()) { pre = cur; cur = node_stack.top(); if ( (cur->left == NULL && cur->right == NULL) || (cur->right == pre)) { //此时是基于中序遍历改后序遍历,条件变为了(cur->right == pre),因为中序遍历的栈的构建顺序是cur的左儿子,左左儿子,左左左儿子,cur->right。这个顺序说明pre只能是cur的右子树。 //如果基于前序遍历改后序遍历,相应条件变为 (pre==cur->left || pre==cur->right) 这两个条件。 //因为栈的构建顺序依次是cur, cur->right, cur->left,所以pre可能是左子树或右子树。 node_stack.pop(); result.push_back(cur->val); }else{ cur = cur->right; while (cur != NULL) { node_stack.push(cur); cur=cur->left; } } } return result; } };
第三种后序遍历的非递归实现,用一个变量来表示当前元素是否是第2次出现在栈顶,如果是第2次出现则可以弹出,否则说明右孩子还没有访问到,当前元素应该继续入栈。
//后序非递归第三种 void post_order_3(link t, void (*visit)(link)) { std::stack<tag_node*> s; link p=t; tag_node *temp; while(p!=NULL||!s.empty()) { while(p!=NULL) { tag_node* t_node=(tag_node*)malloc(sizeof(tag_node)); t_node->node_link=p; t_node->is_first=true; s.push(t_node); p=p->l; } if(!s.empty()) { temp=s.top(); s.pop(); if(temp->is_first==true) //表示是第一次出现在栈顶 { temp->is_first=false; s.push(temp); p=temp->node_link->r; } else //第二次出现在栈顶 { visit(temp->node_link); free(temp); p=NULL; //避免下一次循环的时候重复找P的最深左子节点 } } } return; }
完整的头文件和源文件在下一篇中给出。
相关文章推荐
- 【应聘笔记系列】二叉树的递归与非递归遍历实现
- 二叉树的遍历:先序中序后序遍历的递归与非递归实现及层序遍历
- 二叉树系列(3)层序遍历的非递归实现
- 微软面试100题系列---二叉树的遍历递归和非递归实现
- 数据结构(二叉树系列)先序创建三种遍历和求深度(递归实现)
- c++实现二叉树的非递归创建以及非递归先序、中序、后序遍历
- 二叉树递归遍历的python实现
- 数据结构---二叉树的前序、中序、后序遍历的递归和非递归 实现(C++)
- 实现平衡二叉排序树的各种算法(包括二叉树的递归遍历、非递归遍历)
- 二叉树的遍历:前序、中序、后序、层序的非递归实现
- 非递归实现二叉树的遍历
- 利用栈实现二叉树的先序,中序,后序遍历的非递归操作
- 二叉树非递归遍历----前中后及层序的java实现
- 二叉树 前序、中序、后序、层次遍历及非递归实现 查找、统计个数、比较、求深度的递归实现
- java实现二叉树的三种遍历算法(递归)
- 二叉树三种遍历算法的递归和非递归实现(C++)
- C++实现二叉树所有操作 -- 创建递归遍历迭代遍历拷贝清空查找
- 二叉树先序、中序、后序遍历的非递归实现
- 二叉树的遍历 递归非递归 思路和 java实现
- 二叉树的遍历的迭代和递归实现方式