Java:二叉树(1)

  从现在开始,我们进入二叉树的学习,二叉树是数据结构的重点部分,在了解这个结构之前,我们先来了解一下什么是树型结构吧!

一、树型结构

1、树型结构简介

  树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成的具有层次关系的集合。把它叫成树,是因为它看起来很像棵倒挂的树,也就是说,它是根朝上,叶朝下的。

  树具有以下几个特点:

1、有一个特殊的结点,称为根结点,它没有前驱结点。

2、除了根结点以外,其余结点被分为M(M>0)个互不相交的集合T1 、T2 、T3 ……Tm,其中每一个集合又是一棵类似的子树



2、树型结构的判断

如图:

1、A便是根结点,其余的B、C、D、E等都可以称为子树!

2、以此图,我们再引入两个概念:

父结点:有衍生出子类的结点称为父结点。比如A衍生出B、C、D,因此A是B、C、D结点的父结点!

子结点:父结点衍生出来的结点称为子结点。比如B、C、D都是A的子结点!

 注意:子树之间不能有交集。

子树之间有没有交集判断时有一个依据:  除了根结点以外,每个结点有且只有一个父结点!

分析此图:

1、C结点有 A 和 B两个父结点

2、结点有  D 和 H 两个父结点

因此,这个结构不是树型结构



3、树型结构的一些重要概念

1、结点的度:一个结点含有子树的个数称为该结点的度 ,如上图:A的度为4,D的度为2

2、树的度:一棵树中,所有结点度的最大值称为该树的度,如上图:该树的度为4

3、叶结点或终端结点:度为0的结点称为叶结点,如上图,E、F、G、H、J都称为叶结点

4、结点的层次:从根开始定义起,根为第一层,根的子结点为第二层,以此类推。

5、树的高度:即树中结点的最大层次,如图,该树的高度是3。

6、兄弟结点:拥有同一个父结点的称为兄弟结点,比如B、C、D、E互为兄弟结点。

7、堂兄弟结点:父结点在同一层次的称为堂兄弟结点,比如G和H互为堂兄弟结点。



二、二叉树 【概念】

1、什么是二叉树

二叉树是结点的一个有限集合,该集合

1、或者为空

2、或是是由一个根结点加上两棵分别称为左子树右子树的二叉树组成!

那为什么称为二叉树?

答:此树不存在度大于2的结点 。从上图可以看到,每个结点的度都不会超过2,因此它称为二叉树!



2、两种特殊的二叉树

1、满二叉树:

一颗二叉树,如果每层的结点数都达到最大值,则这棵树称为满二叉树,换言之,如果一棵二叉树的层数为K,且结点数是2^k -1,则它就是满二叉树!

如图:

2、完全二叉树:

对于层数为k的,结点数为n的二叉树,当且仅当其每一个结点都与层数为k的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树

通俗点说,就是将结点从上到下,从左到右依次存放!

比如此图,它就是一个完全二叉树,它的每个结点都是按照右从上到下,从左到右依次存放!

举一个不是完全二叉树的反例:

那么,我们将其修改成完全二叉树,看看需要怎么操作?

只需要将给红色结点添加两个子结点,此时,这个又是一个完全二叉树!



3、二叉树的性质 

1、对于任何一棵二叉树,如果叶结点个数为n0,度为2的非叶结点个数为n2,则有n0=n2+1 

如果有兴趣的伙伴,我们可以来推导一下这个公式。

这个推导基于我们知道一个事实:一棵N个结点的树有N-1条边

推导过程:

假设二叉树度为0的结点个数为n0度为1的结点个数为n1度为2的结点个数为n2

那么我们可以知道,度为0的结点可以产生0条边,度为1的结点可以产生1条边,度为2的结点可以产生2条边!

就有:N-1=n1+n2+n2

           N=n0+n1+n2

将2式代入1式:n0+n1+n2-1=n1+n2+n2   ------>>   n0=n2+1


2、具有n个结点的完全二叉树的高度k为log2(n+1)上取整

