java树的整理

37 篇文章 4 订阅
4 篇文章 0 订阅

用树的原因:因为它通常结合了另外两种数据结构的有点:一种是数组,一种是链表,在树中查找数据项的速度和在有序数组中查找一样快,并且插入数据项和删除数据项的速度和链表一样。

路径:设想一下顺着链接点的边从一个节点走到另一个节点,所经过的节点顺序排列就称为“路径”。

根:树顶端的节点就称为根,一棵树只能有一个根,从根到任意节点有且只有一条路径。

父节点:每个节点上面的节点为此节点的父节点,根没有父节点。

子节点:每个节点下面的节点为此节点的子节点。

叶节点:没有子节点的的节点称为叶节点。

子树:每个节点都可以作为“子树”的根,一个节点的子树包含它所有的子孙。

关键字:每个存储对象中用来定位查询和其他操作的数据域。

层:从根节点到此节点有多少代,假设根是0层,它的子节点就是1层。

二叉树:如果树中每个节点最多只能有两个子节点,这样的树就是二叉树,它的两个子节点(可有可无)被称为左子节点和右子节点。

满二叉树:高度为h,由2^h-1个节点构成的二叉树称为满二叉树,即没有空着的。

完全二叉树:完全二叉树是由满二叉树而引出来的,若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数(即1~h-1层为一个满二叉树),第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

二叉搜索树

一个节点的左子节点的关键字值小于这个节点,右子节点的g关键字值大于或者等于这个父节点。

构建二叉查找树:最常用的方法是把子节点存在无关联的存储容器中,而通过每个节点中指向自己子节点的引用来连接。需要一个节点对象类和一个表示树本身的类,最后是操作树的类(自己实现即可)。

实现find方法:

实现insert方法:与查找相比,就是在遇到null时先插入一个节点再返回,如果有相同的值按照较大的节点处理。

遍历树:前序遍历,中序遍历,后序遍历,最常用的为中序遍历,前中后指遍历时的父节点相对子节点遍历的顺序。

中序遍历:

1.调用自甚来遍历节点的左子树。

2.访问这个几点。

3.调用自身来遍历节点的右子树。

层序遍历:利用队列的先进先出

查找最大和最小:

最小:先走到跟的左子节点处,然后再继续走左子节点,直到找到一个没有左子节点的节点就是最小值。

最大值:从根的右子节点开始找没有右节点的节点。

删除节点:一种比较复杂的操作,右三种情况要考虑。

1,该节点没有子节点 :只需要将该节点的父节点的对应字段值指向null即可。

2.有一个子节点 。把它的子节点连接到它的父节点上。

3 有两个子节点。先找到改点的后继节点,然后用后继节点替换改节点,在改变此节点父节点中的引用。

后继节点:比改节点的关键字值次高的节点就是它的后继节点。首先找到初始节点的的右节点,它的关键字值一定比初始值大,然后转到右节点的左节点那里(如果有的话)然后一路一直找左子节点一路找下去,直到找到最后一个左子节点,就是后继节点,如果初始节点的右节点没有左子节点,那么初始节点的右节点就是后继节点。

前驱:该节点左子树中最大的节点。

删除操作:

二叉搜索树有一个很麻烦的问题,就是如果插入的数据时随机的,则执行效果很好,但是如果插入的是有序数据或者是逆序数据,插入就变得很慢,因为插入的数值有序时,二叉树就是非平衡的了,而对于非平衡树,它的快速查找 ,插入和删除指定数据项的能力就消失了。

平衡二叉树:它或者是一颗空树,或者具有以下性质的二叉树:它的左子树和右子树的深度之差(平衡因子)的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树

红黑树

