树的基本概念以及java实现二叉树(二)

本文深入讲解二叉树的遍历方法(包括递归与非递归实现)、获取二叉树的高度与度的方法、创建二叉树的过程以及其他应用如层序遍历、复制二叉树和判断二叉树是否相等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本文是我在学习了树后作的总结文章,接上篇文章,本节大致可以总结为:

  • 二叉树的遍历与实现(递归和非递归)
  • 获取二叉树的高度和度
  • 创建一棵二叉树
  • 其他应用(层序遍历,复制二叉树,判断二叉树是否相等)

文章传送门:

树的基本概念以及java实现二叉树(一):https://blog.youkuaiyun.com/qingtian_1993/article/details/80637917
树的基本概念以及java实现二叉树(二):https://blog.youkuaiyun.com/qingtian_1993/article/details/80877487

代码传送门,欢迎star:https://github.com/mcrwayfun/java-data-structure

正文

4 二叉树的遍历与实现

二叉树遍历:从树的根节点出发,按照某种次序依次访问二叉树中所有的结点,使得每个结点被访问仅且一次。普遍有三种遍历方式,前序、中序和后序;这里有两个关键词:访问和次序。有一个基本思想要注意下:一个根结点+左右子树均可以看作一棵二叉树

4.1 递归实现二叉树的遍历
4.1.1 前序遍历

基本思想:若二叉树为空,则返回。否则从根结点开始,优先访问根结点,再前序遍历左子树,前序遍历右子树,即根——左——右

图中按照前序遍历的访问结果为:A、B、D、G、H、C、E、I、F

使用代码递归来实现前序遍历,如下所示:

    /**
     * 前序遍历(中左右)
     * output:A、B、D、G、H、C、E、I、F
     * @param root
     */
    public void preOrder(TreeNode root) {

        if (root == null) {
            return;
        } else {
            System.out.println("preOrder data:" + root.getData());
            preOrder(root.leftChild);
            preOrder(root.rightChild);
        }
    }
4.1.2 中序遍历

基本思想:若二叉树为空,则返回。否则优先中序遍历左子树,再访问根结点,再后序遍历右子树,即左——根——右

图中按照中序遍历的访问结果为:G、D、H、B、A、E、I、C、F

使用代码递归来实现中序遍历,如下所示:

    /**
     * 中序遍历(左中右)
     * output:G、D、H、B、A、E、I、C、F
     * @param root
     */
    public void midOrder(TreeNode root) {

        if (root == null) {
            return;
        } else {
            midOrder(root.leftChild);
            System.out.println("midOrder data:" + root.getData());
            midOrder(root.rightChild);
        }
    }
4.1.3 后序遍历


基本思想:若二叉树为空,则返回。否则优先后序遍历左子树,再后序遍历右子树,最后访问根结点,,即左——右——根

图中按照后序遍历的访问结果为:G、H、D、B、I、E、F、C、A

使用代码递归来实现后序遍历,如下所示:

    /**
     * 后序遍历(左右中)
     * output:G、H、D、B、I、E、F、C、A
     * @param root
     */
    public void postOrder(TreeNode root){

        if (root == null) {
            return;
        } else {
            postOrder(root.leftChild);
            postOrder(root.rightChild);
            System.out.println("postOrder data:" + root.getData());
        }
    }
4.2 非递归实现二叉树的遍历
4.2.1 利用栈进行前序遍历

每访问一个结点后,在向左子树遍历下去之前,利用栈来记录该结点的右子女(如果有的话),以便在左子树退回时可以直接从栈顶取得右子树的根结点,继续其右子树的遍历。上图是过程的演示,先将null压入栈中,当栈中无元素时将其推出,表示结束。

    /**
     * 前序遍历非递归算法
     * output:A-B-D-E-C-F
     * @param root
     */
    public void preOrderNonRecursive(TreeNode root) {

        Stack<TreeNode> nodeStack = new Stack<>();
        nodeStack.push(null);
        while (root != null) {
            // 访问根结点
            System.out.println("preOrderNonRecursive data:" + root.getData());
            // 当前结点右子树不为空则放入栈中
            if (root.rightChild != null)
                nodeStack.push(root.rightChild);
            // 访问左子树
            if (root.leftChild != null)
                root = root.leftChild;
            else root = nodeStack.pop();
        }
    }
