<JavaDS> 二叉树遍历各种遍历方式的代码实现 -- 前序、中序、后序、层序遍历

news2024/11/25 21:24:14

目录

有以下二叉树:

一、递归

1.1 前序遍历-递归

1.2 中序遍历-递归

1.3 后序遍历-递归

二、递归--使用链表

2.1 前序遍历-递归-返回链表

2.2 中序遍历-递归-返回链表

2.3 后序遍历-递归-返回链表

三、迭代--使用栈

3.1 前序遍历-迭代-使用栈

3.2 中序遍历-迭代-使用栈

3.3 后序遍历-迭代-使用栈

四、层序遍历

4.1 层序遍历-迭代-使用队列

4.2 层序遍历-迭代-返回二维链表


有以下二叉树:


一、递归

逻辑/思路:
递归思想是将大问题分解为解法相似的小问题
已知根节点有左右子树,根节点的子节点又各有自己的左右子树,不断地对每棵子树进行左右分解,这就是从大问题到小问题。
递归有两大关键条件:递推条件和回归条件,前者作用于递,后者作用于归。
递推条件在方法中,不断将左右子节点分别作为参数,重复调用本方法,每轮调用方法都在访问更深地子节点,达成了递归中的推进条件;
回归条件当访问到的节点为null时(如上图),就不能继续往下访问了,所以节点为null时,就return,这是回归条件;

二叉树的前中后序递归遍历,思路基本相同,区别只在于调用方法和打印的顺序不同。
以下分别是二叉树的前序、中序、后序递归遍历的代码:

1.1 前序遍历-递归

    public static void PreorderTraversal1(TreeNode root) { 
        //当前节点为null,则return;
        if(root == null){
            return;
        }
        //打印当前节点;
        System.out.print(root.val+" ");
        //找左子节点;
        PreorderTraversal1(root.left);
        //找右子节点;
        PreorderTraversal1(root.right);
    }

//运行结果:
1 2 3 4 5 6 7 

1.2 中序遍历-递归

    public static void InorderTraversal1(TreeNode root) {
        //当前节点为null,则return;
        if(root == null){
            return;
        }
        //找左子节点;
        InorderTraversal1(root.left);
        //打印当前节点;
        System.out.print(root.val+" ");
        //找右子节点;
        InorderTraversal1(root.right);
    }

//运行结果:
3 2 4 1 5 7 6 

1.3 后序遍历-递归

    public static void PostorderTraversal1(TreeNode root) {
        //当前节点为null,则return;
        if(root == null){
            return;
        }
        //找左子节点;
        PostorderTraversal1(root.left);
        //找右子节点;
        PostorderTraversal1(root.right);
        //打印当前节点;
        System.out.print(root.val+" ");
    }

//运行结果:
3 4 2 7 6 5 1 

二、递归--使用链表

逻辑/思路:
        同样是使用递归的逻辑思想,只是由于使用了链表的数据结构,所以在递归的过程中需要将元素加入到链表中。

2.1 前序遍历-递归-返回链表

    public static List<Integer> preorderTraversal2(TreeNode root){
        //新建链表;
        List<Integer> list = new ArrayList<>();
        //当前节点为null,则return;
        if(root == null){
            return list;
        }
        //add当前节点;
        list.add(root.val);
        //找当前节点的左子节点,并存储在新链表中;
        List<Integer> listLeft = preorderTraversal2(root.left);
        //将代表左子树的新链表中的元素全部添加到list中;
        list.addAll(listLeft);
        //找当前节点的右子节点,并存储在新链表中;
        List<Integer> listRight = preorderTraversal2(root.right);
        //将代表右子树的新链表中的元素全部添加到list中;
        list.addAll(listRight);

        //返回代表这个子树的list;
        return list;
    }

//运行结果:
1 2 3 4 5 6 7 

