入门数据结构JAVA DS——二叉树的介绍 (构建,性质,基本操作等) (1)

news2024/12/28 22:57:09

前言

二叉树的概念和性质

二叉树的基本概念

二叉树的种类

二叉树的性质

二叉树的构建存储与遍历

存储

构建

遍历

前序遍历

后序遍历

中序遍历

层序遍历

二叉树的基本操作

获取树中结点个数

获取叶子结点个数

获取第K层结点的个数

获取二叉树的高度

检测值为value的元素是否存在

判断两棵树是否相同


需要笔者的代码请点击链接,都在github里了

MyJava/JavaDS2/src/tree at main · calljsh/MyJava (github.com)

前言


说实话,笔者在之前没有系统学习过数据结构之前,看到二叉树是有点害怕的,这也是我写下博客的原因之一,笔者想要告诉大家,它没有这么可怕,只要系统的学习过,有代码底子,是可以入门的,笔者将从如何构建二叉树,二叉树的性质,常见的操作等方面为主,以介绍一部分OJ题目为辅, 希望对你们有帮助

当然了,学习二叉树也需要你对于递归,搜索等算法思维有基础.

本文大致分成这个几个部分
 

1.二叉树的概念和性质

2.二叉树的构建存储与遍历

3.二叉树的基本操作

各位读者选择自己需要的部分查看即可

二叉树的概念和性质

二叉树(Binary Tree)是树形结构的一种特殊形式,其中每个节点最多有两个子节点,分别称为左子节点右子节点。二叉树是一种非常常用的数据结构,在计算机科学中被广泛应用于各种算法和数据存储方式。

二叉树的基本概念

  • 根节点:二叉树的顶点称为根节点(Root Node),它没有父节点。
  • 子节点:每个节点可以有两个子节点,分别是左子节点和右子节点。
  • 叶子节点:没有任何子节点的节点称为叶子节点(Leaf Node)。
  • 父节点:某个节点的上一级节点称为父节点。
  • 兄弟节点:同一个父节点下的两个子节点互称兄弟节点。
  • 深度:从根节点到当前节点的路径长度称为该节点的深度。
  • 高度:从当前节点到叶子节点的最长路径长度称为该节点的高度。
  • 层次:二叉树中节点所在的层,根节点为第1层,子节点为第2层,依次类推。

二叉树的种类

  1. 满二叉树:在满二叉树中,每一层的节点数都达到了最大值,即每个非叶子节点都有两个子节点。
  2. 完全二叉树:完全二叉树的每一层节点都是从左到右排列,只有最后一层的节点可以不满,但必须从左到右紧密排列。
  3. 平衡二叉树(AVL Tree):一种高度平衡的二叉树,其左右子树的高度差不超过1。
  4. 二叉搜索树(Binary Search Tree, BST):一种特殊的二叉树,满足左子树所有节点的值小于根节点,而右子树所有节点的值大于根节点。

二叉树的性质

二叉树有很多已经被研究出来的性质,笔者在此做一个小汇总

当然了,性质这种东西看看就好了,反正笔者是不能完全记住的 

二叉树的构建存储与遍历

存储

存储有链式存储和顺序存储,本文介绍链式存储

首先我们要知道,二叉树也是一个一个结点穿起来的,对于一个结点来说,除了存储结点的值以外,也需要存储两个"孩子"的地址,也就是左子树地址和右边子树地址,如果用JAVA代码来写,就是这样写的

   static class BTNode
    {
        BTNode left;
        BTNode right;
        int value;
        BTNode(int value) {
            this.value = value;
        }
    }

可以看到,二叉树的存储和前面的链表并无本质区别,所以只要前面基础打得好,其实不难的.

构建

其实二叉树的构建也是一个可以细说的点,笔者大致把他分成这两类

1.直接手动构建  顾名思义,就是自己动手把所有结点连接起来

2.告诉你遍历顺序,通过递归构建

第二类是有专门的OJ题目的,链接在下面

106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

二叉树遍历_牛客题霸_牛客网 (nowcoder.com)

10.黄金树 - 蓝桥云课 (lanqiao.cn)

这三题都是很典型的题目,笔者可以自己去写.

为了能介绍如何遍历,我们使用"最轮椅"的第一种方法,手动创建一颗二叉树

    private BTNode root;

    public BTNode createBinaryTree() {
        BTNode node1 = new BTNode(1);
        BTNode node2 = new BTNode(2);
        BTNode node3 = new BTNode(3);
        BTNode node4 = new BTNode(4);
        BTNode node5 = new BTNode(5);
        BTNode node6 = new BTNode(6);
        BTNode node7 = new BTNode(7);
        BTNode node8 = new BTNode(8);
        root = node1;
       node1.right=node2;
       node1.left=node3;
       node2.left=node4;
       node2.right=node8;
       node3.right=node5;
        return node1;
    }

