数据结构与算法——16.二叉树

news2025/1/11 19:51:20

这篇文章我们来讲一下二叉树

目录

1.概述

2.代码实现

1.概述

树:(Tree)是计算机数据存储的一种结构,因为存储类型和现实生活中的树类似所以被称为树。

树的源头被称为,树其余分叉点被称为节点,而树这种数据结构的起始分叉点被称为根节点。树衍生的尽头就是叶,在树这种数据结构中把叶称之为叶节点

树中每一节点的起源点被称为父节点,衍生出去的点被称为子节点。没有父节点的就是根节点,没有子节点的就是叶节点,而同一父节点的就是兄弟节点

树的基础概念:

高度,是从下往上看,用来表示从根节点到最顶端叶节点所需要遍历的节点数 (包括叶节点)

深度与高度相反,是从上往下看,用来表示 从最顶端叶节点到根节点所需要遍历的节点数(包括根节点)。层数,即 高度 +1

二叉树:是最常用的树形结构,每个结点最多能够有两个子节点

完全二叉树:上层全部填满,并且最后一层按从左到右的顺序来填充。(前面讲的堆就是一个完全二叉树)

满二叉树:一个二叉树,如果每一层的结点数都达到最大值,则这个二叉树就是满二叉树。也就说,如果一个二叉树的结点总数为(2^k)-1,则它就是满二叉树

二叉查找树:所有的左节点值都小于父节点值,所有的右节点值都大于父节点值的二叉树。

注意:这里要注意完全二叉树和满二叉树的区别。

二叉树的实现方式有数组和链表两种实现方式。

下面看一下二叉树的遍历

遍历:即把二叉树的每一个节点都访问一遍。遍历有两种方式,即广度优先和深度优先,其中深度优先又分前序,中序和后序三种,下面来详细的看一下。

广度优先遍历:尽可能的先访问距离根节点最近的节点,也称为层序遍历

深度优先遍历:对于二叉树,深度优先遍历可以分为三种(要深入到叶子节点):

  • 前序遍历:对于每一颗子树,先访问该节点,然后是左子树,最后是右子树
  • 中序遍历:对于每一颗子树,先访问左子树,然后是该节点,最后是右子树
  • 后序遍历:对于每一颗子树,先访问左子树,然后是右子树,最后是该节点

下面用具体的实例来看一下二叉树的几种遍历方式:

解析:

广度优先,不解释了

前序遍历:前序遍历的定义是先访问该节点,然后是左子树,然后是右子树。就上图来说,首先是根节点即1,然后是左子树,即2这边,在这个子树上面依然按照上面的规则走,所以先是2,然后访问2的左子树,然后是右子树。然后又回到上面的根节点处,现在1的左子树访问完了,然后就访问1的右子树,右子树的访问规则和上面的是一样的。

中序遍历:中序遍历的定义是先访问左子树,然后是根节点,然后是右子树。依然拿上图来说,先访问左子树,即2那部分,然后看2有没有左子树,有,那就访问2的左子树,即4,然后是2,然后是2的右子树,2的那颗树访问完了,就回到根节点,即1,然后再访问1的右子树,逻辑和上面是一样的。这就是中序遍历。

后序遍历:后序遍历的定义是先访问左子树,然后是右子树,然后是根节点。拿上图来说,先访问1的左子树,到达了2后,先访问2的左子树,访问完再访问2的右子树,然后是2,然后回到1处,1的左子树访问完了,然后接着访问1的右子树,等右子树访问完后,最后访问根节点1.

2.代码实现

下面用代码来实现一下树的相关操作:

节点类如下:

下面看一下代码:

package Tree;

import java.util.LinkedList;

/**普通的二叉树的三种深度优先遍历*/
public class L1_Tree {