如图,该完全二叉树有10个结点,log2(11)的结果是在3和4之间的,那么就向上取整,因此其高度是4!


3、对应具有n个结点的完全二叉树,如果按照从上至下,从左至右的顺序对所有结点从0开始编号,则对于序号为i的结点有:

》若i>0,父结点序号为:(i-1)/2  ;若i=0;无父结点:

如图:

》若2i+1<n,则下标为i的结点的左孩子序列为2i+1;否则就是无左孩子

》若2i+2<n,则下标为i的结点的右孩子序列为2i+2;否则就是无右孩子 

如图:


4、有奇数个结点的完全二叉树, 度为1的结点个数为0;有偶数个结点的完全二叉树,度为1的结点个数为1。



 三、二叉树的存储

1、二叉树的节点

二叉树的存储结构分为:顺序存储类似于链表的链式存储

现在我们不介绍顺序存储,我们先来学习链式存储

二叉树的链式存储是由一个一个的节点连接起来的,每个节点包含三个域。

class Node{
int val;      //数据域
Node left;    //左孩子的引用
Node right;   //右孩子的引用
}



 2、二叉树的遍历

  前面我们学习链表和顺序表的时候,其实本质上都是在遍历链表和顺序表,以此完成增删查改操作,由于二叉树是非线性结构,因此其遍历的方式比较不同,所有在实现二叉树之前,让我们先来了解一下二叉树的遍历方法! 

二叉树有以下四种遍历方法:

》前序遍历:  根、左、右(先遍历根节点,然后再遍历左子树,最后遍历右子树)

》中序遍历:  左、 根 、右(先遍历左子树,然后遍历根节点,最后遍历右子树)

》后序遍历:  左 、右 、根(先遍历左子树,然后遍历右子树,最后遍历根节点)

》层序遍历: 从上到下,从左到右  依次遍历

让我们以打印二叉树结点数据为例子,看看不同的遍历序列有什么不同:

前序遍历:根  左  右(先遍历根节点,然后再遍历左子树,最后遍历右子树)

注意:图中的数字代表二叉树遍历的前后顺序!蓝色箭头为即遍历路径无论使用何种遍历方式,遍历路径都是不变的!

前序遍历要求:无需遍历树的左右子树便可以打印该节点的内容;

听不懂也没有关系,只要我们结合下面三种遍历方式的不同便可以深刻的理解了!

进入A(打印A),把A作为根节点,再遍历节点A的左子树, 

进入B(打印B),把B看作新的根节点,再遍历B的左子树;

进入D(打印D),把D看作新的根节点,再遍历D的左子树;

发现D的左子树为null,回到D,再遍历D的右子树;

发现D的右子树为null,回到D;

(注意:此时完成了第一颗树D的遍历)

回到B,再遍历B的右子树;

发现B的右子树为null,回到B;

(注意:此时完成了第二棵树B的遍历)

回到A,再遍历A的右子树C;

进入C(打印C),把C看作新的根节点,再遍历C的左子树;

进入E(打印E),把E看作新的根节点,再遍历E的左子树,

发现E的左子树为null,回到E,再遍历E的右子树;

发现E的右子树为null,回到E;

(注意:此时完成了第三棵树E的遍历)

回到C,再遍历C的右子树F;

进入F(打印F),把F看作新的根节点,再遍历F的左子树;

发现F的左子树为null,回到F;再遍历F的右子树;

发现F的右子树为null,回到F;

(注意:此时完成了第四棵树F的遍历)

回到C;

(注意:此时完成了第五颗树C的遍历)

回到A;

(注意:此时全部的树都已经遍历完成)

因此,前序遍历打印的结果为:ABDCEF


中序遍历:  左、 根 、右(先遍历左子树,然后遍历根节点,最后遍历右子树)

中序遍历的时候,二叉树的遍历路径也是一样的,但是中序遍历要求:只有在完成每一棵树的左子树的遍历后,才可以打印该节点的内容;

比如,第一次进入A的时候,不能直接打印,要等A树的左子树遍历完成,再次回到A时才可以打印A。

进入A,把A作为根节点,再遍历A的左子树;