红黑树放弃了追求完全平衡,追求大致平衡(最长路径不超过最短路径的两倍),这也是与AVL树(高度平衡树)的区别,红黑树是一种自平衡的二叉查找树,是一种高效的查找树,红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作.比如 Java 中的 TreeMap。红黑树使用红黑二色进行“着色”,目的是利用颜色值作为二叉树的平衡对称性的检查,只要插入的节点“着色”满足红黑二色的规定,最短路径与最长路径不会相差的太远,即可保证任意节点到其每个叶子节点路径最长不会超过最短路径的2倍,红黑树的节点分布就能大体上达至均衡。原因如下:当某条路径最短时,这条路径必然都是由黑色节点构成。当某条路径长度最长时,这条路径必然是由红色和黑色节点相间构成(性质4限定了不能出现两个连续的红色节点)。而性质5又限定了从任一节点到其每个叶子节点的所有路径必须包含相同数量的黑色节点。此时,在路径最长的情况下,路径上红色节点数量 = 黑色节点数量。该路径长度为两倍黑色节点数量,也就是最短路径长度的2倍。

红黑树中的搜索方法和二叉搜索树一样。插入和删除跟二叉搜索树相比改动较大。插入时可分为:

自顶向下插入:沿着树向下查找插入点,在此进程中可能要对树的结构做一些改变。

自底向上插入: 它需要找到插入数据项的位置,然后沿着树向上改变树的结构,相比从上开始效率较低,这是因为必须顺着树扫描两次。

红黑规则:当插入或者删除一个节点时必须遵循的规则,如果遵循这些规则,树就是平衡的。

1.每一个节点不是红色就是黑色的。

2.根总是黑色的。

3.所有的叶节点的子节点(Null)都是黑色的,.

4.每个红色结点的两个子结点一定都是黑色.

5.从任一节点到其每个叶节点径都包含相同数目的黑色节点或者说从任意节点到叶节点路径上的黑色高度相同。黑高:从根到叶节点路径上的黑色节点数目称为黑色高度。

由性质5推出性质5.1:如果一个结点存在黑子结点,那么该结点肯定有两个子结点。

有了上面的几个性质作为限制,即可避免二叉查找树退化成单链表的情况。

前面讲到红黑树能自平衡,它靠的是什么?三种操作:左旋、右旋和变色。

  • 左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。如图3。
  • 右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。如图4。
  • 变色:结点的颜色由红变黑或由黑变红。

上面所说的旋转结点也即旋转的支点,图4和图5中的P结点。
我们先忽略颜色,可以看到旋转操作不会影响旋转结点的父结点,父结点以上的结构还是保持不变的。
左旋只影响旋转结点和其右子树的结构,把右子树的结点往左子树挪了。
右旋只影响旋转结点和其左子树的结构,把左子树的结点往右子树挪了。

所以旋转操作是局部的。另外可以看出旋转能保持红黑树平衡的一些端详了:当一边子树的结点少了,那么向另外一边子树“借”一些结点;当一边子树的结点多了,那么向另外一边子树“租”一些结点。

但要保持红黑树的性质,结点不能乱挪,还得靠变色了。怎么变?具体情景又不同变法,后面会具体讲到,现在只需要记住红黑树总是通过旋转和变色达到自平衡

红黑树插入操作:

红黑树的插入过程和二叉查找树插入过程基本类似,不同的地方在于,红黑树插入新节点后,需要进行调整,以满足红黑树的性质,性质1规定红黑树节点的颜色要么是红色要么是黑色,那么在插入新节点时,这个节点应该是红色还是黑色呢?答案是红色,原因也不难理解。如果插入的节点是黑色,那么这个节点所在路径比其他路径多出一个黑色节点,这个调整起来会比较麻烦(参考红黑树的删除操作,就知道为啥多一个或少一个黑色节点时,调整起来这么麻烦了)。如果插入的节点是红色,此时所有路径上的黑色节点数量不变,仅可能会出现两个连续的红色节点的情况。这种情况下,通过变色和旋转进行调整即可,比之前的简单多了。

所有插入情景如图所示:

另外,根据二叉树的性质,除了情景2,所有插入操作都是在叶子结点进行的。这点应该不难理解,因为查找插入位置时,我们就是在找子结点为空的父结点的。

插入情景1:红黑树为空树

最简单的一种情景,直接把插入结点作为根结点就行,但注意,根据红黑树性质2:根节点是黑色。还需要把插入结点设为黑色。

处理:把插入结点作为根结点,并把结点设置为黑色。