    public static void main(String[] args) {
        /*树的构造*/
        L1_TreeNode root = new L1_TreeNode(
                new L1_TreeNode(new L1_TreeNode(4),2,null),
                1,
                new L1_TreeNode(new L1_TreeNode(5),3,new L1_TreeNode(6))
        );
        postOrder2(root);
    }

    public static void preOrder1(L1_TreeNode node){
        if (node == null){return;}
        System.out.print(node.value+" ");
        preOrder1(node.leftTreeNode);
        preOrder1(node.rightTreeNode);
    }
    public static void inOrder1(L1_TreeNode node){
        if (node == null){return;}
        inOrder1(node.leftTreeNode);
        System.out.println(node.value+" ");
        inOrder1(node.rightTreeNode);
    }
    public static void postOrder1(L1_TreeNode node){
        if (node == null){return;}
        postOrder1(node.leftTreeNode);
        postOrder1(node.rightTreeNode);
        System.out.println(node.value+" ");
    }

    public static void preOrder2(L1_TreeNode node){

        LinkedList<L1_TreeNode> stack = new LinkedList<>();
        L1_TreeNode current = node;//代表当前节点

        while (current != null || !stack.isEmpty()){
            if (current!=null){
                System.out.println("去:"+current.value);
                stack.push(current);//压入栈,通过栈记住回来的路
                current = current.leftTreeNode;
            }else {
                L1_TreeNode pop = stack.pop();
                //System.out.println("回:"+pop.value);
                current = pop.rightTreeNode;
            }
        }

    }


    public static void inOrder2(L1_TreeNode node){
        LinkedList<L1_TreeNode> stack = new LinkedList<>();
        L1_TreeNode current = node;//代表当前节点

        while (current != null || !stack.isEmpty()){
            if (current!=null){
                //System.out.println("去:"+current.value);
                stack.push(current);//压入栈,通过栈记住回来的路
                current = current.leftTreeNode;
            }else {
                L1_TreeNode pop = stack.pop();
                System.out.println("回:"+pop.value);
                current = pop.rightTreeNode;
            }
        }
    }


    public static void postOrder2(L1_TreeNode node){
        LinkedList<L1_TreeNode> stack = new LinkedList<>();
        L1_TreeNode current = node;//代表当前节点
        L1_TreeNode pop = null;//最近一次弹栈的元素
        while (current != null || !stack.isEmpty()){
            if (current!=null){
                //System.out.println("去:"+current.value);
                stack.push(current);//压入栈,通过栈记住回来的路
                current = current.leftTreeNode;
            }else {
                L1_TreeNode peek = stack.peek();
                if (peek.rightTreeNode == null || peek.rightTreeNode == pop){
                    pop = stack.pop();
                    System.out.println("回:"+pop.value);
                }else {
                    current = peek.rightTreeNode;
                }
            }
        }
    }

    //一套代码完成二叉树的三种深度优先遍历
    public static void Order(L1_TreeNode node){
        LinkedList<L1_TreeNode> stack = new LinkedList<>();

        L1_TreeNode current = node;//代表当前节点
        L1_TreeNode pop = null;//最近一次弹栈的元素
        /**
         *前序:值左右
         *中序:左值右
         *后序:左右值
         * */
        while (current != null || !stack.isEmpty()){
            if (current!=null){
                stack.push(current);//压入栈,通过栈记住回来的路
                //sout(value)前序
                current = current.leftTreeNode;//处理左子树
            }else {
                L1_TreeNode peek = stack.peek();
                if (peek.rightTreeNode == null ){//没有右子树
                    //sout(value)中序
                    pop = stack.pop();
                    //sout(value)后序
                }
                else if (peek.rightTreeNode == pop){//右子树处理完成
                    pop = stack.pop();
                    //sout(value)后序
                }
                else {
                    //sout(value)中序
                    current = peek.rightTreeNode;//处理右子树
                }
            }
        }
    }

}

递归的很简单

主要就是要了解非递归的解决方案。

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

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

相关文章