4.2.2 利用栈进行中序遍历

从根结点开始沿着leftChild到最下角的结点,将指针依次压入栈中,直到该结点的leftChild指针为NULL。访问它的数据后,再遍历该结点的右子树。此时该结点为栈中推出的指针。

    /**
     * 中序遍历非递归算法
     * output:D-B-E-A-F-C
     * @param root
     */
    public void midOrderNonRecursive(TreeNode root) {

        Stack<TreeNode> nodeStack = new Stack<>();
        do {
            while (root != null) {
                nodeStack.push(root);
                root = root.leftChild;
            }

            if (!nodeStack.empty()) {
                root = nodeStack.pop();
                System.out.println("preOrderNonRecursive data:" + root.getData());
                root = root.rightChild;
            }
        } while (root != null || !nodeStack.empty());
    }
4.2.3 利用栈进行后序遍历

因为后序遍历的访问顺序为左右根,所以在访问的时候比较麻烦,需要考虑到访问完左结点后,当前结点有无右结点需要访问,若有则需要右进访问右子树,所以要有一个变量来记录当前结点。
- 从根结点开始沿着leftChild到最下角的结点,将指针依次压入栈中,直到该结点的leftChild指针为NULL。
- 判断当前结点有无右子树,若有,则优先访问右子树
- 无右子树货已经访问过右子树则访问当前结点

    /**
     * 后序遍历非递归算法
     * output:D-E-B-F-C-A
     *
     * @param root
     */
    public void postOrderNonRecursive(TreeNode root) {

        Stack<TreeNode> nodeStack = new Stack<>();
        // 上一个结点
        TreeNode prev = root;
        do {
            while (root != null) {
                nodeStack.push(root);
                root = root.leftChild;
            }

            // 访问当前结点的右结点
            if (!nodeStack.empty()) {
                // 获取右子树,但先不弹出
                TreeNode temp = nodeStack.peek().rightChild;
                // 不存在右子树或右子树已经访问过,可以访问父结点
                if (temp == null || temp == prev) {

                    root = nodeStack.pop();
                    System.out.println("postOrderNonRecursive data:" + root.getData());
                    // 记录访问过的结点
                    prev = root;
                    // 当前结点置空
                    root = null;
                } else {
                    // 存在右子树,需要优先访问右子树
                    root = temp;
                }
            }
        } while (root != null || !nodeStack.empty());
    }

5 获取二叉树的高度和度

5.1 获取二叉树高度

树的性质第12点:叶结点的高度为1,非叶结点的高度等于它子女结点高度的最大值加1。核心思想是递归实现

    /**
     * 求二叉树的深度(高度)
     *
     * @return
     */
    public int getHeight() {
        return getHeight(root);
    }

    /**
     * 求二叉树的深度(高度)
     *
     * @param root
     * @return
     */
    private int getHeight(TreeNode root) {
        if (root == null) {
            return 0;
        } else {
            int i = getHeight(root.leftChild);
            int j = getHeight(root.rightChild);
            return i >= j ? i + 1 : j + 1;
        }
    }
5.2 获取二叉树的度
    /**
     * 求二叉树的结点数
     *
     * @return
     */
    public int getSize() {
        return getSize(root);
    }

    /**
     * 求二叉树的结点数
     *
     * @param root
     * @return
     */
    private int getSize(TreeNode root) {

        if (root == null) {
            return 0;
        } else {
            return 1 + getSize(root.leftChild) + getSize(root.rightChild);
        }
    }
6 创建一棵二叉树

完全二叉树和满二叉树是一般二叉树的一种形态,所以只要会创建一般二叉树即会创建所有形态的二叉树。用数组来存储一棵二叉树,对这棵树按照完全二叉树的方式编号,其中结点不存在的使用特殊字符来标明。

