zoukankan      html  css  js  c++  java
  • 二叉树的前序、中序、后序遍历(递归、非递归)实现

    本文部分来源于CSDN兰亭风雨大牛的原创。链接为http://blog.csdn.net/ns_code/article/details/12977901

     因为感觉大牛讲的很好,所以这里的文字讲解采用大牛的,大家可以直接看原创!代码部分是我自己的,leetcode代码,可在leetcode Accepted

    二叉树是一种非常重要的数据结构,很多其他数据机构都是基于二叉树的基础演变过来的。二叉树有前、中、后三种遍历方式,因为树的本身就是用递归定义的,因此采用递归的方法实现三种遍历,不仅代码简洁且容易理解,但其开销也比较大,而若采用非递归方法实现三种遍历,则要用栈来模拟实现(递归也是用栈实现的)。下面先简要介绍三种遍历方式的递归实现,再详细介绍三种遍历方式的非递归实现

    一、三种遍历方式的递归实现(比较简单,这里不详细讲解)

     

    1、先序遍历——按照“根节点-左孩子-右孩子”的顺序进行访问

     1 /**
     2  * Definition for binary tree
     3  * struct TreeNode {
     4  *     int val;
     5  *     TreeNode *left;
     6  *     TreeNode *right;
     7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     8  * };
     9  */
    10 class Solution {
    11 public:
    12     vector<int> preorderTraversal(TreeNode *root) {
    13       vector<int> root_vec;
    14       vector<int> left_vec;
    15       vector<int> right_vec;
    16       if(root==NULL) return root_vec;
    17       root_vec.push_back(root->val);
    18       if(root->left!=NULL) left_vec=preorderTraversal(root->left);
    19       if(root->right!=NULL) right_vec=preorderTraversal(root->right);
    20       root_vec.insert(root_vec.end(),left_vec.begin(),left_vec.end());
    21       root_vec.insert(root_vec.end(),right_vec.begin(),right_vec.end());
    22       return root_vec;
    23     }
    24 };
    View Code

    2、中序遍历——按照“左孩子-根节点-右孩子”的顺序进行访问。

     1 /**
     2  * Definition for binary tree
     3  * struct TreeNode {
     4  *     int val;
     5  *     TreeNode *left;
     6  *     TreeNode *right;
     7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     8  * };
     9  */
    10  /*recursive*/
    11 class Solution {
    12 public:
    13     vector<int> inorderTraversal(TreeNode *root) {
    14         vector<int> root_vec;
    15         vector<int> left_vec;
    16         vector<int> right_vec;
    17         if(root==nullptr) return root_vec;
    18         if(root->left!=nullptr) left_vec=inorderTraversal(root->left);
    19         root_vec.push_back(root->val);
    20         if(root->right!=nullptr) right_vec=inorderTraversal(root->right);
    21         left_vec.insert(left_vec.end(),root_vec.begin(),root_vec.end());
    22         left_vec.insert(left_vec.end(),right_vec.begin(),right_vec.end());
    23         return left_vec;
    24     }
    25 };
    View Code

    3、后序遍历——按照“左孩子-右孩子-根节点”的顺序进行访问。 

     1 /**
     2  * Definition for binary tree
     3  * struct TreeNode {
     4  *     int val;
     5  *     TreeNode *left;
     6  *     TreeNode *right;
     7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     8  * };
     9  */
    10 class Solution {
    11 public:
    12     vector<int> postorderTraversal(TreeNode *root) {
    13         vector<int> root_vec;
    14         vector<int> left_vec;
    15         vector<int> right_vec;
    16         if(root==NULL) return root_vec;
    17         if(root->left) left_vec=postorderTraversal(root->left);
    18         if(root->right) right_vec=postorderTraversal(root->right);
    19         root_vec.push_back(root->val);
    20         left_vec.insert(left_vec.end(),right_vec.begin(),right_vec.end());
    21         left_vec.insert(left_vec.end(),root_vec.begin(),root_vec.end());
    22         return left_vec;
    23     }
    24 };
    View Code

    二、三种遍历方式的非递归实现

        为了便于理解,这里以下图的二叉树为例,分析二叉树的三种遍历方式的实现过程。

                                                         

              1、前序遍历的非递归实现 

    根据先序遍历的顺序,先访问根节点,再访问左子树,后访问右子树,而对于每个子树来说,又按照同样的访问顺序进行遍历,上图的先序遍历顺序为:ABDECF。非递归的实现思路如下:

    对于任一节点P,

    1)输出节点P,然后将其入栈,再看P的左孩子是否为空;

    2)若P的左孩子不为空,则置P的左孩子为当前节点,重复1)的操作;

    3)若P的左孩子为空,则将栈顶节点出栈,但不输出,并将出栈节点的右孩子置为当前节点,看其是否为空;

    4)若不为空,则循环至1)操作;

    5)如果为空,则继续出栈,但不输出,同时将出栈节点的右孩子置为当前节点,看其是否为空,重复4)和5)操作;

    6)直到当前节点P为NULL并且栈空,遍历结束。

       

       下面以上图为例详细分析其先序遍历的非递归实现过程:

     

    首先,从根节点A开始,根据操作1),输出A,并将其入栈,由于A的左孩子不为空,根据操作2),将B置为当前节点,再根据操作1),将B输出,并将其入栈,由于B的左孩子也不为空,根据操作2),将D置为当前节点,再根据操作1),输出D,并将其入栈,此时输出序列为ABD;

    由于D的左孩子为空,根据操作3),将栈顶节点D出栈,但不输出,并将其右孩子置为当前节点;

    由于D的右孩子为空,根据操作5),继续将栈顶节点B出栈,但不输出,并将其右孩子置为当前节点;

    由于B的右孩子E不为空,根据操作1),输出E,并将其入栈,此时输出序列为:ABDE;

    由于E的左孩子为空,根据操作3),将栈顶节点E出栈,但不输出,并将其右孩子置为当前节点;

    由于E的右孩子为空,根据操作5),继续将栈顶节点A出栈,但不输出,并将其右孩子置为当前节点;

    由于A的右孩子C不为空,根据操作1),输出C,并将其入栈,此时输出序列为:ABDEC;

    由于A的左孩子F不为空,根据操作2),则将F置为当前节点,再根据操作1),输出F,并将其入栈,此时输出序列为:ABDECF;

    由于F的左孩子为空,根据操作3),将栈顶节点F出栈,但不输出,并将其右孩子置为当前节点;

    由于F的右孩子为空,根据操作5),继续将栈顶元素C出栈,但不输出,并将其右孩子置为当前节点;

    此时栈空,且C的右孩子为NULL,因此遍历结束。

     

       根据以上思路,前序遍历的非递归实现代码如下:

     1 /**
     2  * Definition for binary tree
     3  * struct TreeNode {
     4  *     int val;
     5  *     TreeNode *left;
     6  *     TreeNode *right;
     7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     8  * };
     9  */
    10 class Solution {
    11 public:
    12     vector<int> preorderTraversal(TreeNode *root) {
    13        vector<int> preorder_vec;
    14        TreeNode *p=root;//定义用来指向当前访问的节点的指针  
    15        if(p==NULL) return preorder_vec;//若为空树,则返回空vector
    16        stack<TreeNode *> treenode_stack;//创建一个空栈
    17        //直到当前节点p为NULL且栈空时,循环结束 
    18        while(p||!treenode_stack.empty())
    19        {
    20           //从根节点开始,输出当前节点,并将其入栈,  
    21           //同时置其左孩子为当前节点,直至其没有左孩子,及当前节点为NULL  
    22            preorder_vec.push_back(p->val);
    23            treenode_stack.push(p);
    24            p=p->left;
    25            //如果当前节点p为NULL且栈不空,则将栈顶节点出栈,  
    26           //同时置其右孩子为当前节点,循环判断,直至p不为空 
    27            while(!p&&!treenode_stack.empty())
    28            {
    29                p=treenode_stack.top();
    30                treenode_stack.pop();
    31                p=p->right;
    32            }
    33        }
    34     }
    35 };
    View Code

    2、中序遍历的非递归实现

    根据中序遍历的顺序,先访问左子树,再访问根节点,后访问右子树,而对于每个子树来说,又按照同样的访问顺序进行遍历,上图的中序遍历顺序为:DBEAFC。非递归的实现思路如下:

    对于任一节点P,

    1)若P的左孩子不为空,则将P入栈并将P的左孩子置为当前节点,然后再对当前节点进行相同的处理;

    2)若P的左孩子为空,则输出P节点,而后将P的右孩子置为当前节点,看其是否为空;

    3)若不为空,则重复1)和2)的操作;

    4)若为空,则执行出栈操作,输出栈顶节点,并将出栈的节点的右孩子置为当前节点,看起是否为空,重复3)和4)的操作;

    5)直到当前节点P为NULL并且栈为空,则遍历结束。

     

       下面以上图为例详细分析其中序遍历的非递归实现过程:

    首先,从根节点A开始,A的左孩子不为空,根据操作1)将A入栈,接着将B置为当前节点,B的左孩子也不为空,根据操作1),将B也入栈,接着将D置为当前节点,由于D的左子树为空,根据操作2),输出D;

    由于D的右孩子也为空,根据操作4),执行出栈操作,将栈顶结点B出栈,并将B置为当前节点,此时输出序列为DB;

    由于B的右孩子不为空,根据操作3),将其右孩子E置为当前节点,由于E的左孩子为空,根据操作1),输出E,此时输出序列为DBE;

    由于E的右孩子为空,根据操作4),执行出栈操作,将栈顶节点A出栈,并将节点A置为当前节点,此时输出序列为DBEA;

    此时栈为空,但当前节点A的右孩子并不为NULL,继续执行,由于A的右孩子不为空,根据操作3),将其右孩子C置为当前节点,由于C的左孩子不为空,根据操作1),将C入栈,将其左孩子F置为当前节点,由于F的左孩子为空,根据操作2),输出F,此时输出序列为:DBEAF;

    由于F的右孩子也为空,根据操作4),执行出栈操作,将栈顶元素C出栈,并将其置为当前节点,此时的输出序列为:DBEAFC;

    由于C的右孩子为NULL,且此时栈空,根据操作5),遍历结束。

     

        根据以上思路,中序遍历的非递归实现代码如下:

     1 /**
     2  * Definition for binary tree
     3  * struct TreeNode {
     4  *     int val;
     5  *     TreeNode *left;
     6  *     TreeNode *right;
     7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     8  * };
     9  */
    10  /*iteratively*/
    11 class Solution {
    12 public:
    13     vector<int> inorderTraversal(TreeNode *root) {
    14        stack<TreeNode *> inorder_stack;
    15        TreeNode * p=root;
    16        vector<int> inorder_vec;
    17        if(p==nullptr) return inorder_vec;//若为空树,则返回空vector
    18        while(p||!inorder_stack.empty())
    19        {
    20            if(p->left!=nullptr)//若左节点不空,当前节点进栈,并使右孩子为当前节点,继续判断
    21            {
    22                inorder_stack.push(p);
    23                p=p->left;
    24            }
    25            else          //如果左孩子为空,则输出当前节点,并将其右孩子设为当前节点,看其是否为空 
    26            {
    27                inorder_vec.push_back(p->val);
    28                p=p->right;
    29                //如果为空,且栈不空,则将栈顶节点出栈,并输出该节点,  
    30                //同时将它的右孩子设为当前节点,继续判断,直到当前节点不为空 
    31                while(!p&&!inorder_stack.empty())
    32                {
    33                    p=inorder_stack.top();
    34                    inorder_vec.push_back(p->val);
    35                    inorder_stack.pop();
    36                    p=p->right;
    37                }
    38            }
    39        }
    40        return inorder_vec;
    41        
    42     }
    43 };
    View Code

    3、后序遍历的非递归实现

    根据后序遍历的顺序,先访问左子树,再访问右子树,后访问根节点,而对于每个子树来说,又按照同样的访问顺序进行遍历,上图的后序遍历顺序为:DEBFCA。后序遍历的非递归的实现相对来说要难一些,要保证根节点在左子树和右子树被访问后才能访问,思路如下:

    对于任一节点P,

    1)先将节点P入栈;

    2)若P不存在左孩子和右孩子,或者P存在左孩子或右孩子,但左右孩子已经被输出,则可以直接输出节点P,并将其出栈,将出栈节点P标记为上一个输出的节点,再将此时的栈顶结点设为当前节点;

    3)若不满足2)中的条件,则将P的右孩子和左孩子依次入栈,当前节点重新置为栈顶结点,之后重复操作2);

    4)直到栈空,遍历结束。

       下面以上图为例详细分析其后序遍历的非递归实现过程:

    首先,设置两个指针:Cur指针指向当前访问的节点,它一直指向栈顶节点,每次出栈一个节点后,将其重新置为栈顶结点,Pre节点指向上一个访问的节点;

    Cur首先指向根节点A,Pre先设为NULL,由于A存在左孩子和右孩子,根据操作3),先将右孩子C入栈,再将左孩子B入栈,Cur改为指向栈顶结点B;

    由于B的也有左孩子和右孩子,根据操作3),将E、D依次入栈,Cur改为指向栈顶结点D;

    由于D没有左孩子,也没有右孩子,根据操作2),直接输出D,并将其出栈,将Pre指向D,Cur指向栈顶结点E,此时输出序列为:D;

    由于E也没有左右孩子,根据操作2),输出E,并将其出栈,将Pre指向E,Cur指向栈顶结点B,此时输出序列为:DE;

    由于B的左右孩子已经被输出,即满足条件Pre==Cur->lchild或Pre==Cur->rchild,根据操作2),输出B,并将其出栈,将Pre指向B,Cur指向栈顶结点C,此时输出序列为:DEB;

    由于C有左孩子,根据操作3),将其入栈,Cur指向栈顶节点F;

    由于F没有左右孩子,根据操作2),输出F,并将其出栈,将Pre指向F,Cur指向栈顶结点C,此时输出序列为:DEBF;

    由于C的左孩子已经被输出,即满足Pre==Cur->lchild,根据操作2),输出C,并将其出栈,将Pre指向C,Cur指向栈顶结点A,此时输出序列为:DEBFC;

    由于A的左右孩子已经被输出,根据操作2),输出A,并将其出栈,此时输出序列为:DEBFCA;

    此时栈空,遍历结束。

       根据以上思路,后序遍历的非递归实现代码如下:

     

     1 /**
     2  * Definition for binary tree
     3  * struct TreeNode {
     4  *     int val;
     5  *     TreeNode *left;
     6  *     TreeNode *right;
     7  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     8  * };
     9  */
    10 class Solution {
    11 public:
    12     vector<int> postorderTraversal(TreeNode *root) {
    13         vector<int> postorder_vec;
    14         TreeNode *cur=root; //定义指针,指向当前节点  
    15         TreeNode *pre=NULL;//定义指针,指向上一各访问的节点   
    16         if(cur==NULL) return postorder_vec;
    17         stack<TreeNode *> postorder_stack;//创建一个空栈  
    18         postorder_stack.push(cur);//先将树的根节点入栈
    19         //直到栈空时,结束循环
    20         while(!postorder_stack.empty())
    21         {
    22             cur=postorder_stack.top();//当前节点置为栈顶节点  
    23             if((cur->left==NULL&&cur->right==NULL)||
    24             ((pre!=NULL)&&(cur->left==pre||cur->right==pre)))
    25             {
    26             //如果当前节点没有左右孩子,或者有左孩子或有孩子,但已经被
    27             //访问输出,则直接输出该节点,将其出栈,将其设为上一个访问的节点  
    28                 postorder_stack.pop();
    29                 postorder_vec.push_back(cur->val);
    30                 pre=cur;
    31             }
    32             else
    33             {
    34                  //如果不满足上面两种情况,则将其右孩子左孩子依次入栈  
    35                 if(cur->right!=NULL) postorder_stack.push(cur->right);
    36                 if(cur->left!=NULL) postorder_stack.push(cur->left);
    37             }
    38         }
    39     }
    40 };
    View Code

    本博文写的非常粗糙,不断修改完善中。。。

  • 相关阅读:
    转:UFLDL_Tutorial 笔记(deep learning绝佳的入门资料 )
    转:使用RNN解决NLP中序列标注问题的通用优化思路
    CTR预估中GBDT与LR融合方案
    ZOJ1157, POJ1087,UVA 753 A Plug for UNIX (最大流)
    Gentoo:startx出现Failed to load module问题
    HTTP请求和响应2:方法(Method)
    SharePoint 2013 表单认证使用ASP.Net配置工具加入用户
    理解支持向量机(四)LibSVM工具包的使用
    LeetCode 14: Longest Common Prefix
    精通Hibernate——域对象之间的关系
  • 原文地址:https://www.cnblogs.com/zhoutaotao/p/3807071.html
Copyright © 2011-2022 走看看