未知非参数需求和有限价格变动的动态定价

英文题目&#xff1a;Dynamic Pricing with Unknown Non-Parametric Demand and Limited Price Changes 中文题目&#xff1a;未知非参数需求和有限价格变动的动态定价 单位&#xff1a;麻省理工学院&#xff0c;剑桥 时间&#xff1a;2019 论文链接&#xff1a;https://do…

制作频谱灯

最近研究了下傅里叶变换&#xff0c;用它可以通过采集声音信号由时域转换到频域内&#xff0c;从而得到声音的频谱信息&#xff0c;可以做个频谱灯。 主要使用ESP32来实现了他&#xff0c;实现效果如下&#xff1a; 频谱灯 为了可以带出去露营&#xff0c;我把它做的很大&…

ubuntu20.04下源码编译colmap

由于稠密重建需要CUDA&#xff0c;因此先安装CUDA&#xff0c;我使用的是3050GPU&#xff0c;nvidia-smi显示最高支持CUDA11.4。 不要用sudo apt安装&#xff0c;版本较低&#xff0c;30系显卡建议安装CUDA11.0以上&#xff0c;这里安装了11.1版本。 下载&#xff1a; cuda_1…

C语言之内存函数篇(3)

目录 memcpy memcpy的使用 memcpy的模拟实现 NO1. NO2. memcpy可否实现重叠空间的拷贝 my_memcpy memcpy memmove memmove memmove 分析 代码 memset memset的使用 memcmp memcmp的使用 <0 0 >0 今天我们继续介绍几个重要的内存操作函数。&…

js中的数据结构:栈,队列,链表,字典哈希表,树

栈&#xff1a;先进后出 队列&#xff1a;先进先出 链表&#xff1a; 单链表&#xff1a; 双链表&#xff1a; 环形链表&#xff1a;最后一个数据的next指针不是指向null&#xff0c;指向的是任意之间的一个数据&#xff0c;形成一个环 数组和链表的区别&#xff1a; 字典和哈…

FPGA的汽车尾灯控制Verilog

名称&#xff1a;汽车尾灯控制Verilog 软件&#xff1a;Quartus 语言&#xff1a;Verilog 要求&#xff1a; 设计一个汽车尾灯控制器。假设汽车尾部各有2个指示灯&#xff08;LED&#xff09;&#xff0c;分别代表转弯、刹车&#xff0c;控制器功能包括&#xff1a; &…

微信收款码费率0.38太坑了

作为一个有多年运营经验的商家&#xff0c;我本人在申请收款功能时曾经走过了不少弯路。我找遍了市面上的知名的支付公司&#xff0c;但了解到的收款手续费率通常都在0.6左右&#xff0c;最低也只能降到0.38。这个过程吃过不少苦头。毕竟&#xff0c;收款功能是我们商家的命脉&…

有什么好用的设备管理软件?智慧巡检对后勤运维有什么帮助?

定期巡检在设备管理、后勤管理和运维管理中扮演着不可或缺的角色&#xff0c;以及及时发现异常并控制风险。然而&#xff0c;传统巡检工作存在弊端。为了解决这些问题&#xff0c;“的修”报修系统对巡检管理功能进行了突破性的设计和开发。   “的修”报修巡检管理功能包含了…