画图能发现长成这样

一颗很普通的二叉树,不是满二叉树,也不是完全二叉树

遍历

我们通过这棵树讲遍历

前序遍历

前序遍历的顺序是:根节点 -> 左子树 -> 右子树

步骤

  1. 访问根节点。
  2. 前序遍历左子树。
  3. 前序遍历右子树。

我们采取递归的方法,先根,然后左子树,然后右子树

 public void preorder(BTNode root)
    // 前序遍历
    {
        if (root == null) {
            return;
        }
        System.out.print(root.value + " ");
        preorder(root.left);
        preorder(root.right);
    }

效果如下

1 3 5 2 4 8 

后序遍历

后序遍历的顺序是:左子树 -> 右子树 -> 根节点

步骤

  1. 后序遍历左子树。
  2. 后序遍历右子树。
  3. 访问根节点。

代码如下

  public void lastorder(BTNode root)
    // 后序遍历
    {
        if (root == null) {
            return;
        }
        lastorder(root.left);
        lastorder(root.right);
        System.out.print(root.value + " ");
    }

效果如下

5 3 4 8 2 1

中序遍历

中序遍历的顺序是:左子树 -> 根节点 -> 右子树

步骤

  1. 中序遍历左子树。
  2. 访问根节点。
  3. 中序遍历右子树。

代码如下

 public void inorder(BTNode root)
    // 中序遍历
    {
        if (root == null) {
            return;
        }
        inorder(root.left);
        System.out.print(root.value + " ");
        inorder(root.right);
    }

效果如下

3 5 1 4 2 8

 对于刚刚入门二叉树的朋友,鄙人的建议是多画图,多调试,让你的大脑多跟着代码的逻辑跑一跑,就能看懂代码了

层序遍历

层序遍历(Level Order Traversal)广度优先搜索(BFS, Breadth-First Search实际上是相似的概念,特别是在树的遍历中,层序遍历就是BFS的应用。

BFS怎么写可以看

经典bfs模板分享-长草 以及类似模板题-扩散_bfs模版题-CSDN博客

填坑-bfs解决扩散.-CSDN博客

二叉树的层序就更赤裸裸了

102. 二叉树的层序遍历 - 力扣(LeetCode)

 public void levelOrder(BTNode root)
    {
        if (root == null)
        {
            return;  // 如果根节点为空,直接返回
        }
        Queue<BTNode> queue = new LinkedList<>();
        queue.offer(root);  // 将根节点加入队列
        while(!queue.isEmpty())
        {
            BTNode temp=queue.poll();
            System.out.print(temp.value+" ");
            if(temp.left!=null)
                queue.offer(temp.left);
            if(temp.right!=null)
                queue.offer(temp.right);
        }
    }

 效果如下

1 3 2 5 4 8

二叉树的基本操作

笔者主要想介绍的是这么几个基本操作

部分操作,笔者会介绍两种写法,二者思路完全一致,只是代码风格不一样.

1. 获取树中节点的个数
2.获取叶子节点的个数
3.获取第 K 层节点的个数
4. 获取二叉树的高度
5.检测值为 value 的元素是否存在
6.判断两棵树是否相同

获取树中结点个数

获取树中的结点个数,只需要在遍历的基础上加上计数器即可,不管是前序后序中序层序.
代码如下
  public int treesize(BTNode root)
    // 节点个数
    {
        size1 = 0;
        dfs2(root);
        return size1;
    }

    private void dfs2(BTNode root) {
        if (root != null) {
            size1++;
            dfs2(root.left);
            dfs2(root.right);
        } else {
            return;
        }
    }

获取叶子结点个数

获取叶子结点个数,同样需要遍历我们的整棵树,但是我们需要判断条件了,如果一个结点既没有左子树也没有右子树,那么他就是一个叶子结点,

基于这个思路,我们给出如下代码

    private int sum;  // 用来存储叶子节点的数量
    public int leafsize(BTNode root) {
        sum = 0;  // 初始化sum
        dfs(root);  // 启动DFS
        return sum;  // 返回叶子节点的总数
    }

    private void dfs(BTNode node) {
        // 进入节点 node
        if (node == null) {
            return;
        }
        if (node.left == null && node.right == null) {
            // 如果是叶子节点,更新sum
            sum++;
            return;
        }
        // 递归处理左子树和右子树
        dfs(node.left);
        dfs(node.right);
        // 离开节点 node
    }

我大致阐述一下思路,如果你看不懂,说明你不适合学计算机或者你基础不好,需要多调试代码

进入搜索方法,首先判断这个结点是不是空,不是空,继续,判断是不是叶子结点, 是,就可以返回了,如果上述条件都不符合,就继续搜索,知道无法搜索为止(空或者是叶子结点)

也有更EZ的版本,就是递归,代码如下

        public int leafsize(BTNode root)
    // 叶子节点
    {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.right == null) {
            return 1;
        }
        return leafsize(root.left) + leafsize(root.right);
    }