2.2 中序遍历-递归-返回链表

    public static List<Integer> InorderTraversal2(TreeNode root){
        //新建链表;
        List<Integer> list = new ArrayList<>();
        //当前节点为null,则return;
        if(root == null){
            return list;
        }
        //找当前节点的左子节点,并存储在新链表中;
        List<Integer> listLeft = InorderTraversal2(root.left);
        //将代表左子树的新链表中的元素全部添加到list中;
        list.addAll(listLeft);
        //add当前节点;
        list.add(root.val);
        //找当前节点的右子节点,并存储在新链表中;
        List<Integer> listRight = InorderTraversal2(root.right);
        //将代表右子树的新链表中的元素全部添加到list中;
        list.addAll(listRight);
        //返回代表这个子树的list;
        return list;
    }

//运行结果:
3 2 4 1 5 7 6 

2.3 后序遍历-递归-返回链表

    public static List<Integer> PostorderTraversal2(TreeNode root){
        //新建链表;
        List<Integer> list = new ArrayList<>();
        //当前节点为null,则return;
        if(root == null){
            return list;
        }
        //找当前节点的左子节点,并存储在新链表中;
        List<Integer> listLeft = PostorderTraversal2(root.left);
        //将代表左子树的新链表中的元素全部添加到list中;
        list.addAll(listLeft);
        //找当前节点的右子节点,并存储在新链表中;
        List<Integer> listRight = PostorderTraversal2(root.right);
        //将代表右子树的新链表中的元素全部添加到list中;
        list.addAll(listRight);
        //add当前节点;
        list.add(root.val);
        //返回代表这个子树的list;
        return list;
    }

//运行结果:
3 4 2 7 6 5 1 

三、迭代--使用栈

逻辑/思路:

        迭代是使用栈来帮助遍历二叉树。这种遍历方式利用了栈“后进先出”的特点,来达到对二叉树中的父节点进行回溯的目的。

        也就是说,当遍历到一个节点即将该节点压栈,当完成对左子树的访问之后,利用弹出并记录栈顶元素的方式,得到左子树的父节点,并通过这个父节点访问右子树。

        因为压栈的第一个元素必然为根节点,因此,当栈为空时,必然全部节点都遍历完成了。

3.1 前序遍历-迭代-使用栈

    public static void PreorderTraversal3(TreeNode root){
        //如果root为null,return;
        if(root == null){
            return;
        }
        //新建一个栈;
        Stack<TreeNode> stack = new Stack<>();
        //将根节点压栈;
        stack.push(root);
        //如果栈不为空;
        while (!stack.isEmpty()){
            //弹出栈顶元素,并记录为cur;
            TreeNode cur = stack.pop();
            //因为是前序遍历,打印当前节点,再进行后续操作;
            System.out.print(cur.val+" ");
            //如果cur的右子节点不为空,则将其压栈;
            if(cur.right != null){
                stack.push(cur.right);
            }
            //如果cur的左子节点不为空,则将其压栈;
            if(cur.left != null){
                stack.push(cur.left);
            }
        }
    }

//运行结果:
1 2 3 4 5 6 7 
为什么压栈先压右子节点,再压左子节点?
        因为要按前序遍历打印,而栈是后进先出,所以后压左子节点,等下先弹出的也是左子节点,先弹出先打印。

3.2 中序遍历-迭代-使用栈

    public static void InorderTraversal3(TreeNode root){
        //如果root为null,return;
        if(root == null){
            return;
        }
        //新建一个栈;
        Stack<TreeNode> stack = new Stack<>();
        //用一个临时“指针”记录root(因为要移动指针,不然等下根节点跑哪去都不知道了)
        TreeNode cur = root;
        //如果cur不为空或者栈不为空;
        while (cur != null || !stack.isEmpty()){
            //如果节点不为空,则将节点压栈,并让指针不断向左子节点移动,直到节点为空;
            //当循环停下时,此时栈顶元素必然是树中最左边且未被遍历过的节点;
            while (cur != null){
                stack.push(cur);
                cur = cur.left;
            }
            //弹出栈顶元素,并记录为pre;
            TreeNode pre = stack.pop();
            //因为是中序遍历,打印当前节点,再进行后续操作;
            System.out.print(pre.val+" ");
            //如果pre的右子节点不为空,则将指针cur移动到右子节点上;
            if(pre.right != null){
                cur = pre.right;
            }
        }
    }

