美文网首页
二叉树的遍历

二叉树的遍历

作者: 董成鹏 | 来源:发表于2017-09-30 07:07 被阅读0次

现在决定把自己很久以前的一些文章重新markdown一下,发到简书来,先从这篇二叉树的遍历说起的。
大家都知道二叉树的遍历分为前序遍历,中序遍历,后序遍历。记得大学学习这一部分的时候,觉得用递归就可以轻易实现,简直太简单了,所以也就没有认真学,不过后来面试的时候考官要写一下二叉树的中序遍历,而且不能用递归,当时就很尴尬了,所以现在把二叉树的每种遍历思想和方法都记录一下,做个备忘

递归的解法来解决前序中序和后续的遍历

这简直是太简单不过了

递归前序遍历

public static void preTranverse(TreeNode root){
  if(root != null){
    visit(root);
    preTranverse(root.leftChild);
    preTranverse(root.rightChild);
  }
}

是不是非常言简意赅,遍历的时候先访问本身,然后遍历左节点,接着遍历右节点,而且简单易懂,接下来直接写中序遍历和后续遍历,思路是一样的

递归中序遍历

public static void midTranverse(TreeNode root){
  if(root != null){
    midTranverse(root.leftChild);
    visit(root);
    midTranverse(root.rightChild);
  }
}

递归后续遍历

public static void postTranverse(TreeNode root){
  if(root != null){
    postTranverse(root.leftChild);
    postTranverse(root.rightChild);
    visit(root);
  }
}

这三种写法都简单明了,一看就懂。下面的重点是三种遍历方式的非递归实现

要理解非递归实现,我们就要先清楚三种遍历的逻辑过程,以中序遍历为例:

  • 想要访问节点p,就要先访问p的左子节点p.leftChild.
  • 想要访问p.leftChild,同样也要先访问p的左子节点的子节点,p.leftChild.leftChild
  • 依次类推,当p的左子节点以及左子节点的左子节点都访问完的时候,我们才可以访问P。这个时候P的指向应该是原来的root节点的最后一个左子节点
  • 当访问完P的时候,我们要借助P来对P的右节点进行访问.
  • 不断重复上一个过程,就可以中序遍历完整个二叉树

知道了这些,我们就可以大致写出代码了,代码中有注释,可以参考

public static void midTranverse(TreeNode root){
  TreeNode p = root;
  //stack用来存放我们第二和第三个步骤中遍历到的左子节点,我们要依靠这些左子节点来进入到他们对应的右树中去
  Stack<TreeNode> s = new Stack();
  while(p!= null || !s.isEmpty()){
    //按照思路,先让P节点的所有左子节点入栈
    while(p!=null){
      s.push(p);
      p = p.leftChild;
    }

    //这时候P应该为空,那么我们只能根据栈内是否有元素来判断是否要出栈了
    if(!s.isEmpty()){
      //注意,这里的if不能写成while,这样的话相当于全部出栈了,又回到了root节点,但是此时我们仅仅遍历了root节点左子树的所有左节点,还没有遍历左子树的所有右节点
      p=s.pop();
      //可以访问p了,因为这时候P指向root左子树的最后一个左子节点。
      visit(p);
      //虽然P是左子树的最后一个左子节点,但是P可能还会有右子节点,如果有的话,进入p的右子节点进行遍历,如果没有的话也没有关系,因为在下次大循环中P为空,还会继续取出我们原来栈内的元素进行遍历
      p=p.rightChild();
    }
  }
}

完整的代码就是这样,我们发现在外层循环的时候内层还有循环,这样效率不是很高,不过经过我们以上的分析,发现内层循环其实是可以省略的,代码如下

public static void midTranverse(TreeNode root){
  TreeNode p = root;
  Stack<TreeNode> s = new Stack();
  while(p!= null || !s.isEmpty()){
    if(p!=null){
      s.push(p);
      p=p.leftChild;
    }else{
      p = s.pop();
      visit(p);
      p=p.rightChild;
    }
  }
}