C++之内部类实现总结(二百三十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

vscode终端中打不开conda虚拟包管理

今天&#xff0c;想着将之前鸽的Unet网络模型给实现一下&#xff0c;结果发现&#xff0c;在vscode中运行python脚本&#xff0c;显示没有这包&#xff0c;没有那包。但是在其他的ipynb中是有的&#xff0c;感觉很奇怪。我检查了一下python版本&#xff0c;发现不是我深度学习的…

转载 - 洞察问题本质,解决工作难题

作者&#xff1a;关苏哲 高效管理者的三大技能 问题界定的6个问题 1.你所需要解决的问题是什么&#xff1f; 2.你为什么需要解决这个问题&#xff1f; 3.你期待的理想结果是什么&#xff1f; 4.这个问题包括哪些子问题&#xff1f; 5.你曾经尝试过哪些解决方式&#xff1f…

Intel架构的基本知识

字节序 字节序根据存储的方向不同, 分为大端字节序(big-endian) 和 小端字节序(little-endian) 大端字节序(big-endian): 低字节存储在起始地址 小端字节序(little-endian): 高字节存储在起始地址 获得当前主机字节序 #include <iostream>using namespace std;int ma…

LabVIEW崩溃后所产生的错误日志文件的位置

LabVIEW崩溃后所产生的错误日志文件的位置 LabVIEW开发环境刚刚崩溃&#xff0c;请问我如何访问崩溃后自动生成的日志文件&#xff1f; LabVIEW崩溃后产生的转储文件位于何处&#xff1f; 代码导致了LabVIEW崩溃&#xff0c;请问哪些文件可以帮助NI技术支持了解具体原因&…

如何做到人声和背景音乐分离?简单粗暴,赶紧学起来~

在这个短视频盛行的时代&#xff0c;优质的背景音乐会让视频锦上添花&#xff0c;但也会造成类似的问题&#xff1a;想单独使用视频中的某一段人声&#xff0c;但会被背景音乐扰乱视听效果。这时就需要将人声和背景音乐进行分离了&#xff0c;今天就来教大家如何将人声和背景音…

最新文档:微信、企业号+地理位置定位+地图展示

概述 公司有需求&#xff0c;通过企业号打卡项目&#xff0c;需要用到企业微信定位 详细 前言 demo是基于微信、企业号平台的一个定位&#xff0c;地图展示项目 后台使用springboot架构搭建的与微信交互的服务&#xff0c;使用httpclient连接池&#xff0c;调用微信接口&…

小黑子—MyBatis:第二章

MyBatis入门2.0 四 小黑子诉说Mybatis核心配置文件详情4.1 多环境4.2 Mybatis的事务管理器 - transactionManager4.3 dataSource&#xff08;数据源&#xff09;4.3.1 不同类型下的数据源有不同的属性4.3.2 pool 和 unpooled 的区别4.3.3 配置具体的数据库连接池对象 4.4 prope…

【操作系统笔记三】内存寻址

物理寻址 主存&#xff08;内存&#xff09; 计算机主存也可以称为物理内存&#xff0c;内存可以看成由若干个连续字节大小的单元组成的数组每个字节都有一个唯一的物理地址&#xff08;Physical Address&#xff09;CPU访问内存前&#xff0c;先拿到内存地址&#xff0c;然后…

Django — 会话

目录 一、Cookie1、介绍2、作用3、工作原理4、结构5、用途6、设置7、获取 二、Session1、介绍2、作用3、工作原理3、类型4、用途5、设置6、获取7、清空信息 三、Cookie 和 Session 的区别1、存储位置2、安全性3、数据大小4、跨页面共享5、生命周期6、实现机制7、适用场景 四、P…

掌动智能浅析故障注入测试的好处与实践方法

在现代技术环境中&#xff0c;系统面临各种潜在的威胁和故障&#xff0c;如硬件故障、网络问题、软件错误等。为了应对这些挑战&#xff0c;开发团队需要确保系统在逆境中依然能够提供可靠的服务。故障注入测试是一种模拟现实故障和异常情况的方法&#xff0c;旨在提高系统的鲁…

vue3+ts+java使用WebSocket传输数据

一、环境 系统&#xff1a;win11 IDE&#xff1a;vscode 框架&#xff1a;electron22.0.0vite2vue3typescript4.8.4springboot2.2.5jdk1.8 二、websocket介绍 2.1 由来 WebSocket未出现之前&#xff0c;浏览器和服务器之间的通信是通过Web的poll技术进行通信&#xff0c;就…