现有一棵二叉树如下图所示,则其用数组表示为['A','B','D','#','#','E','#','#','F','#','#','#'],叶结点需要带子女结点,且子女结点用特殊字符标识。

        A
      /  \
     B    C
    / \  /
   D   E F
  1. 输入一个arraylist,list中存放的数据是结点数组(通过list.reove来获取数据)
  2. 获取list下标为0的元素(总是第一个),如果遇到结束符’#’,则不创建结点,并执行remove(0);否则创建一个结点,判断index = 0是否成立,若成立则表明根结点不存在,创建根结点,并执行remove(0)
  3. 循环步骤1,2,递归创建左子树和右子树
    /**
     * 前序遍历创建二叉树
     * ABD##E##CF###
     *
     * @param data
     * @return
     */
    public TreeNode createBinaryTree(List<String> data) {

        if (data == null || data.isEmpty())
            return null;

        return createBinaryTreeHelper(data.size(), data);
    }

    private TreeNode createBinaryTreeHelper(int size, List<String> data) {

        TreeNode node;

        String d = data.get(0);
        int index = size - data.size();

        // 遇到结束符#
        if (d.equals("#")) {
            node = null;
            data.remove(0);
            return node;
        }
        node = new TreeNode(index, d);
        // 根结点
        if (index == 0) {
            root = node;
        }
        data.remove(0);
        node.leftChild = createBinaryTreeHelper(size, data);
        node.rightChild = createBinaryTreeHelper(size, data);
        return node;

    }

7 其他应用(层序遍历,复制二叉树,判断二叉树是否相等)

7.1 层序遍历

层序遍历依照从根结点开始,自上而下,从左到右。需要有一个结构来存储当前层的结点,当访问完当前层结点后,将其抛出后继续访问下层结点,队列的先进先出符合这一要求。

  1. 将根节点压入队列中
  2. 队列不为空则开始循环,此时只有根结点,则推出根结点并访问。判断根结点有无左右子树,若有则将其压入队列中
  3. 队列为空,结束循环
    /**
     * 层序遍历
     *
     * @param root
     */
    public List<List<String>> levelOrder(TreeNode root) {

        List<List<String>> reList = new ArrayList<>();
        Queue<TreeNode> nodeQueue = new LinkedList<>();
        // 压入根结点
        nodeQueue.offer(root);
        while (!nodeQueue.isEmpty()) {
            int levelSize = nodeQueue.size();
            List<String> subList = new ArrayList<>();
            while (levelSize != 0) {
                TreeNode temp = nodeQueue.poll();
                subList.add(temp.getData() + "");
                if (temp.leftChild != null) nodeQueue.offer(temp.leftChild);
                if (temp.rightChild != null) nodeQueue.offer(temp.rightChild);
                levelSize--;
            }
            reList.add(subList);
        }

        return reList;
    }
7.2 通过前序遍历复制一棵二叉树

为了实现二叉树的复制方法,可以利用二叉树的前序遍历算法。若二叉树parent不为空,则首先复制根结点,这相当于二叉树前序遍历中访问根结点的语句;然后分别复制二叉树的左子树和右子树,这相当于二叉树前序遍历算法中的遍历左子树和右子树。整个算法的思想是递归。

    /**
     * 复制一棵二叉树
     * @param parent
     * @return
     */
    public TreeNode copy(TreeNode parent){

        if(parent == null)
            return null;

        // 构造根结点
        TreeNode temp = new TreeNode(parent.getIndex(),parent.getData());

        // 递归构造左子树
        temp.leftChild = copy(parent.leftChild);

        // 递归构造右子树
        temp.rightChild = copy(parent.rightChild);

        return temp;
    }
7.3 判断两棵树是否相等

递归判断每个结点的s.data == t.data是否成立即可

    /**
     * 判断两棵树是否相等
     * @param s
     * @param t
     * @return
     */
    public boolean euqal(TreeNode s,TreeNode t){

        if(s == null && t == null)
            return true;

        if(s != null && t != null && s.data == t.data &&
                euqal(s.leftChild,t.leftChild) &&
                euqal(s.rightChild,t.rightChild))
            return true;
        else
            return false;
    }

总结

本章节主要学习了

  • 二叉树的遍历与实现(递归和非递归)
  • 获取二叉树的高度和度
  • 创建一棵二叉树
  • 其他应用(层序遍历,复制二叉树,判断二叉树是否相等)

学习树最主要的思想就是要了解递归,因为树的定义是一个递归的定义,即树的定义中又用到了树的概念。

附录

本文参考的资料:

  • 数据结构(用面向对象方法与C++语言描述)第二版,殷人昆主编
  • 图解数据结构——使用java,胡昭民主编

代码传送门,欢迎star:https://github.com/mcrwayfun/java-data-structure

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值