//运行结果:
3 2 4 1 5 7 6 
为什么进入循环的判断条件是cur != null || !stack.isEmpty()?

        cur不为空的判断条件是为了让一开始栈中还没有元素时,能够顺利进入循环。

        栈不为空代表还有元素没有遍历。

3.3 后序遍历-迭代-使用栈

    public static void PostorderTraversal3(TreeNode root){
        //如果root为null,return;
        if(root == null){
            return;
        }
        //新建一个栈;
        Stack<TreeNode> stack = new Stack<>();
        //用一个临时“指针”记录root(因为要移动指针,不然等下根节点跑哪去都不知道了)
        TreeNode cur = root;
        //将根节点压栈;
        stack.push(root);
        //如果栈不为空;
        while (!stack.isEmpty()) {
            //查看并记录栈顶元素这个节点;
            TreeNode peek = stack.peek();
            //根据以下条件,进行后续操作;
            if (peek.left != null && peek.left != cur && peek.right != cur) {
                stack.push(peek.left);
            } else if (peek.right != null && peek.right != cur) {
                stack.push(peek.right);
            } else {
                System.out.print(stack.pop().val + " ");
                cur = peek;
            }
        }
    }

//运行结果:
3 4 2 7 6 5 1 
上述代码中的 if...else if..else 为什么这样设置条件?

if (peek.left != null && peek.left != cur && peek.right != cur) { stack.push(peek.left); }

判断peek有没有左子节点,且peek的左右子节点有没有被处理过;

如果左右子节点都没有被处理过,那么将peek的左子节点压栈;
else if (peek.right != null && peek.right != cur) { stack.push(peek.right); }

再判断peek有没有右子节点,且peek的右子节点有没有被处理过;

在这里不能对左子节点判断是否操作过,因为是先遍历的左子节点,如果存在左子节点必然是操作过的。所以如果加入左子节点的判断,则必然进不了这个else if;

如果右子节点没有被处理过,那么将peek的右子节点压栈;
通过前两个条件可以看出,只要有左子节点,必然先处理左子节点,没有左子节点或者左子节点被处理完了,才开始处理右子节点;处理方法如下:
else {
                System.out.print(stack.pop().val + " ");
                cur = peek;
            }
直到所有左右子节点处理完毕,最后一个弹出栈并被处理的,必然是一开始压栈的根节点root。

四、层序遍历

逻辑/思路:

        层序遍历与前序、中序、后序遍历都不同,层序遍历使用的是队列的数据结构进行遍历。

        核心思想是利用队列“先进先出”和“队头出,队尾入”的特点,分层遍历二叉树。

        如果需要返回一个二维链表,则是将二叉树每层的节点按顺序添加到各个链表中,每个链表代表一层,最终链表将作为元素,被添加到二维链表中;

4.1 层序遍历-迭代-使用队列

    public static void SequenceTraversal1(TreeNode root){
        //如果root为null,return;
        if(root == null){
            return;
        }
        //创建队列,root入队列;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        //如果队列中还有元素;
        while (queue.size() != 0){
            //队首出队列并记录cur;
            TreeNode cur = queue.poll();
            //打印cur的值;
            System.out.print(cur.val + " ");
            //如果cur有左子节点,将左子节点入队列;
            if(cur.left != null){
                queue.offer(cur.left);
            }
            //如果cur有右子节点,将右子节点入队列;
            if(cur.right != null){
                queue.offer(cur.right);
            }
        }
    }

//运行结果:
1 2 5 3 4 6 7 

4.2 层序遍历-迭代-返回二维链表

    public static List<List<Integer>> SequenceTraversal2(TreeNode root){
        //创建二维链表diList;
        List<List<Integer>> diList = new LinkedList<>();
        //如果root为空则return;
        if(root == null){
            return diList;
        }
        //创建队列,root入队列;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        //如果队列中还有元素;
        while(!queue.isEmpty()){
            //计算队列中的元素数量;
            int size = queue.size();
            //创建链表,用于存储每层的节点;
            List<Integer> list = new LinkedList<>();
            //根据上面的size确定需要循环多少次,即处理多少个节点;
            while (size>0){
                //队首出队列;
                TreeNode cur = queue.poll();
                //出队列一个size就--;
                size--;
                //把出队列的元素添加到list中;
                list.add(cur.val);
                //如果cur有左子节点,将左子节点入队列;
                if(cur.left != null){
                    queue.offer(cur.left);
                }
                //如果cur有右子节点,将右子节点入队列;
                if(cur.right != null){
                    queue.offer(cur.right);
                }
            }
            //把链表list添加到diList中;
            diList.add(list);
        }
        //返回diList;
        return diList;
    }

