递归地对二叉树进行遍历的做法比较好理解。有一颗树,长成这个样子。
下面分别是对它进行三序遍历的结果:
前序递归
//前序递归
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;
}
};
//后序非递归第三种
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;
}
完整的头文件和源文件在下一篇中给出。