进入B,把B作为新的根节点,再遍历B的左子树;

进入D,把D作为新的根节点,再遍历D的左子树;

发现D的左子树为null,回到D,再遍历D的右子树;(此时D树的左子树遍历完成,可以打印D)

发现D的右子树为null,回到D;

(注意:此时完成第一棵树D的遍历)

回到B,然后再遍历B的右子树;(此时B树的左子树遍历完成,可以打印B)

发现B的右子树为null,回到B;

(注意:此时完成第二棵树B的遍历)

回到A,然后再遍历A的右子树C;(此时A树的左子树遍历完成,可以打印A)

进入C,把C看作新的根节点,再遍历C的左子树;

进入E,把E看作新的根节点,再遍历E的左子树;

发现E的左子树为null,回到E,再遍历E的右子树;(此时E树的左子树遍历完成,可以打印E)

发现E的右子树为null,回到E;

(注意:此时完成第三棵树E的遍历)

回到C,再遍历C的右子树F;(此时C树的左子树遍历完成,可以打印C)

进入F,把F看作新的根节点,再遍历F的左子树;

发现F的左子树为null,回到F,再遍历F的右子树;(此时F树的左子树遍历完成,可以打印F)

发现F的右子树为null,回到F;

(注意:此时完成第四棵树F的遍历)

回到C;

(注意:此时完成第五课树C的遍历)

回到A;

(注意:此时完成全部树的遍历)

因此,中序遍历打印的结果为:DBAECF


后序遍历:  左 、右 、根(先遍历左子树,然后遍历右子树,最后遍历根节点)

 后序遍历时,要求完成每一棵树的左右子树的遍历,才可以打印该节点的内容;

进入A,把A作为根节点,再遍历A的左子树;

进入B,把B作为新的根节点,再遍历B的左子树; 

进入D,把D作为新的根节点,再遍历D的左子树;

发现D的左子树为null,回到D,再遍历D的右子树;

发现D的右子树为null,回到D;(此时D树的左右子树遍历完成,可以打印D)

(注意,此时完成第一颗树D的遍历)

回到B,再遍历B的右子树;

发现B的右子树为null,回到B;(此时B树的左右子树遍历完成,可以打印B)

(注意,此时完成第二棵树B的遍历)

回到A,再遍历A的右子树C;

进入C,把C作为新的根节点,再遍历C的左子树;

进入E,把E作为新的根节点,再遍历E的左子树;

发现E的左子树为null,回到E,再遍历E的右子树;

发现E的右子树为null,回到E;(此时E树的左右子树遍历完成,可以打印E)

(注意:此时完成第三棵树E的遍历)

回到C,再遍历C的右子树;

进入F,把F作为新的根节点,再遍历F的左子树;

发现F的左子树为null,回到F,再遍历F的右子树;

发现F的右子树为null,回到F;(此时F树的左右子树遍历完成,可以打印F)

(注意:此时完成第四棵树F的遍历)

回到C;(此时C树的左右子树遍历完成,可以打印C)

(注意:此时完成第五颗树C的遍历)

回到A;(此时A树的左右子树的遍历完成,可以打印A)

(注意:此时完成全部树的遍历)

因此,后序遍历打印的结果为:DBEFCA

层序遍历的情况暂且不作讲解,大家有兴趣可以自行了解!



四、二叉树的实现

现在我们万事俱备,是时候来了解一下二叉树的实现了!

类文件,首先,我们先创建一个TestBinaryTree类,用来实现我们的二叉树;

这个Test类,用来测试我们的二叉树!

1、节点类:

通过前面的学习,我们知道,二叉树的每一个节点包含三个域:left、val、right;

因此,我们需要在TestBinaryTree类内部定义一个内部的节点类!

 

内部类的实现:

注意,这个Node类是定义在TestBinaryTree类的内部的!



2、创建一个简易的二叉树: 

