Java实现二叉树的遍历(递归和非递归)

2023-11-10

现有一颗如下图所示的二叉树
图1

一、基本概念

(1)先序遍历(深度优先遍历):
前、中、后这三个词是针对根节点的访问顺序而言的
先访问根结点,再访问左子结点,最后访问右子结点。
图中的二叉树的先序遍历的顺序是1 2 4 8 9 5 3 6 7
(2)中序遍历:
先访问左子结点,再访问根结点,最后访问右子结点。
图中的二叉树的中序遍历的顺序是8 4 9 2 5 1 6 3 7
(3)后序遍历:
先访问左子结点,再访问右子结点,最后访问根结点。
图中的二叉树的后序遍历的顺序是8 9 4 5 2 6 7 3 1
(4)层序遍历(广度优先遍历):
先访问树的第一层结点,再访问树的第二层结点……一直访问到最下面一层的结点。在同一层结点中,以从左到右的顺序依次访问。
图中的二叉树的层序遍历的顺序是1 2 3 4 5 6 7 8 9

二、构造二叉树

为了后面测试方便,我们先把它构造出来
构造二叉树的代码如下

    /**
     * 根据层序遍历数组构造二叉树
     * 返回二叉树的根结点
     * @param array
     */
    public static Node createBinTree(int[] array) {
        List<Node> nodeList = new LinkedList<Node>();
        // 将一个数组的值依次转换为Node节点
        for (int nodeIndex = 0; nodeIndex < array.length; nodeIndex++) {
            nodeList.add(new Node(array[nodeIndex]));
        }
        // 对前lastParentIndex-1个父节点按照父节点与孩子节点的数字关系建立二叉树
        for (int parentIndex = 0; parentIndex < array.length / 2 - 1; parentIndex++) {
            // 左孩子
            nodeList.get(parentIndex).leftChild = nodeList
                    .get(parentIndex * 2 + 1);
            // 右孩子
            nodeList.get(parentIndex).rightChild = nodeList
                    .get(parentIndex * 2 + 2);
        }
        // 最后一个父节点:因为最后一个父节点可能没有右孩子,所以单独拿出来处理
        int lastParentIndex = array.length / 2 - 1;
        // 左孩子
        nodeList.get(lastParentIndex).leftChild = nodeList
                .get(lastParentIndex * 2 + 1);
        // 右孩子,如果数组的长度为奇数才建立右孩子
        if (array.length % 2 == 1) {
            nodeList.get(lastParentIndex).rightChild = nodeList
                    .get(lastParentIndex * 2 + 2);
        }
        return nodeList.get(0);
    }
     /**
     * 内部类:节点
     */
    private static class Node {
        Node leftChild;
        Node rightChild;
        int data;

        Node(int newData) {
            leftChild = null;
            rightChild = null;
            data = newData;
        }
    }

三、递归的方式实现前序、中序和后序遍历

前序、中序和后序遍历三种遍历方式可以使用递归和非递归来实现,递归的自然更简单一些。这三种不同的遍历结构都是一样的,只是先后顺序不一样而已。代码如下

    /**
     * 先序遍历
     */
    public static void preOrderTraverse(Node node) {
        if (node == null)
            return;
        System.out.print(node.data + " ");
        preOrderTraverse3(node.leftChild);
        preOrderTraverse3(node.rightChild);
    }

    /**
     * 中序遍历
     */
    public static void inOrderTraverse(Node node) {
        if (node == null)
            return;
        inOrderTraverse2(node.leftChild);
        System.out.print(node.data + " ");
        inOrderTraverse2(node.rightChild);
    }

    /**
     * 后序遍历
     */
    public static void postOrderTraverse(Node node) {
        if (node == null)
            return;
        postOrderTraverse(node.leftChild);
        postOrderTraverse(node.rightChild);
        System.out.print(node.data + " ");
    }

四、深度优先遍历和广度优先遍历

深度优先遍历

实现思路:使用栈来实现。由于出栈顺序和入栈顺序相反,所以每次添加节点的时候先添加右节点,再添加左节点。这样在下一轮访问子树的时候,就会先访问左子树,再访问右子树

    public static void preOrderTraverse1(Node root) {
        if (root == null)
            return;
        LinkedList<Node> stack = new LinkedList<Node>();
        stack.push(root);
        while (!stack.isEmpty()) {
            Node node = stack.pop();
            System.out.print(node.data + " ");
            if (node.rightChild != null) {
                stack.push(node.rightChild);
            }
            if (node.leftChild != null) {
                stack.push(node.leftChild);
            }

        }
    }
广度优先遍历

实现思路:使用队列,出队列的同时左右孩子依次进队列

    public static void levelOrderTraverse(Node root) {
        if (root == null) {
            return;
        }
        LinkedList<Node> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            Node node = queue.poll();
            System.out.print(node.data + " ");
            if (node.leftChild != null) {
                queue.offer(node.leftChild);
            }
            if (node.rightChild != null) {
                queue.offer(node.rightChild);
            }
        }
    }