插入情景2:插入结点的Key已存在

插入结点的Key已存在,既然红黑树总保持平衡,在插入前红黑树已经是平衡的,那么把插入结点设置为将要替代结点的颜色,再把结点的值更新就完成插入。

处理:把I设为当前结点的颜色,更新当前结点的值为插入结点的值。

插入情景3:插入结点的父结点为黑结点

由于插入的结点是红色的,当插入结点的黑色时,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。

处理:直接插入

插入情景4:分为父亲是左子树和右子树两种大情况,每种大情况又分为三种小情况。

一 : 父节点是左子树,对于这种情况又可以分为以下3中小的情况考虑

二 : 父节点是右子树,对于这种情况也可以分为以下3中小的情况考虑

注意:如果每棵子树都能自平衡,那么整棵树最终总是平衡的。

红黑树删除

红黑树的删除操作也包括两部分工作:一查找目标结点;而删除后自平衡。查找目标结点显然可以复用查找操作,当不存在目标结点时,忽略本次操作;当存在目标结点时,删除后就得做自平衡处理了。删除了结点后我们还需要找结点来替代删除结点的位置,不然子树跟父辈结点断开了,除非删除结点刚好没子结点,那么就不需要替代。

二叉树删除结点找替代结点有3种情情景:

  • 情景1:若删除结点无子结点,直接删除
  • 情景2:若删除结点只有一个子结点,用子结点替换删除结点
  • 情景3:若删除结点有两个子结点,用后继结点(大于删除结点的最小结点)替换删除结点

删除结点被替代后,在不考虑结点的键值的情况下,对于树来说,可以认为删除的是替代结点!跟二叉搜索树一样Q为p的后继节点。

 

下面我们开始讨论修复操作(下面的叶子节点都是指非NULL的叶子节点):

A. 删除的是叶子节点且该叶子节点是红色的 ---> 无需修复,因为它不会破坏红黑树的5个特性

B. 删除的是叶子节点且该叶子节点是黑色的 ---> 很明显会破坏特性5,需要修复。

C. 删除的节点(为了便于叙述我们将其称为P)下面有一个子节点 S,对于这种情况我们通过 将P和S的值交换的方式,巧妙的将删除P变为删除S,S是叶子节点,这样C这种情况就会转 换为A, B这两种情况:

C1: P为黑色,S为红色 ---> 对应 A 这种情况

C2: P为黑色或红色,S为黑色 --- > 对应 B 这种情况

D. 删除的节点有两个子节点,对于这种情况,我们通过将P和它的后继节点N的值交换的方 式,将删除节点P转换为删除后继节点N,而后继节点只可能是以下两种情况:

D1: N是叶子节点 --- > 对应情况 A 或 B

D2: N有一个子节点 ---- > 对应情况 C

所以通过上面的分析我们发现,红黑树节点删除后的修复操作都可以转换为 A 或 B这两种情况,而A不需要修复,所以我们只需要研究B这种情况如何修复就行了。

下面我们讨论如何修复B中情况:

 

AVL 树

计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。具有如下特性:

  1. 具有二叉查找树的全部特性。
  2. 每个节点的左子树和右子树的高度差至多等于1。

特性2的这个要求实在是太严了,导致每次进行插入/删除节点的时候,几乎都会破坏平衡树的第二个规则,进而我们都需要通过左旋右旋来进行调整,使之再次成为一颗符合要求的平衡树。显然,如果在那种插入、删除很频繁的场景中,平衡树需要频繁着进行调整,这会使平衡树的性能大打折扣,为了解决这个问题,于是有了红黑树。

下面图一为AVL树,图二不是。

右旋操作跟红黑树的右旋操作一致。

左旋操作也跟红黑树的左旋操作一致。

在插入的过程中,会出现一下四种情况破坏AVL树的特性,我们可以采取如下相应的旋转。

1、左-左型:做右旋。

2、右-右型:做左旋转。

3、左-右型:先做左旋,后做右旋。

4、右-左型:先做右旋,再做左旋。

节点:

树类:

B

