数据结构中常见的各种树原理详解(学习笔记)

01.回顾

1.树

树(tree)是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合。它是由n (n>0)个有限节点通过连接它们的边组成一个具有层次关系的集合。

02.二叉树

二叉树:树的每个节点最多只能有两个子节点。

3.堆

堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵完全二叉树的数组对象。
1.它是完全二叉树

2.它通常用数组实现。

3.每个节点都大于等于它的两个子节点
这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个子结点的顺序并没有做规定,跟我们学习的二叉查找树是有区别的。

1.它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。
在这里插入图片描述
2.它通常用数组实现
在这里插入图片描述
如果一个结点的位置k_则它的父结点的位置为[(k2].而它的两个子结点的位置则分别为2k和2k+1。这样,在不使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:戏a[k]向上一层,就令k等于k/2,向下一层就令k等于2k或2k+1。
3.每个节点都大于等于它的两个子节点

堆的插入
在最后的面插入,然后再进行上浮操作(让该数组为堆)

堆的删除
将最后一个元素替换要删除的元素。然后在将最后一个元素删除。最后再开始上浮操作,让该数组为堆。

堆排序

将数组复制,并将其搞成堆,堆中第一个元素是最大的,将第一个元素和最后一个元素交换。

然后将交换后的数组进行下沉(k/2以上的元素开始),使其成为堆(最后一个元素不参与下沉),这是第一个元素是第二大的元素,将其于倒数第二个元素交换。重复上述操作。

优先队列

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在某些情况下,我们可能需要找出队列中的最大值或者最小值,例如使用一个队列保存计算机的任务,一般情况下计算机的任务都是有优先级的,我们需要在这些计算机的任务中找出优先级最高的任务先执行,执行完毕后就需要把这个任务从队列中移除。普通的队列要完成这样的功能,需要每次遍历队列中的所有元素,比较并找出最大值,效率不是很高,这个时候,我们就可以使用一种特殊的队列来完成这种需求,优先队列。

底层采用堆的思想

在这里插入图片描述
优先队列按照其作用不同,可以分为以下两种:
最大优先队列
可以获取并删除队列中最大的值

最小优先队列:
可以获取并删除队列中最小的值

最小堆:
1.最小的元素放在数组的索引1处。

2.每个结点的数据总是小于等于它的两个子结点的数据。

索引优先队列

之前学的优先队列,无法获取任意的元素。

索引优先队列给数据关联了键。

在之前实现的最大优先队列和最小优先队列,他们可以分别快速访问到队列中最大元素和最小元素,但是他们有一个缺点,就是没有办法通过索引访问已存在于优先队列中的对象,并更新它们。为了实现这个目的,在优先队列的基础上,学习一种新的数据结构,索引优先队列。接下来我们以最小索引优先队列举列。

步骤一:

存储数据时,给每一个数据元素关联一个整数,例如insert(int k,T t),我们可以看做k是t关联的整数,那么我们的实现需要通过k这个值,快速获取到队列中t这个元素,此时有个k这个值需要具有唯一性。

最直观的想法就是我们可以用一个T[ items数组来保存数据元素,在insert(int k,T t)完成插入时,可以把k看做是items数组的索引,把t元素放到items数组的索引k处,这样我们再根据k获取元素t时就很方便了,直接就可以拿到items[k]即可。

在这里插入图片描述
步骤二:

完成后的结果,虽然我们给每个元素关联了一个整数,并且可以使用这个整数快速的获取到该元素,但是,items数组中的元素顺序是随机的,并不是堆有序的,所以,为了完成这个需求,我们可以增加一个数组int]pq,来保存每个元素在items数组中的索引, pq数组需要堆有序,也就是说,pq[1]对应的数据元素items[pq[1]要小于等于pq[2]和pq[3]对应的数据元素items[pq[2]和items[pq[3]]。
在这里插入图片描述
步骤三:
通过步骤二的分析,我们可以发现,其实我们通过上浮和下沉做堆调整的时候,其实调整的是pg数组。如果需要对items中的元素进行修改,比如让items[0]=“H”,那么很显然,我们需要对pq中的数据做堆调整,而且是调整pq19]中元素的位置。但现在就会遇到一个问题,我们修改的是items数组中0索引处的值,如何才能快速的知道需要挑中pq[9]中元素的位置呢?
最直观的想法就是遍历pq数组,拿出每一个元素和O做比较,如果当前元素是0,那么调整该索引处的元素即可,但是效率很低。

在这里插入图片描述

3.二叉搜索树(二叉查找树)

如果我们给二叉树加一个额外的条件,就可以得到一种被称作二叉搜索树(binary search tree)的特殊二叉树。

二叉搜索树要求:

若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

它的左、右子树也分别为二叉排序树。

二叉搜索树查找节点:
查找某个节点,我们必须从根节点开始查找。
1、查找值比当前节点值大,则搜索右子树;
2、查找值等于当前节点值,停止搜索(终止条件)
3、查找值小于当前节点值,则搜索左子树;

二叉搜索树-插入节点:
要插入节点,必须先找到插入的位置。与查找操作相似,由于二叉搜索树的特殊性。
待插入的节点也需要从根节点开始进行比较,小于根节点则与根节点左子树比较。
反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置。

二叉搜索树-遍历节点:

遍历树是根据一种特定的顺序访问树的每一个节点。比较常用的有前序遍历,中序遍历和后序遍历。

而二叉搜索树最常用的是中序遍历(因为结果是有序的)

1、中序遍历:左子树——》根节点——》右子树

2、的序遍历:根节点——》左子树——》右子树

3、后李遍历:左子树——》右子树——》根节点

二叉搜索树-查找最大值和最小值
要找最小值,先找根的左节点,然后一直找这个左节点的左节点,直到找到没有左节点的节点,那么这个节点就是最小值。

同理要找最大值,一直找根节点的右节点,直到没有右节点,则就是最大值。
在这里插入图片描述
二叉搜索树-删除节点:
删除节点是二叉搜索树中最复杂的操作,删除的节点有三种情况,前两种比较简单,但是第三种却很复杂。

1、该节点是叶节点(没有子节点)

2、该节点有一个子节点

3、该节点有两个子节点
删除没有子节点的节点

要删除叶节点,只需要改变该节点的父节点引用该节点的值,即将其引用改为null即可。

在这里插入图片描述
删除有一个子节点的节点
删除有一个子节点的节点,我们只需要将其父节点原本指向该节点的引用,改为指向该节点的子节点即可。
在这里插入图片描述
删除有两个子节点的节点
当删除的节点存在两个子节点,那么删除之后,两个子节点的位置我们就没办法处理了。
既然处理不了,我们就想到一种办法,用另一个节点来代替被删除的节点,那么用哪一个节点来代替呢?(大于该节点的最小节点
我们知道二叉搜索树中的节点是按照关键字来进行排列的,某个节点的关键字次高节点是它的中序遍历后继节点。用后继节点来代替删除的节点。显然该二叉搜索树还是有序的。

在这里插入图片描述

在这里插入图片描述
二叉搜索树具有,二分查找算法高性能和链表的灵活性的优点。

普通二叉树的缺点
在这里插入图片描述
怎么解决二叉搜索树退化成线性链表的问题?
如果插入元素时,树可以自动调整两边平衡,会保持不错的查找性能。

因此二叉平衡树来了。

4.二叉平衡树(ALV)

什么是二叉平衡树呢?
1.具有二叉查找树的全部特性。

2.每个节点的左子树和右子树的高度差至多为1。
在这里插入图片描述
平衡树基于这种特点就可以保证不会出现大量节点偏向于一边的情况了!(插入或者删除时,会发生左旋、右旋操作,使这棵树再次左右保持一定的平衡)

为什么有了平衡树还需要红黑树呢?
虽然平衡树解决了二叉查找树退化为近似链表的缺点,能够把查找时间控制在O(logn),不过却不是最佳的。

因为平衡树要求每个节点的左子树和右子树的高度差至多等于1,这个要求实在是太严了,导致每次进行插入删除节点的时候几乎都会破坏平衡树的第二个规则,进而我们都需要通过左旋和右旋来进行调整,使之再次成为一颗符合要求的平衡树。

显然,如果在那种插入、删除很频繁的场景中,平衡树需要频繁着进行调整,这会使平衡树的性能大打折扣,为了解决这个问题,于是有了红黑树!

红黑树
红黑树就是一种平衡树,它可以保证二叉树基本符合矮矮胖胖的结构。我们回忆一下AVL树,它在插入和删除节点时,总要保证任意节点左右子树的高度差不超过1。正是因为有这样的限制,插入一个节点和删除一个节点都有可能调整多个节点的不平衡状态。频繁的左旋转和右旋转操作一定会影响整个AVL树的性能,除非是平衡与不平衡变化很少的情况下,否则AVL树所带来的搜索性能提升不足以弥补平衡树所带来的性能损耗。

那有没有绝对平衡的一种树呢?没有高度差也不会有平衡因子,没有平衡因子就不会调整旋转操作。2-3树正是一种绝对平衡的树,任意节点到它所有的叶子节点的深度都是相等的。

2-3树的数字代表一个节点有2到3个子树。它也满足二分搜索树的基本性质,但它不属于二分搜索树。

02. 2-3查找树

2-3树是二叉查找树的变种,树中的2和3代表两种节点,以下表示为2-节点和3-节点。

2-节点,含有一个元素和两个子树(左右子树),左子树所有元素的值均小于它父节点,右子树所有元素的值均大于它父节点;

3-节点,还有两个元素和三个子树(左中右子树),左子树所有元素的值均小于它父节点,中子树所有元素的值都位于父节点两个元素之间,右子树所有元素的值均大于它父节点;

子树也是空树、2-节点或者3-节点;

没有元素相等的节点。在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
2-3树的查找类似二分搜索树的查找,根据元素的大小来决定查找的方向。要判断一个元素是否存在,我们先将待查找元素和根节点比较,如果它和其中任意一个相等,那查找命中;否则根据比较的结果来选择查找的方向。
在这里插入图片描述
2-3树插入元素
插入元素首先进行查找命中,若查找命中则不予插入此元素,如果需要支持重复的元素则将这个元素对象添加一个属性count。若查找未命中,则在叶子节点中插入这个元素。

空树的插入很简单,创建一个节点即可。如果不是空树,插入的情况分为5种:
1.向2-节点中插入元素;

2.向一颗只含有一个3-节点的树中插入元素;

3.向一个父节点为2-节点的3-节点中插入元素;

4.向一个父节点为3-节点的3-节点中插入元素。

5.分解根节点
向2-节点中插入元素
往2-3树中插X元素和往二叉查找树中插入元素一样,首先要进行查找,然后将节点挂到未找到的节点上。2-3树之所以能够保证在最差的情况下的效率的原因在于其插入之后仍然能够保持平衡状态。如果查找后未找到的节点是一个2-结点,那么很容易,**我们只需要将新的元素放到这个2-结点里面使其变成一个3-结点即可。**但是如果查找的节点结束于一个3-结点,那么可能有点麻烦。
在这里插入图片描述
向一颗只含有一个3-节点的树中插入元素
假设2-3树只包含一个3-结点,这个结点有两个键,没有空间来插入第三个键了,最自然的方式是我们假设这个结点能存放三个元素暂时使其变成一个4-结点,同时他包含四条链接。然后,我们将这个4-结点的中间元素提升,左边的键作为其左子结点,右边的键作为其右子结点。插入完成,变为平衡2-3查找树,树的高度从0变为1。
在这里插入图片描述
向一个父节点为2-节点的3-节点中插入元素
和上面的情况一样一样,我们也可以将新的元素插入到3-结点中,使其成为一个临时的4-结点,然后,将该结点中的中间元素提升到父结点即2-结点中,使其父结点成为一个3-结点,然后将左右结点分别挂在这个3-结点的恰当位置。
在这里插入图片描述

在这里插入图片描述

向一个父节点为3-节点的3-节点中插入元素
当我们插入的结点是3-结点的时候,我们将该结点拆分,中间元素提升至父结点,但是此时父结点是一个3-结点,插入之后,父结点变成了4-结点,然后继续将中间元素提升至其父结点,直至遇到一个父结点是2-结点,然后将其变为3-结点,不需要继续进行拆分。
在这里插入图片描述
在这里插入图片描述
分解根节点
当插入结点到根结点的路径上全部是3-结点的时候,最终我们的根结点会编程一个临时的4-结点,此时,就需要将根结点拆分为两个2-结点,树的高度加1。

在这里插入图片描述
在这里插入图片描述

2-3树的性质

通过对2-3树插入操作的分析,我们发现在插入的时候,2-3树需要做一些局部的变换来保持2-3树的平衡。—棵完全平衡的2-3树具有以下性质:

1.任意空链接到根结点的路径长度都是相等的。

2.4-结点变换为3-结点时,树的高度不会发生变化,只有当根结点是临时的4-结点,分解根结点时,树高+1。

3.2-3树与普通二叉查找树最大的区别在于,普通的二叉查找树是自顶向下生长,而2-3树是自底向上生长

2-3查找树实现起来比较复杂,在某些情况插入后的平衡操作可能会使得效率降低。但是2-3查找树作为一种比较重要的概念和思路对于我们后面要讲到的红黑树、B树和B+树非常重要。

03.红黑树

前面介绍了2-3树,可以看到2-3树能保证在插入元素之后,树依然保持平衡状态,它的最坏情况下所有子结点都是2-结点,树的高度为IgN,相比于我们普通的二叉查找树,最坏情况下树的高度为N,确实保证了最坏情况下的时间复杂度,但是2-3树实现起来过于复杂,所以我们介绍一种2-3树思想的简单实现∶红黑树。

红黑树主要是对2-3树进行编码,红黑树背后的基本思想是用标准的二叉查找树(完全由2-结点构成)和一些额外的信息(替换3-结点)来表示2-3树。

我们将树中的链接分为两种类型︰

红链接∶将两个2-结点连接起来构成一个3-结点;

黑链接∶则是2-3树中的普通链接。

确切的说,我们将3-结点表示为由由一条左斜的红色链接(两个2-结点其中之一是另一个的左子结点)相连的两个2-结点。这种表示法t一个优点是,我们无需修改就可以直接使用标准的二叉查找树的get方法。

红黑树的定义:
红黑树是含有红黑链接并满足下列条件的二叉查找树:

1.红链接均为左链接;

2.没有任何一个结点同时和两条红链接相连;

3.该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同;
下面是红黑树与2-3树对应的关系。

红黑树的节点类设计
因为每个结点都只会有一条指向自己的链接(从它的父结点指向它),我们可以在之前的Node结点中添加一个布尔类型的变量color来表示链接的颜色。如果指向它的链接是红色的,那么该变量的值为true,如果链接是黑色的,那么该变量的值为false。
在这里插入图片描述

红黑树的性质
在这里插入图片描述
红黑树并不是一个完美平衡二叉查找树,从图上可以看到,根结点P的左子树显然比右子树高。

但左子树和右子树的黑结点的层数是相等的,也即任章一个结点到到每个叶子结点的路径都包含数量相同的黑结点(性质5)。所以我们叫红黑树这种平衡为黑色完美平衡。

红黑树的性质讲完了,只要这棵树满足以上性质,这棵树就是趋近与平衡状态的。

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

1.变色:结点的颜色由红变黑或由黑变红。

⒉.左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结论的左子结点变为旋转结点的右子结点,左子结点保持不变。

3.右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变
左旋:
在这里插入图片描述

右旋:
在这里插入图片描述
红黑树的查找:
在这里插入图片描述
跟二叉查找树一样的原理。

红黑树插入

插入操作包括两部分工作:

1.查找插入的位置

2.插入后自平衡

注意:插入节点,必须为红色,理由很简单,红色在父节点(如果存在)为黑色节点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作.但如果插入结点是黑色,那么插入位置所在的子树黑色结点总是多1,必须做自平衡。

在开始每个情景的讲解前,我们还是先来约定下:
在这里插入图片描述

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

情景2:插入结点的Key已存在
处理:更新当前节点的值,为插入节点的值
在这里插入图片描述
情景3:插入结点的父结点为黑结点
由于插入的结点是红色的,当插入结点的黑色时,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。
在这里插入图片描述
插入情景4.1∶叔叔结点存在并且为红结点

依据红黑树性质4可知,红色节点不能相连=>祖父结点肯定为黑结点;
因为不可以同时存在两个相连的红结点。那么此时该插入子树的红黑层数的情况是:黑红红。显然最简单的处理方式是把其改为:红黑红处理:
1.将P和U节点改为黑色

2.将PP改为红色

3.将PP设置为当前节点,进行后续处理
在这里插入图片描述
可以看到,我们把PP结点设为红色了,如果PP的父结点是黑色,那么无需再做任何处理;

但如果PP的父结点是红色,则违反红黑树性质了。所以需要将PP设置为当前节点,继续做插入操作自平衡处理,直到平衡为止。

插入情景4.2∶叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的左子结点
注意:单纯从插入前来看,叔叔节点非红即空(NIL节点),否则的话破坏了红黑树性质5,此路径会比其它路径多一个黑色节点。
在这里插入图片描述
处理:
1.变颜色:将P设置为黑色,将PP设置为红色

2.对PP节点进行右旋

在这里插入图片描述
叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点
处理:
1.对P进行左旋

2.将P设置为当前节点,得到LL红色情况

3.按照LL红色情况处理(1.变颜色⒉.右旋PP)
在这里插入图片描述
插入情景4.3:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点

该情景对应情景4.2,只是方向反转,直接看图。
在这里插入图片描述
插入情景4.3.1:新插入节点,为其父节点的右子节点(RR红色情况)
处理:
1.变颜色:将P设置为黑色,将PP设置为红色
2.对PP节点进行左旋
在这里插入图片描述
插入情景4.3.2:新插入节点,为其父节点的左子节点(RL红色情况)

在这里插入图片描述
处理:
1.对P进行右旋
2.将P设置为当前节点,得到RR红色情况3.按照RR红色情况处理(1.变颜色⒉.左旋PP)
在这里插入图片描述
综合:
在这里插入图片描述

04.红黑树的删除

05.Java手写红黑树

06.B树

前面我们已经学习了二叉查找树、2-3树以及它的实现红黑树。2-3树中,一个结点做多能有两个key,它的实现红黑树中使用对链接染色的方式去表达这两个key。接下来我们学习另外一种树型结构B树,这种数据结构中,一个结点允许多于两个key的存在。

B树是一种树状数据结构,它能够存储数据、对其进行排序并允许以O(logn)的时间复杂度进行查找、顺序读取、插入和删除等操作。

B树中允许一个结点中包含多个key,可以是3个、4个、5个甚至更多,并不确定,需要看具体的实现。现在我们选择一个参数M ,来构造一个B树,我们可以把它称作是M阶的B树,那么该树会具有如平特点:

·每个结点最多有M-1个key,并且以升序排列;

·每个结点最多能有M个子结点;

·根结点至少有两个子结点;
在这里插入图片描述
在实际应用中B树的阶数一般都比较大(通常大于100 ),所以,即使存储大量的数据,B树的高度仍然比较小,这样在某些应用场景下,就可以体现出它的优势。

B树的插入

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

B树在磁盘文件中的应用

在我们的程序中,不可避免的需要通过I0操作文件,而我们的文件是存储在磁盘上的。计算机操作磁盘上的文件是通过文件系统进行操作的,在文件系统中就使用到了B树这种数据结构。

磁盘:

磁盘能够保存大量的数据,从GB一直到TB级,但是他的读取速度比较慢,因为涉及到机器操作,读取速度为毫秒级。
在这里插入图片描述
磁盘由盘片构成,每个盘片有两面,又称为盘面。盘片中央有一个可以旋转的主轴,他使得盘片以固定的旋转速率旋转,通常是5400rpm或者是7200rpm,一个磁盘中包含了多个这样的盘片并封装在一个密封的容器内。盘片的每个表面是由一组称为磁道同心圆组成的,每个磁道被划分为了一组扇区,每个扇区包含相等数量的数据位,通常是512个子节,扇区之间由一些间隙隔开,这些间隙中不存储数据。
在这里插入图片描述
磁盘用磁头来读写存储在盘片表面的位,而磁头连接到一个移动臂上,移动臂沿着盘片半径前后移动,可以将磁头定位到任何磁道上,这称之为寻道操作。一旦定位到磁道后,盘片转动,磁道上的每个位经过磁头时,读写磁头就可以感知到该位的值,也可以修改值。对磁盘的访问时间分为寻道时间,旋转时间,以及传送时间。

由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,因此为了提高效率,要尽量减少磁盘/O,减少读写操作。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理︰当一个数据被用到时,其附近的数据也通常会马上被使用。由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此预读可以提高/O效率。

页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(1024个字节或其整数倍),预读的长度一般为页的整倍数。主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。

文件系统的设计者利用了磁盘预读原理,将一个结点的大小设为等于一个页(1024个字节或其整数倍),这样每个结点只需要一次O就可以完全载入。那么3层的B树可以容纳十02410241024差不多10亿个数据,如果换成二叉查找树,则需要30层!假定操作系统一次读取一个节点,并且根节点保留在内存中,那么B树在10亿个数据中查找目标值,只需要小于3次硬盘读取就可以找到目标值,但红黑树需要小于30次,因此B树大大提高了IO的操作效率。

07.B+树

B+树是对B树的一种变形树,它与B树的差异在于:
1.非叶结点仅具有索引作用,也就是说,非叶子节点只存储key,不存储value ;

2树的所有叶结点构成一个有序链表,可以按照key排序的次序遍历全部数据。
在这里插入图片描述
在这里插入图片描述
B+树的优点在于:
1.由于B+树在非叶子结点上不包含真正的数据,只当做索引使用,因此在内存相同的情况下,能够存放更多的key,
2.B+树的叶子结点都是相连的,因此对整棵树的遍历只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并盈相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。

B树的优点在于∶
由于B树的每一个节点都包含key和value,因此我们根据key查找value时,只需要找到key所在的位置,就能找到value,但B+树只有叶子结点存储数据,索引每一次查找,都必须一次一次,一直找到树的最大深度处,也就是叶子结点的深度,才能找到value。

B+树在数据库中的应用

在数据库的操作中,查询操作可以说是最频繁的一种操作,因此在设计数据库时,必须要考虑到查询的效率问题,在很多数据库中,都是用到了B+树来提高查询的效桑;

在操作数据库时,我们为了提高查询效率,可以基于某张表的某个字段建立索引,就可以提高查询效率,那其实这个索引就是B+树这种数据结构实现的。
在这里插入图片描述
执行select * from user where id=18 )需要从第一条数据开始,一直查询到第6条,发现id=18,此时才能查询出目标结果,共需要比较6次;

建立主键索引:

在这里插入图片描述
区间查询

执行select * from user where id>=12 and id<=18 ,如果有了索引,由于B+树的叶子结点形成了一个有序链表,所以我们只需要找到id为12的叶子结点,按照遍历链表的方式顺序往后查即可,效率非常高。

08.并查集

并查集是—种树型的数据结构,并查集可以高效地进行如下操作∶

主要用于解决一些元素分组的问题。它管理一系列不相交的集合,并支持两种操作:

合并(Union):把两个不相交的集合合并为一个集合。

查询(Find):查询两个元素是否在同一个集合中。

查询元素p和元素q是否属于同一组

合并元素p和元素q所在的组
在这里插入图片描述

并查集结构

并查集也是一种树型结构,但这棵树跟我们之前讲的二叉树、红黑树、B树等都不一样,这种树的要求比较简单:

1.每个元素都唯一的对应一个结点;

2.每一组数据中的多个元素都在同一颗树中﹔

3.一个组中的数据对应的树和另外一个组中的数据对应的树之间没有任何联系;

4.元素在树中并没有子父级关系的硬性要求;
在这里插入图片描述
构造方法的实现

1.初始情况下,每个元素都在一个独立的分组中,所以,初始情况下,并查集中的数据默认分为N个组;

2.初始化数组eleAndGroup ;

3.把eleAndGroup数组的索引看做是每个结点存储的元素,把eleAndGroup数组每个索引处的值看做是该结点所在的分组,那么初始化情况下,i索引处存储的值就是i
在这里插入图片描述

在这里插入图片描述

并查集的优化

如果我们并查集存储的每一个整数表示的是一个大型计算机网络中的计算机,则我们就可以通过connected(int p.int g)来检测,该网络中的某两台计算机之间是否连通?

如果连通,则他们之间可以通信,如果不连通,则不能通信,此时我们又可以调用union(int p.intq)使得p和q之间连通,这样两台计算机之间就可以通信了。

一般像计算机这样网络型的数据,我们要求网络中的每两个数据之间都是相连通的,也就是说,我们需要调用很多次union方法,使得网络中所有数据相连,其实我们很容易可以得出,如果要让网络中的数据都相连,则我们至少要调用N-1次union方法才可以,但由于我们的union方法中使用for循环遍历了所有的元素,所以很明显,我们之前实现的合并算法的时间复杂度是O(N2),如果要解决大规模问题,它是不合适的,所以我们需要对算法进行优化。

为了提升union算法的性能,我们需要重新设计find方法和union方法的实现,此时我们先需要对我们的之前数据结构中的eleAndGourp数组的含义进行重新设定∶

1.我们仍然让eleAndGroup数组的索引作为某个结点的元素;

2.eleAndGroup[i]的值不再是当前结点所在的分组标识,而是该结点的父结点;
在这里插入图片描述
在这里插入图片描述

合并方法实现

1.找到p元素所在树的根结点

2.找到q元素所在树的根结点

3.如果p和q已经在同一个树中,则无需合并;

4.如果p和q不在同一个分组,则只需要将p元素所在树根结点

的父结点设置为q元素的根结点即可;

5.分组数量-1
加粗样式
我们优化后的算法union,如果要把并查集中所有的数据连通,仍然至少要调用N-1次union方法,但是,我们发现union方法中已经没有了for循环,所以union算法的时间复杂度由O(N^2)变为了O(N)。

但是这个算法仍然有问题,因为我们之前不仅修改了union算法,还修改了find算法。我们修改前的find算法的时间复杂度在任何情况下都为o(1),但修改后的find算法在最坏情况下是O(N):
路径压缩
在这里插入图片描述
只要我们保证每次合并,都能把小树合并到大树上,就能够压缩合并后新树的路径,这样就能提高find方法的效率。为了完成这个需求,我们需要另外一个数组来记录存储每个根结点对应的树中元素的个数,并且需要一些代码调整数组中的值。

并查集案例

某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府"畅通工程"的目标是使全省任何两城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
在我们的测试数据文件夹中有一个trffic_project.txt文件,它就是诚征道路统计表,下面是对数据的解释︰
在这里插入图片描述
总共有20个城市,目前已经修改好了7条道路,问还需要修建多少条道路,才能让这20个城市之间全部相通?
解题思路:
1.创建—个并查集UF_Tree_Weighted([20];
⒉.分别调用union(0,1),union(6,9),union(3,8),union(5,1),union(2,12),union(6,10),union(4,8),表示已经修建好的道路把对应的城市连接起来;

3.如果城市全部连接起来,那么并查集中剩余的分组数目为1,所有的城市都在一个树中,所以,只需要获取当前并查集中剩余的数目,减去1,就是还需要修建的道路数目;
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值