//运行结果:
1 2 5 3 4 6 7 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1265115.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

flask web开发学习之初识flask(一)

一、概念 flask是一个使用python编写的轻量级web框架&#xff0c;作者为Armin Ronacher&#xff08;中文名&#xff1a;阿尔敏罗纳彻&#xff09;&#xff0c;它广泛被应用于web开发和API。flask提供了简洁而灵活地方式来构建web应用&#xff0c;它不会强加太多约束&#xff0…

docker安装Sentinel zipkin

文章目录 引言I Sentinel安装1.1 运行容器1.2 DOCKERFILE 参考1.3 pom 依赖1.4 .yml配置(整合springboot)II 资源保护2.1 Feign整合Sentinel2.2 CommonExceptionAdvice:限流异常处理类III zipkin引言 消息服务和请求第三方服务可不配置Sentinel。 I Sentinel安装 Sentinel …

智能优化算法应用:基于人工蜂群算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于人工蜂群算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于人工蜂群算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.人工蜂群算法4.实验参数设定5.算法结果6.参考…

玻色量子真机测试完整报告

​ 真机测试 2023年 2023.8 量子计算突破云渲染资源调度&#xff01;真机测试完整报告公开&#xff01; 2023.8 量子计算突破金融信用评分&#xff01;真机测试完整报告公开&#xff01; 组合优化问题专题 2023年 2023.7 玻色量子“揭秘”之旅行商问题与Ising建模 2023.…

食谱菜谱大全API接口

食谱菜谱大全API接口 一、食谱菜谱大全API接口二、使用步骤1、接口2、请求参数3、请求参数示例4、接口 返回示例 三、 如何获取appKey和uid1、申请appKey:2、获取appKey和uid 四、重要说明 一、食谱菜谱大全API接口 包含所有家用或者商用的食谱菜谱的API接口 二、使用步骤 1…

MySQL索引使用总结

索引(index) 官方定义&#xff1a;一种提高MySQL查询效率的数据结构 优点&#xff1a;加快查询速度 缺点&#xff1a; 1.维护索引需要消耗数据库资源 2.索引需要占用磁盘空间 3.增删改的时候会影响性能 索引分类 索引和数据库表的存储引擎有关&#xff0c;不同的存储引擎&am…

uniapp地图基本使用及解决添加markers不生效问题?

uniapp地图使用 App端 通过 nvue 页面实现地图 文章目录 uniapp地图使用效果图templatejs添加 marker使用地图查看位置移到到当前位置 效果图 template <template><view class"mapWrap"><!-- #ifdef APP-NVUE --><map class"map-containe…

Java---权限修饰符、final、static

文章目录 1. 权限修饰符2. final(最终态)3. static(静态) 1. 权限修饰符 修饰符同一个类中同一个包中的子类和无关类不同包的子类不同包的无关类private√默认√√protected√√√public√√√√ 2. final(最终态) 1. final关键字是最终的意思&#xff0c;可以修饰成员方法、…

量子力学技术前沿:探索、挑战与未来

量子力学技术前沿:探索、挑战与未来 一、引言 量子力学,这门揭示微观世界规律的学科,自诞生以来就在科技领域发挥着举足轻重的作用。随着科技的飞速发展,量子力学的应用也在不断拓展和深化。今天,我将带领大家一起领略量子力学技术的魅力,探讨其发展趋势和挑战。 二、量…

【Vue】绝了!这生命周期流程真...

hello&#xff0c;我是小索奇&#xff0c;精心制作的Vue系列持续发放&#xff0c;涵盖大量的经验和示例&#xff0c;如果对您有用&#xff0c;可以点赞收藏哈~ 生命周期 Vue.js 组件生命周期&#xff1a; 生命周期函数&#xff08;钩子&#xff09;就是给我们提供了一些特定的…

