数据结构(三):树

本文详细介绍了树的基本概念,包括树的定义、节点关系、分类以及度的概念。特别关注了二叉树的特性、性质和遍历方法,如前序、中序、后序和层序遍历。还讨论了二叉排序树、完全二叉树、平衡二叉树(AVL树)和B树(包括2-3树和B+树)的区别与实现。最后提到了Java中的TreeSet和TreeMap类,展示了它们在有序集合中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇文章仅仅用于巩固我复习树,写的不好的地方,欢迎大家指正。

树的定义:

           树是n个结点的有限集,当n=0时,叫做空树。而在任意的一颗非空树中,有且仅有一个特定的叫做根的结点,在n>1时,其余的结点就可以分为m(m>0)个互不相交的有限集,而每个有限集本身又都是一棵树,也叫做根的子树。

简述结点:

结点的关系:结点的子树的根叫做该结点的儿子,而该结点叫做父亲。同一个父亲之间的儿子叫    做兄弟结点。从根到某个结点所经过的分支上的所有结点都是它的祖先,而以某个 结点作为根的子树中的任意结点都是它的子孙。

 结点的分类:每一个结点可以有任意个儿子,也有可能是零个儿子。我们把具有零个儿子的结点   叫做叶结点或者终端结点;而大于零个儿子的结点可以分为分支结点或者根结点。

 路径:从结点n1到nk的路径定义为n1,n2...,nk这样的一个序列,使得对于一个i(1<=i<k)的结点ni    是 ni+1结点的父亲,这条路径的长为边的条数,即结点数-1。

度:度可分为度,深度和高度。结点的度即该结点的分支数,而树的度是树中每个结点当中度的  最大值;结点的深度则是从根结点到它的唯一路径的长,树的深度是结点深度最深的度;结          点的高度即结点的层次,是从该结点起到一个最长路径的叶结点的长,而树的高度总是等于          它的深度。

二叉树:

概述:

定义:二叉树是n(n>=0)个结点的有限集合;该集合或者在n=0时,我们称它为空二叉树,或          者是由一个根结点和两棵互不相交的,分别被称为根结点的左子树和右子树的二叉树                      组成。

特点:在二叉树中每个结点最多有两棵子树,所以在二叉树中不存在度大于2的结点;二叉树          是有顺序的,左子树和右子树的顺序不能随意颠倒。

性质:

  1. 在二叉树的第i层至多有2^i-1个结点(i>=1);
  2. 对于任何一棵二叉树T,如果其叶结点数为n0,度为2的结点数为n2,则n0=1+n2;
  3. 如果对一棵有n个结点的完全二叉树进行层序编号,那么对任一结点i(1<=i<=n)有: 如果i=1的话,则根结点就是结点i;如果i>1的话,那么结点i的父结点为结点[i/2];如果2i>n,则结点i无左孩子,否则其左孩子就是结点2i;如果2i+1>n,则结点i无右孩子, 否则 其右孩子就是结点2i+1.

二叉树遍历:

前序遍历:若二叉树为空,则按空操作返回,否则先前序遍历左子树,再前序遍历右子树。(先根再左再右)。

中序遍历:若树为空,则按空操作返回,否则从根结点开始(注意不是先访问根结点),中序遍历根结点的左子树,然后访问根结点,最后再中序遍历右子树。(先左再根再右)。

后序遍历:若树为空,则按空操作返回,否则按照从左到右先叶子再结点的方式遍历访问左右 子树,最后访问根结点。(先左再右再根)。

层序遍历:若树为空,则按空操作返回,否则从树的第一层,也就是根开始访问,从上而下遍历,而在同一层中则按从左到右的顺序对结点访问。

树,森林与二叉树的转换:

树转换为二叉树:

  • 加线:在结点的所有兄弟结点之间加一条边使其相连;
  • 去线:对树的每个结点,只保留它与第一个孩子(最左)结点的边,删除它与其他孩子的边;
  • 层次调整:以树的根结点为轴心,将树调整一定的角度,使其结构分明。(注意结点第一个                      孩 子结点即作为该结点的左孩子,而兄弟结点转换过来的则是右孩子)

森林转换为二叉树:

  • 森林是由若干棵树组成的。
  • 先把每个树转换为二叉树;
  • 第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,使其连接。

二叉树转换为树:

  • 加线:若某结点的左孩子存在,则将这个左孩子的右孩子结点,右孩子的右孩子结点等,即左孩子的n个右孩子都作为该结点的孩子,使其它们连接。
  • 去线:删除原二叉树中所有结点与其右孩子结点的边。
  • 层次调整。

二叉树转换为森林:

  • 从根结点开始,若其右孩子存在,则把与右孩子结点之间的边删除
  • 再看分离后的二叉树,若其右孩子存在,则继续删除边。
  • 最后把每一棵分离的二叉树转换为树即可。

二叉排序树:

定义:

          二叉排序树,又叫做二叉查找树。构造一棵二叉排序树的目的在于为了提高查找,插入和删除的效率。对于一棵二叉排序树,它具有以下性质:1.若它的左子树不空,则左子树上所有结点的值都小于它的根结点的值;2.若它的右子树不空,则右子树上所有结点的值都大于它的根结点的值;3.同时它的左右子树也都是二叉排序树。

简述二叉排序树的删除:

         因为可能二叉排序树的查找和插入没有什么坑,所有就说一说二叉排序树的删除吧。二叉排序树的删除可以分为三种情况:1.删除的结点为叶结点,那么直接删除即可,因为它们不影响其他结点。2.删除的结点为只有左子树或者右子树,这种情况可以让这个“独子”子承父业。3.删除的结点既有左子树又有右子树,那么可以让需删除的结点的直接前驱或者后继结点替代要删除的结点,然后删除这个直接前驱或者后继结点。(也可以理解为需把结点左子树最大的结点或者右子树最小的结点替代后删除)

二叉排序树的简单实现:

       这里就采用链表的形式进行实现,写的很简略,也缺乏更多的测试,错误处理和逻辑整合,主要是帮助我归纳一种思路。

1.树和结点实现:

2.插入:

3.删除:(写的有点绕了,而且没想到出错的是删除叶结点……)

4.测试:(测试也很简单)



完全二叉树:

满二叉树:

定义:在一棵二叉树中,如果所有的分支结点都存在左子树和右子树,并且所有的叶结点都在同一层上,那么这种二叉树叫做满二叉树。

特点:叶结点只能出现在最下一层,否则不可能达到平衡;非叶结点的度一定是2;在同样深度的二叉树中,满二叉树的结点个数最多。

定义:

       对一棵具有n个结点的二叉树按层序编号后,除最下面的那层外,其它各层的结点数都达到最大个数(即都有左右子树),最下面的那层所有的结点都连续集中在最左边,这就是完全二叉树。

特点:

  1. 满二叉树一定是一棵完全二叉树,但完全二叉树不一定是一棵满二叉树。
  2. 叶结点只可能出现在最下面的两层。
  3. 最下面的那层的叶结点一定是集中在左部连续位置。
  4. 倒数第二层,如果有叶结点,那么它们一定是在右部连续位置。
  5. 如果结点的度为1,那么该结点就只有左孩子。
  6. 如果一棵完全二叉树的结点总数为n,那么叶子结点等于n/2(当n为偶数时)或者(n+1)/2(当n为奇数时)。

平衡二叉树(AVL树):

定义:

        平衡二叉树是一种高度平衡的二叉排序树,它的每一个结点的左子树和右子树的高度差至多等于1,增加和删除元素的操作则可能需要借由一次或多次树的旋转,以实现树的重新平衡,以这种方式来提高查找,插入和删除等的效率。

平衡因子:

         我们将树的结点的左子树的高度减去右子树的高度所得出来的值叫做平衡因子BF。在平衡二叉树上的结点的平衡因子只可能是-1,0,1。其他情况都要进行旋转来改变平衡因子的值。

实现原理:

          在构建二叉排序树的过程中,每次在插入和删除时,先检查是否因为这些操作而破环了树的平衡,如果破坏了,则找出最小失衡子树(在新插入的结点向上查找,以第一个平衡因子的值大于 1或者小于-1 的结点为根的子树称为最小不平衡子树)。在保持二叉排序树的特点的前提下,调整最小失衡子树中各个结点之间的连接关系,调整的策略为旋转,使它成为新的平衡子树。

旋转:

旋转分为两种:

  1. 左旋:在找到最小失衡子树后,把它的根结点移动到根结点右孩子的左子树上,如果原本右             孩子它存在左子树,那么把该左子树移动到原来根结点的右子树上,最后让右孩子成             为 新的根结点。
  2. 右旋:在找到最小失衡子树后,把它的根结点移动到根结点左孩子的右子树上,如果原本左             孩子它存在右子树,那么把该右子树移动到原来根结点的左子树上,最后让左孩子成             为新的根结点。

旋转情况:

总共有四种情况:

  1. LL,最小失衡子树的根结点平衡因子为+2,左子树的根结点平衡因子为+1,这种情况一个右旋即可。
  2. RR,最小失衡子树的根结点平衡因子为-2,右子树的根结点平衡因子为-1,这种情况一个左旋即可。
  3. LR,最小失衡子树的根结点平衡因子为+2,左子树的根结点平衡因子为-1,这种情况先把树的左子树进行左旋,再右旋即可。
  4. RL,最小失衡子树的根结点平衡因子为-2,左子树的根结点平衡因子为+1,这种情况先把树的右子树进行右旋,再左旋即可。

多路查找树(B树):

B树没了解很多就写写一些概念吧。

