数据结构 二叉树的 前序遍历,中序遍历,后续遍历的递归实现和非递归实现 TreeIterator 图解 代码

二叉树

这里需要了解的知识点

二叉树的 前序遍历,中序遍历,后续遍历的递归实现和非递归
二叉树的增删改查

二叉树的 前序遍历,中序遍历,后续遍历的递归实现和非递归实现 TreeIterator 图解

本次示例的代码由java实现.

先序遍历(递归)

1.代码

void preOrder(TreeNode root){
    if(root == null){
        return;
    }
    //输出节点的值.
    System.out.println(root.element);
  	//遍历左子树
    preOrder(root.left);
  	//遍历右子树
    preOrder(root.right);
}

先序遍历:输出语句在最前面,(所以叫做先序遍历)(上面的代码可以看出,进入先序遍历的函数后,不为空就会直接输出语句然后才继续递归嵌套)

2.解释图

image-20201116091105796

3.顺序图 (建议按照代码将这个顺序图进行一次复现)

先序遍历的顺序图,遇到节点就先输出

image-20201116091120002

中序遍历(递归)

中序遍历区别于先序遍历就是把输出语句放到中间,所以叫做中序遍历

1.代码

void inOrder(TreeNode root){
    if(root == null){
        return;
    }
    inOrder(root.left);
  	//输出节点的值
    System.out.println(root.element);
    inOrder(root.right);
}

因为打印语句在遍历左子树的后面,所以我们要遍历完成整个左子树才会输出该节点

2.解释图

image-20201116091541922

3.顺序图(建议按照代码将这个顺序图在草稿纸进行一次复现)

image-20201115095748325

后序遍历(递归)

同理,我想你应该已经想到了,没错,后续遍历就是把输出语句放到遍历左子树语句和遍历右子树语句的后面。

1.代码

void inOrder(Node root){
    if(root == null){
        return;
    }
    inOrder(root.leftChild);
    inOrder(root.rightChild);
    //输出节点的值.
    System.out.println(root.value);
}

2.解释图

image-20201115100209439

3.顺序图(建议按照代码将这个顺序图在草稿纸进行一次复现)

image-20201115100308749

前序遍历(非递归-基于栈)

递归的本质就是栈的压栈

因此递归可以实现的东西栈一定也能实现。

代码实现的核心就是使用栈来储存遍历的父节点们,让我们遍历左子树结束后还能回到父节点

1.代码

private void preOrder(TreeNode<E> root){
  //创建一个栈
  Stack<TreeNode<E>> stack = new Stack<>();
  //遍历到 最后一个节点的时候 ,该节点的左右子树都为空,且栈中的值应该也为空,
  //所以只要不是同时满足这2点都要进入循环。
  while (root != null || !stack.isEmpty()) {
    //遍历的顺序得知,先需要一直往左走,
    while (root != null) {
      //输出语句
      System.out.println(root.element);
      //压栈
      stack.push(root);
      //遍历左子树
      root = root.left;
    }
    //遍历顺序得知:左子树遍历完成,弹出父节点,然后遍历父节点的右子树.
    if (!stack.isEmpty()) {
      //左子树遍历结束后弹出左子树的父节点
      root = stack.pop();
      //遍历右子树
      root = root.right;
    }
  }
}

2.解释图

  • 如下的二叉树,遍历左子树,遍历一个入栈一个节点,依次入栈 A - B - D(并输出 A- B -D ) 。到D点时候,遍历D节点的左子树D.left,左子树D.left为空,结束8行的while循环,我们就弹出D节点,然后再去root 赋值为D的右子树,遍历D的右子树

image-20201115111614917

  • 然后D的右子树为空,我们再次 弹出B节点 ,继续遍历B节点的右节点(E),然后输出B的右节点(E)

image-20201115111703668

  • 建议 自己按照代码将前序遍历的整个过程在稿纸上复现一遍

3.遍历的顺序图,同前序遍历的递归方式

中序遍历(非递归-基于栈)

我想你应该已经猜到了,同理递归的中序遍历是移动输出语句到遍历左子树语句的后面,遍历右子树语句的前面,