STM32之模数转换器ADC

目录 1、ADC介绍 1.什么是ADC&#xff1f; ADC的全称是Analog-to-Digital Converter&#xff0c;指模拟/数字转换器 2.ADC的性能指标 3.ADC特性 12位分辨率 4.ADC通道 5.ADC转换顺序 6.ADC触发方式 7.ADC转化时间 8.ADC转化模式 9.模拟看门狗 实验&#xff1a;使用ADC读…

【C数据(一)】数据类型和变量你真的理解了吗?来看看这篇

&#x1f308;write in front :&#x1f50d;个人主页 &#xff1a; 啊森要自信的主页 ✏️真正相信奇迹的家伙&#xff0c;本身和奇迹一样了不起啊&#xff01; 欢迎大家关注&#x1f50d;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;>希望看完我的文章对你有小小的帮助&am…

Shell条件变量练习

1.算数运算命令有哪几种&#xff1f; (1) "(( ))"用于整数运算的常用运算符&#xff0c;效率很高 [rootshell scripts]# echo $((24*5**2/8)) #(( ))2452814 14 (2) "$[ ] "用于整数运算 [rootshell scripts]# echo $[24*5**2/8] #[ ]也可以运…

Spring Boot进行单元测试,一个思路解决重启低效难题!

所谓单元测试就是对功能最小粒度的测试&#xff0c;落实到JAVA中就是对单个方法的测试。 junit可以完成单个方法的测试&#xff0c;但是对于Spring体系下的web应用的单元测试是无能为力的。因为spring体系下的web应用都采用了MVC三层架构&#xff0c;依托于IOC&#xff0c;层级…

2023-11-28 LeetCode每日一题(设计前中后队列)

2023-11-28每日一题 一、题目编号 1760.设计前中后队列二、题目链接 点击跳转到题目位置 三、题目描述 请你设计一个队列&#xff0c;支持在前&#xff0c;中&#xff0c;后三个位置的 push 和 pop 操作。 请你完成 FrontMiddleBack 类&#xff1a; FrontMiddleBack() 初…

HCIP --- MGRE综合实验

一、总体规划 二、AR1配置思路及步骤 一、接口地址分配及缺省路由&#xff1a; The device is running! AR1&#xff1a; <Huawei>sy Enter system view, return user view with CtrlZ. [Huawei]sy r1 [r1]interface s4/0/0 [r1-Serial4/0/0]ip address 15.0.0.1 255.0…

Pytorch Lightning 完全攻略

Pytorch-Lightning这个库我“发现”过两次。第一次发现时&#xff0c;感觉它很重很难学&#xff0c;而且似乎自己也用不上。但是后面随着做的项目开始出现了一些稍微高阶的要求&#xff0c;我发现我总是不断地在相似工程代码上花费大量时间&#xff0c;Debug也是这些代码花的时…

Ubuntu 上使能 SELinux

首发公号&#xff1a;Rand_cs 此文档说明如何在 ubuntu 上启用 SELinux&#xff0c;测试环境为虚拟机&#xff0c;开始前一定一定一定先来个快照&#xff0c;不要问我为什么有三个一定。 卸载 apparmor&#xff08;可选&#xff09; ubuntu 默认安装的安全组件为 apparmor&a…

实时设计:带你0基础入门ComfyUI工作流#N3期AIGC训练营

想知道这个工作流是怎么实现的吗&#xff1f; 使用ComfyUI&#xff0c;一款基于Stable diffusion的节点式UI&#xff0c;低显存占用&#xff0c;完成SD使用流程的自动化。通过自定义的屏幕共享节点&#xff0c;调用实时LCM生成图像。 这个月还有 AI 训练营可以报名吗&#xff1…

第10关:基数排序

任务要求参考答案问答98 任务描述相关知识 基数排序算法编程要求测试说明 任务描述 本关任务&#xff1a;实现基数排序算法&#xff0c;并将乱序数列变成升序。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.基数排序算法。 基数排序算法 基数排序是按…