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

目录

有以下二叉树:

一、递归

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 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity中Shader的BRDF解析(三)

文章目录 前言一、BRDF中的镜面反射项二、分别解析每一个参数1、D、G函数&#xff1a;speclarTerm2、其他中间步骤3、光照颜色4、F函数&#xff08;菲涅尔函数&#xff09; &#xff1a;FresnelTermIBL在下篇文章中继续解析 三、最终代码.cginc文件:Shader文件&#xff1a; 前言…

Unity工具脚本-检测资源文件夹是否有预制件是指定层级

效果&#xff1a; 先在菜单栏里面找到Tools/CheckPrefabLayers打开窗口 代码&#xff1a; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine;public class CheckPrefabLayers : EditorWindow {public in…

直线(蓝桥杯)

直线 题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 在平面直角坐标系中&#xff0c;两点可以确定一条直线。如果有多点在一条直线上&#xff0c; 那么这些点中任意两点确定的直线是同一条。 给定平面上 2 3 个…

(Linux2.6内核)进程调度队列与切换

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 我们首先来了解几个概念 1. 进程在CPU上运行的时候&#xff0c;一定要运行完才行吗&#xff1f;答案是否定的&#xff0c;我们大部分的操作系统&#xff0c;主流就是分时操作系统&#xff0c;即基于时间片进程轮转执行的。 …

初次尝试http OAuth2验证的请求

第一次对接OAuth2验证的接口&#xff0c; 莫不着门道&#xff0c;后面获取token成功后&#xff0c;发现其实不难&#xff0c; 用postman举例&#xff1a; 其实挺简单。用客户端id秘钥 获取token---》后面的请求带上token 1,在head中增加 Authorization头 内容格式如上图&…

JAVA文件IO, File类, 字符流,字节流

文章目录 文件IO1. File2. IO流2.1 字符流2.1.1 Reader2.1.2 Writer 2.2 字节流2.2.1 InputStream2.2.2 FileInputStream2.2.3 利用Scanner进行字符读取2.2.4 OutputStream 文件IO I: Input, 从硬盘往内存读数据 O: Output, 从内存往硬盘输出数据 1. File Java 中通过 java…

服务器如何做好入侵防护

不管是企业还是个人&#xff0c;网上业务都需要依赖于服务器&#xff0c;服务器一旦被黑客入侵&#xff0c;企业会面临很多安全风险&#xff0c;比如业务被中断、数据被窃取、被加密勒索、服务器不稳定等影响。入侵防护&#xff0c;主机安全也是目前网络安全防护的一个重点。关…

通义千问 Qwen-7B-Chat-Int4 模型本地化部署

如需在本地或离线环境下运行本项目&#xff0c;需要首先将项目所需的模型下载至本地&#xff0c;通常开源 LLM 与 Embedding 模型可以从 HuggingFace 下载。 以本项目中默认使用的 LLM 模型 THUDM/ChatGLM2-6B 与 Embedding 模型 moka-ai/m3e-base 为例&#xff1a; 下载模型…

基于YOLO模型建筑工地个人防护设备目标检测

使用安全装备可以保护他们免受建筑工地的意外事故。据统计&#xff0c;每年有数以万计的工人在建筑工地受到严重伤害&#xff0c;造成终生困难。然而&#xff0c;通过自我监控来确保工人穿戴个人防护装备非常重要。在这方面&#xff0c;需要一个准确和快速的系统来检测工人是否…

怎么在电脑上做工作计划?

对于职场人士来说&#xff0c;想要提高工作效率&#xff0c;提前做好工作计划是非常有必要的。我们可以将所有的任务和工作计划都记录下来&#xff0c;并通过设定提醒时间来提醒自己&#xff0c;可以帮助我们更有效地管理时间&#xff0c;从而不会错过重要的工作和任务。 而在…

视觉测量基础

1. 相机模型 1.1 坐标系转换原理 世界坐标系(world Coords):点在真实世界中的位置&#xff0c;描述相机位置。 相机坐标系(Cameras Coords):以相机光学系统中心&#xff08;镜头中心&#xff09;为原点&#xff0c;建立相机坐标系。 图像物理坐标系(Film Coords):经过小孔成…

【Python 训练营】N_12 打印菱形图案

题目 打印菱形图案 分析 先把图形分成两部分来看待&#xff0c;前四行一个规律&#xff0c;后三行一个规律&#xff0c;利用双重for循环&#xff0c;第一层控制行&#xff0c;第二层控制列。 答案 # 方法一 for i in range(4):block **(2*i1)print({:^7}.format(block))…

前端面试灵魂提问

1.自我介绍 2.在实习中&#xff0c;你负责那一模块 3.any与unknow的异同 相同点&#xff1a;any和unkonwn 可以接受任何值 不同点&#xff1a;any会丢掉类型限制&#xff0c;可以用any 类型的变量随意做任何事情。unknown 变量会强制执行类型检查&#xff0c;所以在使用一个…

【扫雷】C语言实现扫雷小游戏

扫雷 游戏资源介绍游戏功能介绍游戏代码编写教程游戏功能测试自动排雷测试地雷标记测试取消标记测试踩雷判定测试重复游戏测试胜利判定测试 头文件游戏主体文件用户主体文件 游戏资源介绍 本次对之前的扫雷游戏进行了重新编写与更新&#xff0c;在此次的游戏实现中新增加了剩余…

uniapp中uni.navigateBack返回后刷新页面数据

文章目录 一、前言1.1、[uni.navigateBack](https://uniapp.dcloud.net.cn/api/router.html#navigateback) 二、方法2.1、父页面设置钩子函数onBackPress2.2、uni.$emit和uni.$on监听通知数据变更2.2.1、子页面2.2.2、父页面 2.3、onShow钩子函数处理数据2.3.1、子页面2.3.2、父…

解决ant-design-vue中Select组件v-model值为空字符串不显示placeholder的bug

方法一&#xff1a; 1.找到node_modules/ant-design-vue/es/vc-select/SingleSelector.js文件 搜索renderPlacehoder方法 将其修改为 const renderPlacehoder () > {const list props.values.filter(val > val.value ! );if (list[0]) {return null}... }2.在此文件中…

集合框架(二)LinkedList的常见使用

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍LinkedList的常见使用以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xff0c;友友们有任何问题可以在评论区留言 目…

vue3+ts 实现时间间隔选择器

需求背景解决效果视频效果balancedTimeElement.vue 需求背景 实现一个分片的时间间隔选择器&#xff0c;需要把显示时间段显示成图表&#xff0c;涉及一下集中数据转换 [“02:30-05:30”,“07:30-10:30”,“14:30-17:30”]‘[(2,5),(7,10),(14,17)]’[4, 5, 6, 7, 8, 9, 10, …

Windows10系统卸载服务和删除服务

记录一下Windows10系统卸载服务和删除服务 最近在使用自己win电脑的时候 发现服务里存在很久之前就没有使用的应用&#xff0c;对应的文件夹也都已经删除了&#xff0c;但是在win服务里一直存在&#xff0c;不知道会不会影响性能&#xff0c;看着吧还是强迫自己删掉好一些&…

安防视频监控/视频融合/云存储EasyCVR页面数据显示不全该如何解决?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…