B-tree就是一颗多叉查找树,它广泛应用于数据库索引和文件系统中。首先我们介绍一下一颗 m 阶B-tree的特性,那么这个 m 阶是怎么定义的呢?这里我们以一个节点能拥有的最大子节点数来表示这颗树的阶数。举个例子,如果一个节点最多有 n 个key,那么这个节点最多就会有 n+1 个子节点,这棵树就叫做 n+1(m=n+1)阶树。一颗 m 阶B-tree包括以下5条特性:

  1. 每个节点最多有 m 个子节点
  2. 除根节点和叶子节点,其它每个节点至少有 ceil(m/2) (ceil表示向上取整的意思)个子节点
  3. 根节点至少有两个子节点,除非B树的根节点关键值不超过最大限制。最多节点数遵循B树定义即m个(m-1 个key)。
  4. 所有叶节点都在同一层上,一般都是用空指针表示,表示查找失败,也就是说到根节点的高度都一样。
  5. 除根节点外,其它节点都包含 n 个key,其中ceil(m/2)  -1 <= n <= m-1
  6. 出叶子节点外所有的节点中的关键词都是互不相等的。
  7. B树中某个节点中左子树所有的关键词都是小于这个节点的,右子树中所有的关键词都是大于这个节点的。这里就是和二叉排序树是相同的。

B树的查找:

这是一棵5阶的B树,(3,5)树,每个节点的分支数不得超过5,同时除根节点,一般节点所拥有的分支数也不得少于3;每个节点至多拥有4个关键码,除根节点外每个节点至少拥有2个关键码。

1. 查找75

   先对常驻内存的根节点进行一次顺序查找,正确命中75。

2.查找69

  先对常驻内存的根节点进行一次顺序查找,这次查找以失败终止于介乎53和75之间的这个引用。对63和69所在的这个节点进行一次顺序查找,成功找到69。

3.查找45

  先对常驻内存的根节点进行一次顺序查找,这次查找以失败终止于53左侧的引用。将19和36这个节点通过IO载入内存,并做一次顺序查找,这次查找以失败终止于36右侧的引用,......最终查找失败于41与49之间的引用。
 

B-tree的插入:就是一个节点至下而上的分裂过程。下面我们具体以一颗4阶树来展示B-tree的插入过程。

首先我们 插入 200,300,400,没有什么问题,直接插入就好。

| 200 | 300 | 400 |

现在我们接着插入500,这个时候我们发现有点问题,根据定义及特性1我们知道一颗4阶B-tree的每个节点最多只能有3个key,插入500后这个节点就有4个key了。

| 200 | 300 | 400 | 500 |

这个时候我们就需要分裂,将中间的key上移到父节点,左边的作为左节点,右边的作为右节点,如下图所示:

这个时候我们是不是就明白特性3了,如果B树的根节点的关键值超过最大限制,那么它发生了分裂,所以至少会有2个子节点。同样我们接着插入600,700,800,900插入过程如下图所示。

现在根节点也已经满了,如果我们继续插入910,920,会怎样呢?根节点就会继续分裂,树继续向上生长。看下图:

通过整个的插入过程我们也会发现,B-tree和二叉树的一个显著的区别就是,B-tree是从下往上生长,而二叉树是从上往下生长的。在树的高度方面如下:

如果n≥1,那么对于任意一棵包含n个关键字、高度为h、最小阶数 t ≥ 2的B树有:

叉查找树结构由于树的深度过大而造成磁盘I/O读写过于频繁,进而导致查询效率低下,这是因为磁盘读取数据是以盘块(block)为基本单位的。位于同一盘块中的所有数据都能被一次性全部读取出来。而磁盘IO代价主要花费在查找时间Ts上。因此我们应该尽量将相关信息存放在同一盘块,同一磁道中。或者至少放在同一柱面或相邻柱面上,以求在读/写信息时尽量减少磁头来回移动的次数,避免过多的查找时间Ts,所以如果减少树的高度,把节点数据尽量放在一个磁盘叶中以加快读取速度