背景:

       对于一棵树而言,它一个结点可以有多个孩子,但是它本身只存储一个元素;而二叉树限制更多,它一个结点最多只有两个孩子。在元素特别多的情况下(数据特别多),树要么就会深度特别大,要么就会度特别大,甚至可能两者都特别大。这对于存储在内存的树来说,就会存在以下问题:1.度特别大就会降低操作速度;2.需不断的从外存设备进行访问。所以为了降低对外存设备的访问次数同时提高效率且降低树的度,就要打破原来一个结点只存储一个元素的限制,多路查找树就此诞生(在文件系统常用)。

定义:

        多路查找树,其每一个结点的孩子可以多于两个而且每一个结点处可以存储多个元素。

2-3树:

定义:

        2-3树是一种多路查找树,它的结点可以分为两种:2结点和3结点。2结点具有一个元素和两个孩子(或没有孩子),同时与二叉排序树类似,左子树包含的元素都小于该结点,右子树包含的元素都大于该结点。不同的是,它不能和二叉排序树一样只有一个孩子。3结点具有一大一小两个元素和三个孩子(或没有孩子)。同样一个3结点要么没有孩子,要么具有三个孩子,要是有孩子的话,左子树的元素都小于小元素,中子树的元素都大于小元素而且都小于大元素,右子树的元素都大于大元素。

操作概述:

  • 插入:与二叉排序树相同的是,2-3树的插入操作一定是发生在叶结点的,但不同的是2-3树 在插入一个元素时有可能会对树的其他结构产生连锁反应。具体分为:1.对于一棵空树,插入一个2结点即可;2.插入一个结点到2结点的叶结点上,则将其升级为3结点即可,其中还要比较插入结点和叶结点的大小,来判断谁左谁右;3.往一个3结点中插入一个结点,需将其拆分,而且还要将3结点的两个元素或者插入结点的元素选择一个向上移动,具体是移动多少,如果该3结点的父结点是一个2结点那么考虑让他升级为3结点,如果它的父结点也是一个3结点,那么再向上考虑。
  • 删除:对于2-3树的删除可以分为:1.需删除的元素位于一个3结点上,那么直接删除并且把3  结点改为2结点即可;2.需删除的元素是一个2结点,如果直接删除那么不符合2-3树的定义(要么2个或者3个孩子,要么没有孩子),那么对于这种情况:如果该2结点的父结点也是一个2结点而且它的兄弟结点为一个3结点,那么对其进行左旋;如果它的兄弟结点是一个2结点的话,那么需要对更上面甚至整棵树进行拆分,使其满足定义;如果该2结点的父结点是一个3结点,同样要进行拆分;3.需删除的元素位于一个分支结点上,那考虑使用中序遍历得到的该元素的直接前驱或者后继元素让它进行补位。

B树:

定义:

        B树是一种平衡的多路查找树,在描述一颗B树时需要指定它的阶数m,阶数m表示了一个结点最多有多少个孩子结点(即孩子结点数目最多的就是B树的阶),2-3树是一棵3阶B树,2-3-4树是一棵4阶B树,两者都是特殊的B树。

特点:

一棵m阶的B树具有以下特点:

  • 所有的叶结点都是位于同一层次
  • 根结点至少要有两棵子树,除非该根结点是一个叶结点
  • 对于除根结点之外的所有分支结点,都至少拥有(m/2) 棵子树,至多有m棵子树;同时至少拥有(m/2)-1 个元素,至多有m-1个元素。
  • 每个结点的元素有顺序,与二叉排序树一样,左边的子树小于该元素,右边的子树大于该元素,用下图简略解释一下。

结点结构:

用这个图简略的说说吧,图中红色区域的数字代表这个结点拥有的元素数目;蓝色区域代表的是指向子树的指针;绿色区域代表的是存储的元素。结点的元素和子树的元素具有以下关系:元素左边的指针指向的子树的元素都小于该元素,右边的指针指向的子树的元素都大于该元素。

简述TreeSet类和TreeMap类:

TreeSet类:

       TreeSet 是 Java ​​​​​​​集合框架中的一种有序集合,它继承了AbstractSet抽象类,实现了NavigableSet<E>,Cloneable,Serializable接口。TreeSet是基于TreeMap实现的,而TreeSet底层是基于红黑树实现的,同时TreeSet它不存储重复的元素,保证了元素的唯一性。TreeSet中的元素可以按照自然排序方式或者按照指定的排序方式进行排序。而且TreeSet是非同步集合,因此不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步。API链接:TreeSet (Java Platform SE 8 ) (oracle.com)

TreeMap类:

          TreeMap是一个有序的key-value集合,它继承了AbstractMap抽象类,实现了NavigableMap<K,V>,Cloneable,Serializable接口。TreeMap类底层是基于红黑树实现的,同时TreeMap的key是不重复的,除非重写比较器。TreeMap的key也可以按照自然排序方式或者按照指定的排序方式进行排序。而且TreeMap是非同步集合,因此不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步。API链接:TreeMap (Java Platform SE 8 ) (oracle.com)

                   

           

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mo@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值