在实现遍历方法之前,我们需要创建一个二叉树!这里我们不妨在TestBinaryTree类内部实现一个方法,创建一个如图的二叉树!

 //实现一个方法:该方法可以简易创建一个二叉树
    public Node createTree(){
        //实例化几个对象
        Node A=new Node('A');
        Node B=new Node('B');
        Node C=new Node('C');
        Node D=new Node('D');
        Node E=new Node('E');
        Node F=new Node('F');
        Node G=new Node('G');
        Node H=new Node('H');

        //连接这几个对象
        A.left=B;
        A.right=C;
        B.left=D;
        B.right=E;
        C.left=F;
        C.right=G;
        E.right=H;

        return A;
    }


3、前序遍历方法:  


    //实现前序遍历:根、左、右
    public void preOrder(Node root){
        //如果root==null,返回
        if(root==null){
            return;
        }
        //先打印根节点内容
        System.out.print(root.val+" ");
        //再遍历左子树
        preOrder(root.left);
        //最后遍历右子树
        preOrder(root.right);
    }

这个方法是通过递归实现,因此我们还是举一个例子来让大家充分理解它的递归过程!

以如下图的二叉树为例子!

 



4、中序遍历方法:

//实现中序遍历:左、根、右
    public void inOrder(Node root){
        if(root==null){
            return;
        }
        //先遍历左子树
        inOrder(root.left);
        //再打印根节点内容
        System.out.print(root.val+" ");
        //最后遍历右子树
        inOrder(root.right);
    }


5、后序遍历方法:

 //实现后序遍历:左、右、根
    public void postOrder(Node root){
        if(root==null){
            return;
        }
        //先遍历左子树
        postOrder(root.left);
        //再遍历右子树
        postOrder(root.right);
        //最后打印根节点内容
        System.out.print(root.val+" ");
    }



6、遍历方法的运行结果



7、计算二叉树节点个数的方法:

首先,我们需要知道

树的节点个数=左子树的节点个数+右子树的节点个数+1 

这个1就是代表这棵树的根节点,在实际计算的过程当中,我们又可以将每一个节点看作一颗树,因此,这个方法我们使用递归实现!

//计算二叉树的节点个数
    public int size(Node root){
        if(root==null){
            return 0;
        }
        int ret=size(root.left)+size(root.right)+1;
        return ret;
    }


8、计算叶子节点的个数的方法:

这里我们需要弄清楚一个点,什么是叶子节点,叶子节点就是没有左右子树的节点!

计算的主体逻辑:叶子节点的个数=左子树叶子节点的个数+右子树叶子节点的个数

 //计算二叉树的叶子节点的个数
    public int getLeafNodeCount(Node root){
        if(root==null){
            return 0;
        }
        //叶子节点
        if(root.left==null&&root.right==null){
            return 1;
        }
        int ret=getLeafNodeCount(root.left)+getLeafNodeCount(root.right);
        return ret;
    }


9、计算第K层有几个节点的方法:

  //计算第K层有多少个节点
    public int getKLevelNodeCount(Node root,int k){
        if(k==0){
            return 0;
        }
        if(k==1){
            return 1;
        }
        return getKLevelNodeCount(root.left,k-1)+
                getKLevelNodeCount(root.right,k-1);
    }



10、计算整棵树的高度的方法:

整棵树的高度=左子树高度和右子树高度中的最大值


    //计算二叉树的高度
    public int getHeight(Node root){
        if(root==null){
            return 0;
        }

        int leftHeight=getHeight(root.left);
        int rightHeight=getHeight(root.right);

        return leftHeight>rightHeight?leftHeight+1:rightHeight+1;
    }


11、查找某个值的方法:

 //找到值为val的节点
    public Node find(Node root,char val){
        if(root==null){
            return null;
        }
        if(root.val==val){
            return root;
        }
        Node nodeLeft=find(root.left,val);
        if(nodeLeft!=null){
            return nodeLeft;
        }
        Node nodeRight= find(root.right,val);
        if(nodeRight!=null){
            return nodeRight;
        }
        //找不到,返回null
        return null;
    }

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

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

相关文章

三角函数与其他复杂函数在C语言中的实现:CORDIC算法、泰勒公式、查表法与math库详解