中序遍历的非递归实现也是要移动输出语句到同样的位置

1.代码

private void inOrder(TreeNode<E> root){
  Stack<TreeNode<E>> treeNodes = new Stack<>();
  while (root!=null || !treeNodes.isEmpty()){
    while (root != null) {
      treeNodes.push(root);
      //遍历左子树
      root = root.left;
    }
    if (!treeNodes.isEmpty()){
      TreeNode<E> popNode = treeNodes.pop();
      //输出语句
      System.out.println(root.element);
      //遍历右子树
      root = popNode.right;
    }
  }
}

2.解释图

同理中序遍历左子树的递归实现,这里在遍历完成整个左子树才会输出左子树的父节点

  • 先遍历左子树在遍历右子树,所以一路往左遍历到节点D,将 A - B - D 依次压栈(并不输出),然后遍历D的子树D.left,

    此时D.left 的值为null,因此,跳出while循环,执行下面的if语句,从不为空的stack中弹出D,输出D,然后将root的值赋值给D的右子树(为空)

    image-20201115112700637

  • D的右子树 = null,弹出 B 节点,输出 B 节点,开始遍历B节点的右子树E.

image-20201115113032377

3.流程图 同 中序遍历流程图(建议在稿纸上复现这个中序遍历的非栈的整个过程)

后序遍历(非递归-基于栈)

后续遍历的非递归是不同于前面的情况的

后序遍历是在遍历完成左子树和右子树之后才进行输出

因此后续遍历决定是否输出值的时候需要判断其左右子树都遍历完毕,

所以需要设置一个 **visited记号(游标)**来记录,并且判断是否遍历完成其右子树

1.代码