五、非递归的方式实现中序遍历和先序遍历

非递归的方式实现中序遍历

中序遍历稍微复杂一点,因为最先遇到的根节点不是最先访问的,需要先访问左子树,再回退到根节点,再访问根节点的右子树,这里的一个难点是从左子树回退到根节点的操作,虽然可以用栈来实现回退,但是要注意在出栈时保存根节点的引用,因为我们还需要通过根节点来访问右子树。
实现思路:
(1)如果当前节点存在,则当前节点入栈,指向leftChild,并leftChild(此时的当前节点)进行相同处理。重复1
(2)如果当前节点不存在,当前指向栈顶元素,栈顶元素出栈,处理当前节点值(因为左子节点不存在或者已经处理完了),指向rightChild,并对rightChild(此时的当前节点)进行相同处理。重复1

    public static void inOrderTraverse(Node root) {
        LinkedList<Node> stack = new LinkedList<>();
        Node pNode = root;
        while (pNode != null || !stack.isEmpty()) {
            if (pNode != null) {
                stack.push(pNode);
                pNode = pNode.leftChild;
            } else { //pNode == null && !stack.isEmpty()
                Node node = stack.pop();
                System.out.print(node.data + " ");
                pNode = node.rightChild;
            }
        }
    }
非递归的方式实现先序遍历

实现思路:
(1)如果当前节点存在,则处理当前节点的value(先处理根节点的值),然后将当前节点入栈,当前节点指向leftChild,并对leftChild(此时的当前节点)进行相同处理。重复1
(2)如果当前节点不存在,当前节点指向栈顶元素,栈顶元素出栈,当前节点指向rightChild,并对rightChild(此时的当前节点)进行相同处理。重复1

    /**
     * 与上面的中序遍历类似,存储入栈的元素是先序遍历,存储出栈的元素是中序遍历
     */
    public static void preOrderTraverse(Node root) {
        LinkedList<Node> stack = new LinkedList<>();
        Node pNode = root;
        while (pNode != null || !stack.isEmpty()) {
            if (pNode != null) {
                System.out.print(pNode.data + " ");
                stack.push(pNode);
                pNode = pNode.leftChild;
            } else { //pNode == null && !stack.isEmpty()
                Node node = stack.pop();
                pNode = node.rightChild;
            }
        }
    }

六、后序遍历

后序遍历在访问完左子树向上回退到根节点的时候不是立马访问根节点的,而是得先去访问右子树,访问完右子树后在回退到根节点

    public static void postOrderTraverse(Node root) {
        Stack<Node> stack = new Stack<>();
        Node cur = root;
        Node pre = null;

        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur); // 添加根节点
                cur = cur.leftChild; // 递归添加左节点
            }
            cur = stack.peek(); // 已经访问到最左的节点了
            //在不存在右节点或者右节点已经访问过的情况下,访问根节点
            if (cur.rightChild == null || cur.rightChild == pre) {
                stack.pop();
                System.out.print(cur.data + " ");
                pre = cur;
                cur = null;
            } else {
                cur = cur.rightChild; // 右节点还没有访问过就先访问右节点
            }
        }
    }

七、总结

可以先记忆三种先序遍历的写法,然后推出其他几种遍历方式的写法
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java实现二叉树的遍历(递归和非递归) 的相关文章