在C语言中实现三角函数&#xff0c;通常有四种主要方法&#xff1a;CORDIC算法、泰勒公式展开、查表法以及直接调用C语言的标准数学库。接下来我们将详细介绍这四种方法&#xff0c;并探讨其他可能的补充实现手段。 1. CORDIC算法 CORDIC&#xff08;Coordinate Rotation Dig…

DDR3简介

文章目录 前言一、ddr_stress_tester_v2.90配置流程二、将inc配置文件下载到板子上1.连接方式2.打开DDR_Tester 软件 uboot中DDR初始化的修改 前言 &#x1f4a6;DDR3在自己做完板子后需要验证下&#xff0c;测试DDR3是否能正常使用&#xff0c;如果不能正常使用&#xff0c;其…

前缀和 求数列的子序列的K倍区间

(直接截图比复制文字要好多了) 不会做的时候我去看了之前做的关于这道题目的笔记&#xff0c; &#xff08;Ak 1&#xff09;% k 1 &#xff08;Ak 1 Ak&#xff09;% k 1 只要发现了同余数的情况就说明有一个区间满足了题目的要求。 这个方法的精妙之处就在于前缀和包括了…

STM32H7使用FileX库BUG,SD卡挂载失败

问题描述&#xff1a; 使用STM32H7ThreadXFileX&#xff0c;之前使用swissbit牌的存储卡可正常使用&#xff0c;最近项目用了金士顿的存储卡&#xff0c;发现无法挂载文件系统。 原因分析&#xff1a; 调试过程发现&#xff0c;关闭D-Cache可以挂载使用exfat文件系统。 File…

C语言-用二分法在一个有序数组中查找某个数字

1.题目描述 有15个数按由大到小顺序放在一个数组中&#xff0c;输入一个数&#xff0c;要求用折半查找法找出该数是数组中第几个元素的值。如果该数不在数组中&#xff0c;则输出“无此数” 二.思路分析 记录数组中左边第一个元素的下标为left&#xff0c;记录数组右边第一个…

2024年畜牧、养殖业与智慧农业国际会议(ICLAHSA2024)

2024年畜牧、养殖业与智慧农业国际会议(ICLAHSA2024) 会议简介 2024国际畜牧业与智慧农业大会&#xff08;ICLAHSA2024&#xff09;将在深圳隆重举行。本次大会旨在汇聚全球畜牧业、畜牧业、智慧农业等领域的专家学者&#xff0c;共同探索行业前沿技术、创新模式和发展趋势…

电脑回收站的东西还原后会在哪里?一文给你答案!

“很奇怪&#xff0c;想问问大家&#xff0c;我电脑回收站里还原的文件会被保存在哪里呀&#xff1f;刚刚恢复文件的时候本来想直接将它拖出&#xff0c;却发现文件不见了&#xff0c;这种情况应该怎么解决呢&#xff1f;” 电脑回收站是一个特殊的文件夹&#xff0c;用于临时存…

【LLMOps】小白详细教程,在Dify中创建并使用自定义工具

文章目录 博客详细讲解视频点击查看高清脑图 1. 搭建天气查询http服务1.1. flask代码1.2. 接口优化方法 2. 生成openapi json schema2.1. 测试接口2.2. 生成openapi schema 3. 在dify中创建自定义工具3.1. 导入schema3.2. 设置工具认证信息3.3. 测试工具 4. 调用工具4.1. Agent…

PC-3000 Mobile Pro: 智能手机及平板设备数据提取工具

天津鸿萌科贸发展有限公司从事数据安全业务20余年&#xff0c;在数据恢复、数据取证、数据备份等领域有丰富的案例经验、前沿专业技术及良好的行业口碑。同时&#xff0c;公司面向取证机构及数据恢复公司&#xff0c;提供数据恢复实验室建设方案&#xff0c;包含数据恢复硬件设…

跨境电商亚马逊、虾皮等平台做测评要用什么IP?