二者的思路基本一致,只是风格不一样.

获取第K层结点的个数

代码如下

 public int getKLevelNodeCount(TreeNode root, int k) {
        // 如果节点为空,返回 0
        if (root == null) {
            return 0;
        }
        // 如果 k == 1,说明当前节点是第 k 层的节点
        if (k == 1) {
            return 1;
        }
        // 否则,继续递归查找左子树和右子树第 k-1 层的节点数量
        int leftCount = getKLevelNodeCount(root.left, k - 1);
        int rightCount = getKLevelNodeCount(root.right, k - 1);
        
        // 返回左右子树中第 k-1 层节点的数量之和
        return leftCount + rightCount;
    }

 public  int num=0;
    // 获取第K层节点的个数
    public int getKLevelNodeCount(BTNode root, int k)
    {
        if(root==null)
        {
            return 0;
        }
       Solve(root,k);
        return num;
    }
    private void Solve (BTNode root,int k)
    {
        if(root==null)
        {
            return ;
        }
        if(k==1&&root!=null)
        {
            num++;
            return ;
        }
        Solve(root.left,k-1);
        Solve(root.right,k-1);
    }

 思路就是找到K-1层,进而算出第K层的数量,如果能看懂前面的,这个笔者相信你们也可以

同样是一种思路两种码风,喜欢那种请读者们自便了,鄙人更喜欢后者

获取二叉树的高度

获取高度,本质上还是遍历我们的树,只不过,我们需要一个值,去存储目前已知的最大高度而已

    public int max;
    public int getHeight(BTNode root) {
        if (root == null) {
            return 0;
        }
        max = 1;
        DFS(root, 1);
        return max;
    }
    private void DFS(BTNode root, int num) {
        if (root.left == null && root.right == null) {
            max = Math.max(num, max);
            return;
        }
        if (root.left != null) {
            int num1 = num + 1;
            DFS(root.left, num1);

        }
        if (root.right != null) {
            DFS(root.right, num + 1);
        }
    }

 依旧是遍历树,然后,如果发现该结点已经是叶子结点,通过 max去存储最大高度,max默认为1,因为一颗树的最低高度就是1

检测值为value的元素是否存在

 如果你还能看到这里,不用我说你也应该知道思路,还是遍历!!!核心问题就是,为了减少对于性能的消耗,一旦找到了,我们就应该终止方法.但这毕竟不是简单的循环,只靠一个break就可以了,因此,笔者又引入了一个变量,如果找到了,我们通过修改变量达到目的

以下是笔者觉得最容易理解的风格了

 BTNode btNode;
    int pd = 0;
    BTNode find(BTNode root, int val) {
        Dfs(root, val);
        if (pd == 1)
            return btNode;
        else
            return null;
    }
    private void Dfs(BTNode root, int val)
    {
        if (pd == 1 || root == null) {
            return;
        }
        if (root.value == val) {
            btNode = root;
            pd = 1;
        }
        Dfs(root.right, val);
        Dfs(root.left, val);
    }

 可以看到,笔者用了变量pd来判断,肯定有更好的方法,但笔者觉得这个最好理解

判断两棵树是否相同

这个操作笔者觉得,很重要,学会了这个,你就可以判断一棵树是否含有某棵子树了,因此笔者列出来

思路与  检测值为value的元素是否存在 有异曲同工之妙

先给你们看看代码

    public int PD = 0; // 标志位,表示是否发现不相同
    public boolean ans = true; // 存储结果,默认是相同

    public boolean isSameTree(BTNode  p, BTNode  q) {
        dfs(p, q);
        return ans;
    }
    private void dfs(BTNode  p, BTNode  q) {
        if (PD == 1) {
            return; // 如果已经发现不同,直接返回
        }
        if (p == null && q == null) {
            return; // 如果两个节点都为空,继续递归
        }
        if (p == null || q == null || p.value != q.value) {
            PD = 1; // 如果只有一个节点为空或者值不同,标记为不同
            ans = false;
            return;
        }
        // 递归检查左右子树
        dfs(p.left, q.left);
        dfs(p.right, q.right);
    }