B树的删除:前面我们介绍了B-Tree的一些基本概念,相关特性以及如何插入关键字(key),下面介绍B-Tree中关键字的删除操作。通过之前对二叉树的学习,我们掌握了删除一个节点(由于二叉树的每个节点只带有一个关键字信息,删除一个关键字就是删除这个节点,而B-Tree的每个节点都带有多个关键字信息,所以B-Tree中我们称作删除关键字而不称作删除节点)的基本规律即,要删除某个节点我们会先找到它的后继节点,由这个后继节点替它“去死”。在B-Tree中也是这样,要删除某个key,我们就要找到这个key的后继key,同样由这个后继key替它“去死”。当然在删除过程中我们有事也会破坏B树的特性,这时我们就需要进行一定的操作来使这棵树重新成为B树。

通过特性2我们可以知道我们找到的后继key一定是在一个叶子节点中,所以B-Tree的key的删除都可以转换为删除叶子节点中的key的操作。那么删除一个叶子节点中的key该如何操作呢?

可以分为以下2中大的情况来讨论:

1. 如果该叶子节点是“富有”(是指该节点即使删除了这个key,该节点拥有的key的数量依然大于等于[m/2] - 1)的,那么很简单直接删除这个key即可。比如我们有下面这样一颗5阶B-tree,我们要删除 80 这个key。

我们直接删除就行,因为 80 所在的这个节点是“富有”的。见下图:

又或者现在我们还想删除 上图的200 这个key,那么我们先找到后继key 230,将后继key的值赋给它,然后我们需要删除的就是后继key 230 这和上面的情况就一样了,看下图:

2. 如果该叶子节点是“不富有”的,那该怎么处理呢?这又可以分为2中情况讨论

A. 兄弟节点是“富有”的(我没钱,但我哥有钱 ?)这种情况我们只需要从父节点中要一个key,然后由兄弟节点补一个key到父节点。比如说我们现在还想将上图的 180 删除,由于 180 所在的节点是“不富有”的,但右兄弟节点是“富有”的,所以我们将父节点中的 230 要下来,然后兄弟节点在补一个 240 到父节点,看下图:

B. 要是我的兄弟节点也“不富有”该怎么办?这个时候只能看爹了。

B1: 父节点也“不富有”(就怕这样的情况,人穷穷一窝啊 ?)这个就比较难办了,首先我们还是从父节点要一个key(没办法先保命啊)然后以父节点为新的当前节点递归(就是让父亲去找他的兄弟和爹,即找你的叔叔和爷爷)直到 新的当前节点富有或 新的当前节点的兄弟富有或 新的当前节点的父亲富有或直到根节点(找到头了也没个“富有”的),这时候只能将树的高度减一。比如我们现在还想 删掉 50 ,会怎样呢?看下图:

B2: 父节点“富有”,这也比较好办,从父节点要一个下来,然后兄弟合并即可。比如我现在还想删除 140,看图:

父节点补个 170 然后和右边的兄弟合并即可。

B +树 

基于B树的一种变体,有着更好的查询性能。

一个m阶的B+树具有如下几个特征:

1.有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点,除根结点外,每个结点有[m/2 ]<= n <=m 个子女,。

2.所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。

3.所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。

颜色标记的表示性质3.

需要注意的是根节点的最大元素(这里是15),也就是等于B+树的最大元素。以后无论插入和删除多少元素,始终保持最大元素在根节点中。至于叶子节点,由于父节点的所有元素都出现在子节点,所以叶子节点包含所有元素。并且每一个元素都有指向下一个节点的指针,形成了一个有序链表。

需要补充的是,在数据库的聚集索引(Clustered Index)中,叶子节点直接包含卫星数据。在非聚集索引(NonClustered Index)中,叶子节点带有指向卫星数据的指针。

B树和B+树总结

B+树相对于B树有一些自己的优势,可以归结为下面几点。

  • 单一节点存储的元素更多,使得查询的IO次数更少(B+树更加矮胖),所以也就使得它更适合做为数据库MySQL的底层数据结构了。
  • 所有的查询都要查找到叶子节点,查询性能是稳定的,而B树,每个节点都可以查找到数据,所以不稳定。
  • 所有的叶子节点形成了一个有序链表,更加便于范围查找。
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值