IP即IP地址&#xff0c;IP地址是指互联网协议地址&#xff08;英语&#xff1a;Internet Protocol Address&#xff0c;又译为网际协议地址&#xff09;&#xff0c;是IP Address的缩写&#xff0c;IP地址是IP协议提供的一种统一的地址格式 功能&#xff1a;它为互联网上的每一…

SpringMVC笔记——SpringMVC基础Tomcat环境配置

Tomcat安装配置 下载Apache Tomcat 进入官网https://tomcat.apache.org/&#xff0c;选择tomcat 9 这边使用idea开发&#xff0c;建议直接下载压缩包 无法访问下载的可以直接用我的下载链接&#xff1a;https://cloudreve.zxbdwy.online/s/6nSA 提取码&#xff1a;w1pwk3将压…

嵌入式学习60-C++

知识零碎&#xff1a; C# &#xff1a;window下用于vs stdio编程 …

Pyside6:QDialog按钮变为中文

如果在Qt Designer中创建了一个Qdialog&#xff0c;自带按钮的类型&#xff0c;那么在Designer中显示是中文&#xff0c;但在运行时将变成英文。 如果程序不需要进行国际化&#xff0c;只在国内使用&#xff0c;那么进行中文化的操作还是有必要的&#xff0c;其实方式很简单&am…

常见七大排序(汇总)

目录 引言 交换函数 直接插入排序 思想 时间复杂度 希尔排序 思想 时间复杂度 选择排序 思想 时间复杂度 堆排序 思想 时间复杂度 冒泡排序 思想 时间复杂度 快速排序&#xff08;递归&#xff09; 霍尔法 前后指针法 三数取中 & 随机值法 第一种是随…

C++ 核心编程 - 内存分区模型

文章目录 1.1 程序运行前1.2 程序运行后1.3 new 操作符 C 程序在执行时&#xff0c;将内存大致划分为 4个区域&#xff1a; 代码区&#xff1a;存放函数体的二进制代码&#xff0c;由操作系统进行管理&#xff1b;全局区&#xff1a;存放全局变量和静态变量以及常量&#xff1…

【CSS】CSS实现元素逐渐消失(实现元素透明逐渐消失/模糊)

mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 10%);mask-image 属性用于定义一个遮罩&#xff0c;它可以隐藏元素的一部分或全部内容。在这个示例中&#xff0c;我们使用 mask-image 属性来定义一个线性渐变的遮罩&#xff0c;使得列表项的内…

nginx配置ip_hash负载均衡策略

一、nginx配置ip_hash负载均衡策略 nginx默认的负载均衡策略为轮询&#xff0c;某些场景需要使用ip_hash负载策略&#xff0c;即&#xff1a;同一个ip地址&#xff0c;永远访问nginx后面同一台tomcat。配置示例如下&#xff0c;主要是设置ip_hash&#xff1a; upstream www.ab…

机器视觉系统-工业光源什么是高角度光,以及发光角度得分类

光路描述&#xff1a;光线与水平面角度>45称为高角度光。 效果分析&#xff1a;高角度照射&#xff0c;光线经 被测物表面平整部分反射后进入镜头&#xff0c;图像效果表现为灰度值较高&#xff1b;不平整部分反射光进入不了镜头&#xff0c;图像效果表现为灰度值较低。 主要…

【Vue】自定义事件实现组件之间的通信(案例讲解)

一、前言 这是部分哔哩哔哩上跟着一个博主【遇见狂神说】学习的&#xff0c;当然自己也是才开始学习的vue&#xff0c;在学到这个Vue的自定义事件的时候&#xff0c;虽然知识点很绕&#xff0c;但是在理解后又觉得很意思&#xff0c;觉得Vue真的很强大。这里博主将自己学习到的…

2、选择什么样的机器人本体

如果说世界是物质的&#xff0c;那么应该先制造出机器人的本体&#xff0c;再让她产生灵魂。如果是精神的呢&#xff0c;世界是无中生有的呢&#xff0c;那就先在仿真中研究算法吧。 而我比较崇尚初中哲学的一句话&#xff0c;世界是物质的&#xff0c;物质是运动的&am…