本质上就是不断的搜索,找有没有不同的点,找到了,立马改变判断变量,返回

那么,怎么算不同呢?如果都是空,那肯定是一样的,如果不是,就有三种情况,A空,B空,AB都不空,但是值不一样,我们就这样不断去找,找到了不同处就可以证明是不同的

结尾

知识简单,但汇总不易,笔者纯纯用爱发电,近来csdn 更改了流量卷的获得方式,更加强调数量了,笔者喜欢更新高质量的免费博客,所以恳请读者们多多点赞,收藏.笔者亦欢迎大牛们在评论区指指点点

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

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

相关文章

C++ —— vector 的模拟实现

目录 前言 1. vector深度剖析 2. 基础框架 3. 核心接口 3.1 reserve 3.2 push_back 和 pop_back 3.3 print 3.4 insert 3.5 erase 3.6 resize 4. 拷贝构造 4.1 构造与析构 4.2 拷贝构造 4.3 赋值重载 4.4 迭代器区间 5. 使用memcpy拷贝问题 前言 接:C —— 关于…

FX5 CPU模块和以太网模块的以太网通信功能

FX5 CPU模块和以太网模块的以太网通信功能的概要如下所示。 CPU模块的内置以太网端口的通信规格如下所示。 1、与MELSOFT的直接连接 不使用集线器&#xff0c;用1根以太网电缆直接连接以太网搭载模块与工程工具(GX Torks3)。无需设定IP地址&#xff0c;仅连接目标指定即可进行…

学习Java(一)类和对象