代码简洁了很多,基本思路没有变,都是当p不为空或者栈中还有元素的时候,不断循环,当p不为空的时候,我们要让p入栈,并进入p的左树,当p为空的时候,我们出栈,把栈顶元素赋给p,并进入p的右树。
这样就可以完成整个二叉树的中序遍历了。

前序遍历和中序遍历流程差不多,只是visit调用的实际不一样,前序遍历是在进入左树之前就会调用visit方法,中序遍历是在进入右树之前调用visit方法

非递归的后续遍历

后续遍历相比于前两种遍历困难的地方在于父节点必须在左右子树都遍历完的时候才能进行访问,我们需要有一个新的变量来记录是否已经访问完右子树了。

public static void postTranverse(TreeNode root){
  TreeNode p = root;
  TreeNode lastVisited = null;
  Stack<TreeNode> s = new Stack();
  
  //我们在判断是否可以访问一个节点时,都需要确保该节点的左子树和有子树都已经访问完了,为了能够确定该节点的左右子树都访问完了,先进入他左子树上每个节点是否都访问过
  while(p!=null){
    s.push(p);
    p=p.leftChild;
  }
  while(!s.isEmpty()){
    //取出栈顶元素
    p=s.pop();
    if(p.rightChild == null || p.rightChild == lastVisited){
      //当p的右子树为Null或者已经被访问过的时候,我们才能访问p
      visit(p);
      lastVisited = p;
    }else{
      //栈顶的元素现在右子树没有被访问过,则再次入栈,先不访问该元素
      s.push(p);
      p=p.rightChild;
      //然后去判断这个右子树是否被访问过
      while(p!=null){
        s.push(p);
        p=p.leftChild;
      }
    }
  }
}

相关文章

  • 二叉树 基础操作

    二叉树的使用 二叉树结构 先序创建二叉树 DFS 先序遍历二叉树 中序遍历二叉树 后序遍历二叉树 BFS 层次遍历...

  • 二叉树遍历

    二叉树 二叉树的存储结构 前序遍历 中序遍历 后序遍历 遍历代码 反转二叉树 深入学习二叉树 二叉树-你必须要懂!...

  • 二叉树操作

    树节点 逐行顺序解析二叉树 前序遍历二叉树 中序遍历二叉树 后序遍历二叉树 删除指定数值的节点 前序遍历顺序存储的...

  • 关于二叉树的算法题

    前序遍历中序遍历后序遍历判断是否是平衡二叉树判断是否是对称二叉树判断二叉树高度按照层遍历二叉树判断二叉树宽度

  • 二叉树三种遍历Swift代码实现

    二叉树的三种遍历 二叉树 前序遍历 中序遍历 后序遍历 另外 不得不说,得到二叉树的前序遍历和中序遍历的结果或者后...

  • 数据结构与算法之二叉树遍历(七)

    目录 前序遍历中序遍历后序遍历层序遍历遍历方式的选择条件根据遍历结果重构二叉树翻转二叉树计算二叉树的高度判断一棵树...

  • 二叉树的遍历

    二叉树的遍历 二叉树遍历 分为前序遍历、中序遍历和后序遍历。 前序遍历 (DLR) 先访问根节点,然后前序遍历左子...

  • 数据结构:树的实现和遍历(c++)

    (一)二叉树的遍历——递归实现 二叉树常见的遍历方式分为前序遍历、中序遍历和后序遍历。 1 前序遍历 前序遍历也叫...

  • 二叉树的蛇形层次遍历(LeetCode.103)

    题目 解析 首先参考二叉树的层次遍历层次遍历二叉树(LeetCode--102二叉树的层次遍历)[https://...

  • 算法精选题总结之二叉树类

    1.二叉树的中序遍历中序遍历的迭代方法中序遍历的递归方法2.二叉树前序遍历3.二叉树后续遍历4.二叉树的最近公共祖...

网友评论

      本文标题:二叉树的遍历

      本文链接:https://www.haomeiwen.com/subject/wzmwextx.html