private void postOrder(TreeNode<E> root){
  Stack<TreeNode<E>> stack = new Stack<>();
  //判断是否已经遍历过的指针指向已经遍历过的节点.
  TreeNode<E> visited =root;
  while (root != null || !stack.isEmpty()){
    while (root != null) {
      //储存遍历的节点在栈stack中.
      stack.push(root);
      //总是优先遍历左子树.
      root = root.left;
    }
    //peek节点进行判断,而不是直接弹出,这里很重要。
    root = stack.peek();
    //if node(root).right is null or the right is visited
    // (we  out put it and let the visited = node)
    // and then ,we pop the stack top to invoke the up level node.
    // 如果节点为空或者节点已经visited都可以输出
    // 输出之后我们让visited等于已经输出的子树
    if (root.right == null || root.right == visited){
      list.add(root);
      visited = root;
      root = stack.pop();
      //这里防止死循环.
      root = null;
    }else {
      //如果右子树不为空并且没有visited就遍历右子树.
      root = root.right;
    }
  }

2.解释图

  • visited 指针用来指向已经遍历过的子树,如果此时我们已经遍历过了左子树(D),然后从stack中peek()(注意这里不是弹出pop)父节点(B)

判断 visited 不等于 B.right 所以我们这次不输出弹出的元素B

image-20201116081846937

  • 遍历完成B的右子树E之后,visited指向遍历之后的右子树E ,再次从栈stack中peek()(注意不是弹出pop)出B的时候,判断

    visited 等于 B.right ,确认右子树已经visited完成遍历,输出B

image-20201116081946113

3.后序遍历非递归顺序图同后序遍历递归顺序图(建议按照代码复现整个顺序图)

代码总和

做一个迭代器Iterator是遍历二叉树的作用之一,

什么是迭代器?举个例子

TreeIterator<Contact> iterator = searchTree.iterator();
while (iterator.hasNext()) {
  Contact next = iterator.next();
  System.out.println(next);
}

我们这里的代码总和使用非递归的栈实现一个二叉树的迭代器TreeIterator

  • 使用LinkedList来储存遍历之后的节点,因为储存过程就是遍历二叉树的输出过程涉及到很多插入操作,
  • 默认使用中序遍历,因为中序遍历会得到一个有序的输出集合(这个你可以通过二叉树的性质1.左子树<本节点<右子树 和 中序遍历的顺序推出)

TreeNode二叉树节点


public class TreeNode<E>  {
    E element;
    TreeNode<E> left;
    TreeNode<E> right;
}


import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.function.Consumer;

/**
 * 二叉树的迭代器.
 */
public class TreeIterator<E> implements Iterator<E> {
    /**
     * 遍历的根节点
     */
    private TreeNode<E> root;
    /**
     * 储存输出的结果
     */
    public LinkedList<TreeNode<E>> list;

    /**
     * 自定义比遍历方式,默认是中序遍历
     * 1 为 preOrder,2 为 inOrder,3 为 postOrder
     */
    private int traverseMethod;
    /**
     * 配合链表list的指针,用来判断list长度和list是否为空.
     */
    int index;


    public TreeIterator(TreeNode<E> root) {
        this(root, 2);
    }

    public TreeIterator(TreeNode<E> root, int traverseMethod) {
        list = new LinkedList<>();
        this.root = root;
        this.traverseMethod = traverseMethod;
        this.index = -1;
        traverse();

    }

    /**
     * 遍历二叉树并储存到list里面.
     */
    private void traverse() {
        if (traverseMethod == 1) {
            preOrder(this.root);
        } else if (traverseMethod == 3) {
            postOrder(this.root);
        } else {
            inOrder(this.root);
        }
    }
    
    /**
     * 前序遍历
     *
     * @param root 根节点root
     */
    private void preOrder(TreeNode<E> root) {
        //创建一个栈
        Stack<TreeNode<E>> stack = new Stack<>();
        //遍历到 最后一个节点的时候 ,该节点的左右子树都为空,且栈中的值应该也为空,
        //所以只要不是同时满足这2点都要进入循环。
        while (root != null || !stack.isEmpty()) {
            //遍历的顺序得知,先需要一直往左走,
            while (root != null) {
                //输出语句
                list.add(root);
                //压栈
                stack.push(root);
                //遍历左子树
                root = root.left;
            }
            //遍历顺序得知:左子树遍历完成,弹出父节点,然后遍历父节点的右子树.
            if (!stack.isEmpty()) {
                //左子树遍历结束后弹出左子树的父节点
                root = stack.pop();
                //遍历右子树
                root = root.right;
            }
        }
    }

    /**
     * 中序遍历,输出一个有序的集合。
     *
     * @param root 根节点
     */
    private void inOrder(TreeNode<E> root) {
        Stack<TreeNode<E>> stack = new Stack<>();
        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                stack.push(root);
                //遍历左子树
                root = root.left;
            }
            if (!stack.isEmpty()) {
                TreeNode<E> popNode = stack.pop();
                //输出语句
                list.add(popNode);
                //遍历右子树
                root = popNode.right;
            }
        }
    }


    /**
     * 后序遍历
     *
     * @param root 根节点root
     */
    private void postOrder(TreeNode<E> root) {
        Stack<TreeNode<E>> stack = new Stack<>();
        //判断是否已经遍历过的指针指向已经遍历过的节点.
        TreeNode<E> visited = root;
        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                //储存遍历的节点在栈stack中.
                stack.push(root);
                //总是优先遍历左子树.
                root = root.left;
            }
            //peek节点进行判断,而不是直接弹出,这里很重要。
            root = stack.peek();
            //if node(root).right is null or the right is visited
            // (we  out put it and let the visited = node)
            // and then ,we pop the stack top to invoke the up level node.
            // 如果节点为空或者节点已经visited都可以输出
            // 输出之后我们让visited等于已经输出的子树
            if (root.right == null || root.right == visited) {
                list.add(root);
                visited = root;
                root = stack.pop();
                //这里防止死循环.
                root = null;
            } else {
                //如果右子树不为空并且没有visited就遍历右子树.
                root = root.right;
            }
        }
    }


    @Override
    public boolean hasNext() {
        return !list.isEmpty() && index < (list.size() - 1);
    }


    @Override
    public E next() {
        index++;
        return list.get(index).element;
    }


    @Override
    public void remove() {
        list.remove(index);
    }


}

二叉树搜索树BinarySearchTree和迭代器TreeIterator的源码
https://gitee.com/bmft001/DataStructure_Grace/tree/master/project-04-version-02-blog/src/main/java/addressBook

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JarvanStack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值