package demo.ceshi;public class Puppy {private int age;private String name;//构造器public Puppy( String name){this.name name;System.out.println("公主的名字叫&#xff1a;"name);}//设置age的值public void setAge(int age){this.age age;System.out.pr…

数值计算 --- 平方根倒数快速算法(中)

平方根倒数快速算法 --- 向Greg Walsh致敬&#xff01; 1&#xff0c;平方根倒数快速算法是如何选择初值的?WTF中的神秘数字究竟是怎么来的&#xff1f; 花开两朵&#xff0c;各表一枝。在前面的介绍中&#xff0c;我们已经知道了这段代码的作者在函数的最后使用了NR-iteratio…

CVE-2024-46103

前言 CVE-2024-46103 SEMCMS的sql漏洞。 漏洞简介 SEMCMS v4.8中&#xff0c;SEMCMS_Images.php的search参数&#xff0c;以及SEMCMS_Products.php的search参数&#xff0c;存在sql注入漏洞。 &#xff08;这个之前就有两个sql的cve&#xff0c;这次属于是捡漏了&#x1f6…

【MATLAB源码-第268期】基于simulink的永磁同步电机PMSM双闭环矢量控制系统SVPWM仿真,输出转速响应曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 永磁同步电机&#xff08;PMSM&#xff09;是目前工业领域中广泛使用的一种高效电机&#xff0c;其具有高功率密度、运行效率高、动态响应快等优点。在控制永磁同步电机时&#xff0c;通常采用矢量控制&#xff08;也称为磁场…

新160个crackme - 060-snake

运行分析 需破解Name和Serial PE分析 32位&#xff0c;未知程序和壳 点击Scan/t按钮外部扫描&#xff0c;发现是C程序 静态分析&动态调试 ida搜索关键字符串&#xff0c;双击进入 发现无法反编译 选中该函数&#xff08;地址&#xff1a;401048 - 401172&#xff09;Edit -…

认识结构体

目录 一.结构体类型的声明 1.结构的声明 2.定义结构体变量 3.结构体变量初始化 4.结构体的特殊声明 二.结构体对齐(重点难点) 1.结构体对齐规则 2.结构体对齐练习 (一)简单结构体对齐 (二)嵌套结构体对齐 3.为什么存在内存对齐 4.修改默认对齐数 三.结构体传参 1…

PMP--二模--解题--51-60

文章目录 14.敏捷--术语表--完成的定义DoD--它是团队需要满足的所有标准的核对单&#xff0c;只有可交付成果满足该核对单才能视为准备就绪可供客户使用。51、 [单选] 在冲刺计划会议上&#xff0c;Scrum主管重申&#xff0c;如果在冲刺结束时敏捷项目团队正在构建的产品增量没…

五种IO模型和阻塞IO

文章目录 五种 IO 模型和阻塞 IO1、五种 IO 模型1.1、阻塞 IO1.2、非阻塞 IO1.3、信号驱动 IO1.4、IO 多路转接1.5、异步 IO1.6、总结 2、高级 IO 概念2.1、同步通信&#xff08;synchronous communication&#xff09;和异步通信&#xff08;asynchronous communication&#…

第十五章:使用html、css、js编程制作一个网页版的下雪场景动画

背景:这是一个充满诗意的下雪场景代码。打开网页时,雪花轻轻飘落,覆盖住你的屏幕,仿佛置身于冬日的夜空下。背景音乐《我期待的不是雪》缓缓响起,伴随着雪花的飘动,仿佛心中的那份爱与温柔悄然绽放。 雪花的飘落是梦境般的存在,每一片雪花都是轻盈的告白,旋转着从天际…

使用GitHub Actions自动发布electron多端安装程序

GitHub Actions 是一个强大的自动化工具&#xff0c;可以帮助开发者在 GitHub 仓库中自动化构建、测试和部署工作流程。我们的客户端就是使用github action来打包项目发布的。 以下是关于 GitHub Actions 自动化构建的一些关键点和步骤&#xff1a; GitHub Actions 的基本概念…

go注册中心Eureka,注册到线上和线下,都可以访问

go注册中心Eureka&#xff0c;注册到线上和线下&#xff0c;都可以访问 本地通过127访问&#xff0c; 线上通过内网ip访问 package mainimport ("github.com/SimonWang00/goeureka""github.com/gin-gonic/gin""wbGo/controller""wbGo/task…

【工具变量】地市环保法庭试点城市DID数据集(2005-2023年)

数据简介&#xff1a;环保法庭是中国司法体系中专门处理环境资源案件的审判机构&#xff0c;其主要职责包括审理涉及自然环境污染、矿产资源保护、自然资源环境开发等环境资源民事纠纷案件&#xff0c;对不服下级人民法院生效裁判的环境资源民事案件进行审查&#xff0c;以及对…

Java_Se--方法

方法就是一个代码片段. 类似于 C 语言中的 "函数"。方法存在的意义(不要背, 重在体会): 1. 是能够模块化的组织代码 ( 当代码规模比较复杂的时候 ). 2. 做到代码被重复使用 , 一份代码可以在多个位置使用 . 3. 让代码更好理解更简单 . 4. 直接调用现有方法开…

cv中每个patch的关联

在计算机视觉任务中&#xff0c;当图像被划分为多个小块&#xff08;patches&#xff09;时&#xff0c;每个 patch 的关联性可以通过不同的方法来计算。具体取决于使用的模型和任务&#xff0c;以下是一些常见的计算 patch 关联性的方法&#xff1a; 1. Vision Transformer (…

IDA Pro-代码结构识别

Lab06-01.exe分析 1.由main 函数调用的唯一子过程中发现的主要代码结构是什么? if语句结构 找到main函数中唯一调用的函数&#xff0c;并进入 判断网络是否链接成功&#xff0c;如果返回0走右边未连接成功 2.位于0x40105F的子过程是什么? 将字符串压栈&#xff0c;猜测…

双非本 985 硕士,秋招上岸字节算法岗!

最近已有不少大厂都在秋招宣讲了&#xff0c;也有一些在 Offer 发放阶段。 节前&#xff0c;我们邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对新人如何快速入门算法岗、如何准备面试攻略、面试常考点、大模型项目落地经验分享等热门话题进行了深入的讨论。…

面向对象程序设计——set容器の简析

1.set的介绍 • 序列式容器和关联式容器 • 我们已经接触过STL中的部分容器如&#xff1a;string、vector、list、deque、array、forward_list等&#xff0c;这些容器统称为序列式容器&#xff0c;因为逻辑结构为线性序列的数据结构&#xff0c;两个位置存储的值之间⼀般没有紧…

Python GUI 编程:tkinter 初学者入门指南——窗口

目录&#xff1a; 创建窗口更改窗口标题更改窗口大小和位置窗口在屏幕上居中窗口设置的其他属性 Tkinter 是在 Python 中开发 GUI&#xff08;图形用户界面&#xff09;最常用的库。在本指南中&#xff0c;我们将引导您了解 Tkinter 的基本知识&#xff0c;学习如何使用 Tkinte…