随机推荐

  • LeetCode-116.填充每个节点的下一个右侧节点指针、深度优先搜索

    题目分析 广度优先搜索 题目要求把二叉树中每一层的的节点连起来 最简单的方法即 BFS 按层的顺序的对树进行遍历 但需要使用 queue 数据结构 空间复杂度为 O N 不符合题目要求 深度优先搜索 由于 next 指针的存在 可以实现对二
  • Unity WorldToScreenPoint坐标变换

    功能 实现标签跟随物体运动 标签是一个Prefab 由底图和文字组成 Dota2中英雄血条的实现也是这种原理 说到底就是标签根据物体位置不间断刷新自己的坐标值 3D gt 2D gt 3D 先来了解一下Unity D中的坐标系统 1 Wor
  • 前台页面上传data image图片,java后台接收图片保存

    最近在项目中有这么一个需求 就是上传一个视频文件 然后要获取视频文件的第一帧图片 这个可以通过canvas获取得到 得到的是一个dataURL 之后还要将这个图片上传到云 这个时候如何操作就不清楚了 于是乎 google一番 总结如下 将d
  • Redis源码分析(三)—— 字典的设计与实现

    前言 字典是一种用于保存键值对的数据结构 Redis数据库使用字典做为底层实现 字典也是哈希键的底层实现之一 C语言中并没有内置字典这个数据结构 Redis自己实现了字典 以下结合源码分析Redis字典的设计与实现 源码版本 Redis 6
  • java执行linux命令:head -n 80 /dev/urandom

    看了微信小程序api后 发现登录Logo接口需要处理随机key 所以着手处理了一下 直接贴代码 先运行命令 让其生成168位随机数 private static String wxSessionkey F3UENUg3JcI31O2RpoB
  • weex实现带有跟手动画的tab栏

    在weex开发的群中看到有人提到这个问题 就想着去实现以下 还不是很完美 只支持一屏的tab栏内容 后续会进行优化 2019 6 20 更新 已支持滚动跟手 可以超出屏幕 2019 6 23 更新 解决子元素包含滚动标签时无法滑动切换的问题
  • Poppuwindow的简单使用

    继 DialogFragment的简单使用 之后 我们再来试试 Poppuwindow 的简单使用 切记 本篇博客只能保证你入门哦 适合小白学习 效果展示 1 几个常用的构造方法 public PopupWindow Context con
  • 什么是压力测试?如何进行Jmeter压力测试

    一 什么是压力测试 软件测试中 压力测试 Stress Test 也称为强度测试 负载测试 压力测试是模拟实际应用的软硬件环境及用户使用过程的系统负荷 长时间或超大负荷地运行测试软件 来测试被测系统的性能 可靠性 稳定性等 常用的压力测试软
  • 因果学习论文阅读

    论文阅读 因果机器学习的前沿进展综述 Overview of the Frontier Progress of Causal Machine Learning 因果概念 提出因果旨在解决虚假相关的问题 相关只需要保持两个变量的分布相同 而因
  • python中的CSV 工具类

    CSV工具类是Python中的自带包 用来解析CSV文件 实例化一个CSV对象 需要传入一个CSV文件的路径 with open case csv as casefile csv DictReader 将CSV读取成字典的形式 rows2
  • Python练习(二)

    目录 列表元素计算 字典最大值 输出一串字符对应的Unicode值 列表基本操作 元素增加 删除 字典值求和 习题 列表元素计算 描述 从键盘输入一个列表 计算输出列表元素的平均值 请完善代码 def mean numlist s 0 0
  • opencv+matlab双目标定(python版)

    采集的图像用opencv自带程序识别不出棋盘格角点 可能是因为分辨率太低了 好在MATLAB标定工具箱可以正常使用 不过获得的标定参数导入opencv之后需要转置 记录一下官方自带实例的方法免得以后忘了 1 MATLAB标定 导入左右相机图
  • 【uniapp关联unicloud,阿里云云服务空间】unicloud本地调试服务启动失败:当前项目未关联unicloud服务空间,请调整后重新运行,已解决

    最近开发app项目 很多都是第一次上手 1 在Hbuider中运行项目 出现如下提示 2 项目根目录下已有uniCloud文件夹 3 如果云开发环境未创建 可以右击项目 选择创建uniCloud云开发环境 4 创建好的目录如下 index
  • DES 数据加密标准 结构详解

    DES Data Encryption Standard 又称数据加密标准 是一种对称加密算法 也是密码学摆脱古典流加密后最简单的一种块加密算法 由于香农与1949年提出 完善保密性 该标准要求密钥长度不短于明文长度 实际操作难以达到 因此
  • 黑白棋子问题

    黑白棋子问题 1 问题描述 两个人下棋 一方执黑棋 一方执白棋 要求双方轮流下子 给出两种情况的解决办法 1 执黑子一方先下 2 双方都可以先下 谁先抢到棋盘谁先下 2 解决 情况 1 信号量 bfg 1 wfg 0 注意信号量及初值的设置
  • 一个顽疾——QT不能包含tslib的头和库文件联合编译的解决方法

    先介绍一下我的交叉编译环境 OS是Fedora9 交叉编译器是arm linux gcc 4 3 3 arm 2009q1 其它 tslib 1 4 QT4 7 2 硬件平台Omap3530 以前我的交叉编译器使用的是arm linux g
  • Java技术之提取指定文件

    目录 序幕 详解 开发工具 简介 主线程代码 静态变量 复制指定文件的方法创建 分析并实现 在Main主线程中的使用 简单搬运 序幕 嗨嗨 我又来咯 距离上一次发布已经有了很长一段时间 问我在干嘛 我在消磨人生 直到昨天 收到了来自父亲的这
  • java content-type设置_POST请求时 content-type的设置以及参数传递

    前提 在前后端联调的时候总会牵扯到一个问题 就是参数的传递方式 GET请求就不说了 参数往url后面一拼 万事大吉 然而一到POST请求的时候 花样就来了 后端童鞋跟你说 我这个接口在postman试过是没问题的 你content type
  • centos7将python升级为3.7

    centos7将python升级为3 7
  • Java实现二叉树的遍历(递归和非递归)

    现有一颗如下图所示的二叉树 一 基本概念 1 先序遍历 深度优先遍历 前 中 后这三个词是针对根节点的访问顺序而言的 先访问根结点 再访问左子结点 最后访问右子结点 图中的二叉树的先序遍历的顺序是1 2 4 8 9 5 3 6 7 2 中序