当前位置: 首页 > news >正文

【数据结构(邓俊辉)学习笔记】二叉搜索树04——AVL树

文章目录

  • 1.重平衡
    • 1.1 AVL= BBST
    • 1.2 平衡因子
    • 1.3 适度平衡
    • 1.4 接口
    • 1.5 失衡 + 复衡
  • 2. 插入
    • 2.1 单旋
    • 2.2 双旋
    • 2.3 实现
  • 3. 删除
    • 3.1 单旋
    • 3.2 双旋
    • 3.3 实现
  • 4. (3 + 4)-重构
    • 4.1 "3+4"重构
    • 4.2 "3+4"实现
    • 4.3 rotateAt
    • 4.4 综合评价

1.重平衡

1.1 AVL= BBST

作为二叉搜索树这章最后一节,我们来介绍最为经典的一种平衡二叉搜索树,也就是AVL树。

回顾此前的几节,首先介绍的是二叉查找树BST,然而我们也看到,尽管同时兼顾高效的静态操作和动态操作的角度讲,BST相对此前简单的向量和列表已经具有某种优势和潜质,但是毕竟它不能保证这一点,其原因在于,它的高度无论是从平均情况还是最坏情况都不能保证做到足够的低,具体来说,也就是做到 logn 以下。

当然在BST中的确存在这么一种特殊的类型,也就是所谓的Complete Binary Tree 完全二叉树。它的高度可以达到严格的最小,也就是logn。然而,相对于整体的BST,这类BST的数量极少。而且我们如果需要将任何一棵树转化为一棵完全二叉树,所需要的成本也太高。也正因为此,我们建议是或许应该适当地放松所谓平衡地标准。也就是说,只需考察某一类在渐进意义下不超过O(log n)高度的树即可。而这样一类树也就是我们所说的平衡二叉搜索树Balanced Binary Search Tree ——BBST。

在这里插入图片描述

比如这节将要介绍的AVL树,就是在这种意义下的一种BBST。以AVL树为代表的这些BBST,首先并没有放弃渐进意义logn 的复杂度底线,同时正因为它已经适度地放松了平衡的标准,所以通过精巧地设计,它们都可以具有这样一种属性。

具体来说,对于任何一棵这样意义下的BBST,在其生命期内,即便在某次操作之后,它不再满足BBST的条件,也就说游离到BBST这个范畴之外,也可以通过之前介绍的等价变化,迅速地将其转化为一棵等价的BBST。也就是说,可以通过极小的代价,就使之重新归入BBST的范畴。

而这种极小的代价是多少呢?不出意料,依然是不超过logn ,令刚刚失衡的搜索树重新恢复为一棵BBST的过程,也称作重平衡rebalance。

而对于包括AVL树在内的各种BBST而言,其核心技巧,无非两条。第一,如何界定一种适度的平衡标准,其次,则是一整套重平衡的技巧和算法。

以下就以AVL树为例,具体地讲解如何完成这两项任务。

1.2 平衡因子

首先,给出在AVL意义下什么叫做适度的平衡,凭借什么来判断一棵树是否是在AVL意义下的适度平衡。
在这里插入图片描述

需要用到这样一个指标,实际上,对于二叉树中的任何一个节点V,都可以定义它的所谓平衡因子 balanced factor。具体来说,也就是它的左子树高度与右子树高度之差,那么根据AVL树发明者的定义,所谓AVL树就是其中所有节点的平衡因子都不超过1,也不小于-1。

比如,不难验证这样一棵BST(上图),其实就是一棵AVL树。当然,AVL树本身只考虑左右子树的高度,所以只要所有节点能够满足全局的单调性即可,并不需要关心,它们的具体数值是多少,所以这里不妨不再将关键码加亮显示,而把关注里更多集中于各个节点的平衡因子。

我们来校验下,

  1. 对于第一个叶节点1而言,它的左右子树都是空,高度均为-1,所以这个节点平衡因子自然就是-1减-1 零。
  2. 再来看第二个节2点,它的左子树高度为零,而右子树高度为-1,所以它的平衡因子应该为正1。
  3. 同样地,对于节点6来说,它的左子树高度为-1,而右子树高度为0,所以这个节点的平衡因子应该为-1。
  4. 类似地,对于节点11而言,它的左子树高度为1,而右子树高度为0,两项之差为正1。
  5. 最后,对于根节点 3 来说,它的左子树高度为1,而右子树高度为2,二者之差为-1。
    ~  
    因此,对于这样一棵BST来说,的确,平衡因子在处处都上下不超过正负1,因此,它的确是一棵名副其实的AVL树。

当然从这个例子可以看出,AVL树未必是完全二叉树,也就是说,它未必是理想平衡。那么反过来,如此定义的AVL树是否的确是适度平衡的呢?

1.3 适度平衡

可以证明AVL树的确是适度平衡的,也就是说,一棵规模为n的AVL树,其高度在渐进意义下是不超过logn的。

height( AVL ) = O(log n)

实际上,为了证明规模固定的AVL树,其高度不会超过某个上限。我们可以等价证明,在高度固定的情况下,一棵AVL树的节点也不至于太少。

n = Ω( 2 h e i g h t ( A V L ) 2 ^{height(AVL)} 2height(AVL)

具体来说,我们可以证明这样一个事实。
在这里插入图片描述
对于高度固定为h的AVL树,其中所包含的点数至少是与h呈Fibonacci数关系,为此需要借助递推式。具体来说,可以证明这样一个递推式

S(h) = 1 + S(h - 1) + S(h - 2)

也就是说,如果我们将高度为h的AVL树的规模下限定义为S(h)的话,那么S(h)与S(h - 1)以及S(h - 2)之间满足这样一个叠加关系。

为此我们来考察那棵高度为h,同时规模达到最小的AVL树(上图)。既然它的规模要达到最少,所以它的左子树和右子树的规模也应该尽可能少,那么在AVL树的定义下,可变化的余地充其量不过其中一棵子树比另一棵子树高一层。不失一般性,假设左子树比右子树高出一层,因为它的高度为h - 1,所以它的规模下限自然也就是S(h - 1),同理,作为高度为h - 2的右子树,它的规模下限自然也就是S(h- 2),当然,还不要忘了这里的树根节点,这也就是为什么,我们还要附加上一个单位1。

这个递归式是我们所有分析的核心,而以下只不过是一些简单的数学技巧而已。为此,我们不妨对它做一个等价变换,也就是在左右各加一个1。

S(h) + 1 = [ S(h - 1) + 1 ] + [ S(h - 2) + 1 ]

左侧添加了一个1,右侧这块也添加了一个1,以及此前原本已有的一个1。接下来,如果我们将S(h)+ 1 定义为一个新的函数T(h),就会发现这个递推式的右侧会变成T(h - 1)再加上T(h - 2),这种形式是Fibonacci数所特有的递推形式,所以我们可以断定。它应该是等于Fibonacci的某一项。那么具体是从h前后位移多少项呢?

S(h) + 1 = [ S(h - 1) + 1 ] + [ S(h - 2) + 1 ]
T(h) = T(h - 1) + T(h - 2) = fib(h + ?)

我们只需考察对应的边界情况即可。

h n T(h)
0 1 2 = fib(3)
1 2 3 = fib(4)

首先考察规模为1,高度为0的AVL树,此时的T(h)应该等于1 加 1也就是2,我们知道这个是Fibonacci树的第三项 fib(3)。再来考察高度为1的AVL树,其规模最小也不至低于2,也就是左子树为一个节点,右子树为空的AVL树。此时的T(h)应该等于 2 + 1,也就是3。我们知道这个是Fibonacci数的第四项 fib(4)。由此可见,这里的T(h)只不过是Fibonacci数向前位移了三位。

我们知道Fibonacci数大致是呈 ϕ h \phi ^h ϕh的指数形式增长,由此我们也得到了n关于高度h的一个下届n = Ω \Omega Ω( ϕ h \phi ^h ϕh),因此反过来等价地n的对数也就构成了h的一个上界 h = O(log n),而这一点正式BBST所谓适度平衡的要求,这就以为这我们的AVL树的确是适度平衡的。

好了,至此也就完成了第一项使命,也就是给出AVL意义下的适度平衡标准。那么接下来,在着手完成第二项使命,也就是给出具体的重平衡算法之前,特许应该首先以C++语言的形式明确给出AVL树各种操作接口的规范。

1.4 接口

接下来,首先就将AVL树关于适度平衡的标准以及它作为数据结构所应该提供的各种接口以C++语言的形式明确定义下来。
在这里插入图片描述

首先是什么叫做平衡,可以看到,所谓的理想平衡就是左右子树的高度完全相等。

而所谓的平衡因子呢?在这里也严格地按照AVL树的定义,取作左子树的高度与右子树的高度之差

那么AVL树所谓的适度平衡标准,也就可以转译为平衡因子最大不过1最小不过-1

我们也可以依然采用模板类的形式,由标准的BST派生出AVL类。因此,包括search在内的很多公用标准接口都可以直接沿用。而作为派生类,这里需要重写的无非就是涉及重平衡的动态操作,也就是插入以及删除。

那么在这两种动态操作之后AVL树的失衡现象具体是什么样的呢?究竟有多严重呢?在给出具体的重平衡算法之前,或许应该首先获得一些感性上的体会。

1.5 失衡 + 复衡

考察这样一个实例
在这里插入图片描述
首先请关注中间这棵BST,不难发现,它其实就是在开篇所举的那个AVL树实例,只不过在这里我们将数字的关键码统一替换为了字母。

  1. 接下来,假设需要加入M。

那么按照BST的常规算法,经过适当的搜索,可以确定应该将M作为K的右孩子接入到这棵树中,然而随后会发现:M的接入,虽然不致引起它的父亲K的失衡,却导致它的祖父N因此失衡,更糟糕的是它的曾祖父(也就是R)也会因为它的插入而导致失衡。而作为一个极端的例子,这里使得它的更高层祖先也会因为它的插入而失衡。

总而言之,在一棵AVL树中,插入一个节点之后,有可能会导致若干个祖先失衡。

当然你大可放心,除了祖先之外的其他节点是不可能失衡的

其背后原因在于,对于非新插入节点祖先的那些节点而言,无论是它们的高度,还是它们孩子的高度,都不会因为新节点的插入而有所变化,所以它们各自的平衡因子也都将维持原状,如果此前是平衡的,那么它们就不可能变成失衡。

  1. 再来看另一个方向的删除操作,假设在原先的这棵AVL树中,我们删除了某一个节点,比如Y。

那么类似地,我们也会发现:Y的删除会导致它此前的那些父节点R发生失衡。你会发现:除了R之外,Y此前的其余祖先,比如说G,并未失衡。这只是一种巧合或者是我们没有考虑到最坏的情况吗?我们说不是这样的。

因为对于删除操作来说,在摘除节点之后的瞬间,至多只有一个节点会失衡。 这背后是什么原因呢?

不妨假设,在某个节点被摘除之后,的确会引起它的某个甚至某些祖先发生失衡。可以证明,其实其中只有一个祖先会失衡。
~  
为此我们不妨考察其中高度最低的那个失衡祖先。我们会发现:这个祖先R尽管失衡了,它的高度却必然保持原样。这背后的原因在于,如果这个节点的失衡的确是因为它的某个后代被摘除了,那么这个后代在此前也必然属于它那个相对更短的分支,而它的高度则是由它相对更长的那些分支所决定的,因此这个节点的删除并不致于引起这个祖先高度的变化,而既然这个祖先的高度不致于变化,那么相对于更高的祖先而言,它们在计算平衡因子时,结果也应该与未删除节点之前是一样的,换而言之,它们必然是平衡的。

所以概括而言,如果在一棵AVL树中,删除某个节点之后,的确引起祖先的失衡,那么这种失衡的祖先充其量不过只有一个

没错,在某个节点删除之后的瞬间,至多只有一个节点失衡,而反过来,我们却刚刚看到,一个节点的插入,却有可能引起几乎所有的祖先同时失衡。

那么我们是否可以说:相对而言,AVL树的删除操作要比插入操作更为简单呢?实际情况恰恰相反。

如果我们将插入操作和删除操作比喻为孩子,那么插入操作是这样一种孩子:他有可能在某个时候会闯下一连串的祸,但这个孩子还至少是个好孩子,因为他能够痛改前非,我们很快就能看到,一旦他能够改正其中一个错误,那么其他的一连串错误也都会自然地烟消云散。而反过来删除操作呢?虽然不能称作是个坏孩子,但是他至少是一个不长记性,不吸取教训的孩子,我们很快就会看到,尽管这个孩子在每一次只会闯下一个祸,但是每当你帮助他改正了这个错误之后,他转眼就会忘掉这件事,并且很快就会在另一个位置犯下同一样的错误,而且这个孩子的记忆力糟糕之极,即便你有足够的耐心帮助他改正下一个错误,接下来转眼之间,他有可能在另一个位置再次犯下同样的错误。

因此相对而言,插入操作要更为简便一些,而删除操作要复杂不少,因此接下来,我们不妨从插入操作入手。

2. 插入

2.1 单旋

首先来考察插入操作的第一种情景
在这里插入图片描述
我们在某个节点(g)原本已经更高的分支(p)插入了一个新的节点,这个分支的高度继续上升一层,从而导致节点g的平衡因子从-1变成-2,突破了AVL树的底线。

请注意,新的节点可能插入在 T 2 T_2 T2下面也可能插入在 T 3 T_3 T3下面,我们在它们之间引入一条虚线,表示二者只能取其中之一。

而且我们假设g是所有因此而发生失衡的祖先中最深的那个,那么从g出发沿着这个新增长的分支,我们可以找到它的孩子节点,以及孙子节点,将它们分别命名为v、p和g,分别暗示是一个节点v以及它的父亲parent以及祖父grandparent。根据这样的命名方式,我们也不难理解,尽管一个节点的插入有可能会导致多个祖先的失衡,其中最低的那个也不会低于它的祖父辈

那么既然此处已经发生了失衡,又当如何令它重新恢复平衡呢?

实际上,我们能做的无非是上一节所介绍的等价变换,也就是zig或者是zag旋转。在此处,我们只需要做一次旋转,也就是所谓的单旋。通过上图理解整个调整过程:
从我们刚才失衡的局部出发,接下来围绕着失衡的节点g做一次逆时针的zag旋转,这样的旋转可以由接下来几步组合完成。

  1. 首先引入一个临时引用rc指向节点p,接下来,我们要令p的左子树 T 1 T_1 T1成为g的右子树,为此只需这样调整。
  2. 再接下来,要令g成为p的左孩子,因此需要做这样的调整。
  3. 再接下来,要将局部子树的根由g替换为p,也就是说要做这样的调整。
  4. 此时的临时引用也完成了历史使命,它可以退出了,而旋转操作也同时宣告完成,至此重平衡化已经完成。

为了更清楚地看到平衡化之后的效果,不妨对上中图稍事整理,如上右图,不难验证,局部的这棵子树的确已经恢复了平衡。然而好消息还不止于此。实际上,如果在此前g以上还有其他的祖先同时发生失衡,那么在这个局部重新恢复平衡之后,也会同时一揽子地重新获得平衡,你能看出这背后的原因吗?在此不妨暂停片刻,就这个问题做一思考。

在这里除了平衡因子以外,局部子树还有一个重要指标就是它的高度,那么它的高度在哪呢?请留意这里所设置的三条基准线。不难发现,在插入新节点之前,原先这个子树的高度应该是以中间这条水平线为基准,然而对照重新平衡后的这棵树,我们会发现它的高度又重新回到了这样一条基准线。这棵局部子树的高度能够复原,又意味着什么呢?我们说这个意义非常重大,这意味着它的所有祖先在计算平衡因子时所得的结果也将与插入新节点之前完全一样。换而言之,在局部子树高度复原之后,所有祖先也必然会统一地恢复平衡,而全树呢?也将因此恢复平衡。

请注意,对于这种情况,我们无非是做了一次zag旋转,这种旋转只涉及到局部的常数个节点,因此它所对应的时间消耗应该是O(1)的,这个结果也在好不过了。当然这种情况只是所有情况中的一种,其特点是刚才所定义的gpv这连续三代的节点在方向上是朝向一致的,比如这里它们同时向右,所以我们也相应地称为zag-zag。不难理解,对于对称的情况,也就是它们一致向左的情况,同样可以参照这个方法予以处理,那种情况我们也称作zig-zig。

那么如果它们朝向并不一致,而是呈所谓的之字形形式呢?

2.2 双旋

在这里插入图片描述
比如这就是祖孙三代呈现一种之字形形式的可能。具体来说,节点p是节点g的右孩子,而节点v却是节点p的左孩子,这样一种情况也称作zig-zag,当然还有对称的zag-zig,其方法和过程完全对称。

我们这里不妨依然以zig-zag为例,那么,请注意,这里我们所谓的g依然是所有失衡祖先中最低的那个,而且节点g的高度也不致于太低,它至少是新插入节点x的祖父,当然,x本身有可能就是v,在这种情况下,我们要进一轮共两次的等价变换,也称这种组合为双旋。通过下面步骤来看一下,这种情况下双旋的执行过程。

  1. 首先,要围绕着节点p做一次顺时针的zig,整个过程与刚才相仿,来重新温习一遍。
    在这里插入图片描述
    至此,zig旋转即告完成。
  2. 接下来,还需要围绕着节点g做一次逆时针的zag旋转,就单独这次旋转来说,与我们刚才的过程完全一样,来重温一遍。 在这里插入图片描述
    至此,zag旋转即告完成。

这样我们实际上已经完成了这一局部的重平衡化,为了能够更清楚地看到这一点,我们不妨同样地对这一结果稍事整理。

在这里插入图片描述
现在应该看的很清楚了,在此局部的这棵子树的确已经恢复了平衡。

那么同样地,g以上有可能之前也是失衡的那些祖先呢?我们说它们依然会一揽子地统一恢复平衡,其背后的原因,与刚才单旋的情况如出一辙。最后正如刚才已经指出的这两种情况以及它的对称情况完全覆盖了插入操作失衡调整的所有情况,因此,就算法而言,已经做了足够的分析和交代。

那么这样一组调整的算法如何具体兑现为代码呢?

2.3 实现

在这里我们给出AVL树插入算法,一种可能的实现
在这里插入图片描述

  1. 可以看到,开始的几步操作无非是常规的BST插入算法,也就是说,我们需要首先进行查找定位并且不妨假设这个节点的确还不存在,接下来我们需要创建一个新的节点并且将它接入到刚才的BST中。

接下来,才是AVL规则的相应处理

  1. 具体来说,我们将从新节点的父亲开始,不断地逐一枚举它的历代祖先,即便这个祖先还是平衡的,我们依然有必要考虑更新它的高度。
  2. 而一旦抵达一个不平衡的祖先,根据我们这里枚举各代祖先的次序,这个祖先必然是所有失衡祖先中的最低者,因此按照刚才所介绍的算法,需要在此局部作旋转调整,而此局部所涉及的三个顶点,无非是g、以及它更高的孩子tallerChild(g)也就是p,以及再更高的孙子tallerChild(tallerChild(g))也就是v。请注意,一旦发现了这样一个节点并且随即完成了局部的重平衡,就可以直接退出这样一个循环,并且直接退出整个算法。

应该知道这正是由我们刚才所指出的那个特性所保证的,也就是说,尽管在某个节点刚刚插入的瞬间,可能同时有多个祖先都是处于失衡的状态,但是一旦我们令其中最低者恢复平衡,那么所有的失衡祖先都必然会统一地恢复平衡。

3. 删除

再来考察AVL树节点删除算法

3.1 单旋

在这里插入图片描述
比如上左图就是在节点删除之后引起失衡的一种情况,如果我们同样地将失衡的那个祖先命名为g,那么它之所以在此时会失衡,是因为此前所摘除的那个节点恰好处于它原本就更短的那个分支,比如 T 3 T_3 T3底部,也就是说,它的平衡因子将由此前的+1变成现在的+2,从而违规。

请注意,这里子树 T 0 T_0 T0 T 1 T_1 T1的底部应该至少有一个节点,而 T 2 T_2 T2底部的这个节点有可能存在,也有可能不存在。

请注意,与插入的情况不同,在这里,失衡节点g有可能恰好就是刚刚被删除节点的父亲,然而无论如何,只要gpv这祖孙三代节点是朝一个方向排列的,比如,p是g的左孩子,v也是p的左孩子,那么我们就可以通过一次单旋来恢复局部的平衡。

具体来说,也就是围绕这个失衡节点g做一次顺时针的zig旋转,这个旋转操作的细节在此前插入算法中已经给出详细的步骤,所以这里不妨直接给出旋转调整之后的结果(上右图)。可以看到,局部的子树树根由原先的g替换成了p,而节点v和g则分别成为了节点p的左右孩子。

不难验证,经过这样一个调整之后,在此局部,的确恢复了平衡,那么故事就此终结了吗?我们说有时候是,有时候不是。这里的关键在于 T 2 T_2 T2这棵子树底层节点是否存在

如果它存在,那么我们会发现:经过这样调整后,子树的新高度与此前原树高度是一样的,因此,与插入算法同理,在这种情况下,我们可以保证:在这棵子树上的每一个祖先它们的高度及平衡因子都会继续保持原状,因此不会发生新的失衡现象。
~  
然而问题在于 T 2 T_2 T2的这个底层节点有可能根本就不存在,在这样的情况下,相对于原树的高度,调整之后新树的高度会缩短一个单位。此时不妨设想这样一个场景,也就是某一个祖先,它的另一个分支可能会更高,换而言之,它此前的平衡因子已经是-1,因此在原本就更短的左侧分支进一步缩短之后,它的平衡因子将进一步下降到-2,从而超标。

请注意,这个节点在我们调整之前,原本是平衡的,而在它下属的后代恢复平衡之后,它却有可能进而失衡。我们也可以等效地认为这个节点的失衡是由于为了消除它后代的失衡进而引发的,这样一种失衡逐渐向上层传播的现象,也是删除操作所特有的。

当然从算法而言这并不是什么了不起的事情,因为对于这个新的失衡祖先,我们完全可以套用整个调整算法继续使它复衡。当然,有可能又会引发更高层祖先的失衡,极端的情况下,我们有可能会在每一层都进行一次调整。累计而言,这种调整有可能会多达logn次。

需要指出的是这样一种估计既不是杞人忧天更不是危言耸听,我们的确可以构造出这样的反例。当然与插入算法一样,我们还需要考虑另外一种情况,也就是gpv这样连续三代节点未必是朝一个方向排列的,如果它们是按照所谓的之字形排列呢?

3.2 双旋

我们只需考虑其中一种也就是所谓的zag-zig的情况。另一种zig-zag的情况完全对称。
在这里插入图片描述
在这种情况下,v是p的右孩子,而p是g的左孩子。此时我们依然只可能至多有一个失衡节点,不难理解如果g果然是这个失衡节点,那此前所删除的必然是 T 3 T_3 T3这棵树的某一底层节点,而且因为这个底层节点的删除,导致 T 3 T_3 T3整体的高度收缩一层,从而使得节点g的平衡因子由此前的+1变成超标的+2。

在这种情况下,我们要先后做两次旋转调整。

  1. 具体来说,首先需要围绕着节点p做一次逆时针的zag旋转,同样地,假定对基本旋转非常熟悉了,所以在这里直接给出旋转之后的结果(上中图),我们可以看到,它等效于将此前的zag-zig转为了现在的zig-zig,恰好就是我们刚刚介绍过的单旋操作。
  2. 接下来,只需要围绕着节点g做一次zig,就可以使得这棵子树重新恢复平衡。
    ~  
    尽管如此,还是特别提醒你留意,这棵局部子树在调整前后的高度变化。我们知道在 T 1 T_1 T1 T 2 T_2 T2这两棵子树的底层至少有一个节点存在,这就意味着,在旋转调整之前,这棵局部子树的高度应该是由最下面这条水平基本线来确定的,而在调整完成之后,这棵局部子树的高度,将由第二条基准线确定。
    ~  
    两相比较,高度收缩了一层,这意味着什么呢?
    ~  
    没错,这意味着在这棵局部子树以上的每一个祖先此时都存在由原先的平衡转为失衡的可能,也就是说,同样会发生失衡的向上传播。当然,果真如此,我们可以依然套用这里所给的算法,好在失衡传播的方向是单调的一直朝上。

所以同样的,充其量至多经过logn次这样的传播,迟早会抵达某个不再失衡的节点或者抵达树根。

至此,节点删除可能的几种情况都已论及,那么这些处理算法又当如何落实为具体代码呢?

3.3 实现

这里就给出AVL树节点删除算法一种可能的实现
在这里插入图片描述
开始的几步只不过是BST常规的节点删除算法,具体来说,我们进行一次搜索,并且不妨假设目标节点的确存在。于是我们调用removeAt例程,将这个节点物理地摘除掉。

接下来,我们依然是通过一个for循环遍历被删除节点地历代祖先。

请特别注意,我们的起点是被删除节点的父亲,而不是像插入操作那样,可以直接从它的祖父开始。

那么在整个遍历过程中,我们每发现一个失衡的祖先g,都要对这个祖先做一轮适当地旋转调整,而旋转所涉及到的三个节点依然是g、以及它更高孩子p、以及再往下更高的那个孙子v。而且无论是否失衡或者做过旋转调整,我们都有必要调整这个祖先的高度。

可以看出在最坏情况下,的确需要做logn次。

不妨将此前插入操作的控制逻辑与此处做一对比。

的确,这里没有可以提前终止遍历过程的窍门,因为在最坏情况下,我们的确需要无一遗漏地处理所有的各代祖先。此后,才能够顺利地结束算法并且返回。

至此,你可能会发现,对于这里的旋转操作,我们还没有给出它的具体实现方法,它的确是按照我们刚才所推荐的单选和双旋那种方式来实现的吗?很有趣的是,答案是否定的。

4. (3 + 4)-重构

4.1 "3+4"重构

实际上,以上针对AVL树插入操作和删除操作所介绍的单旋式和双旋式调整技巧无非是为了帮助你形成对算法的理解,而在真正的实现时,我们大可不必机械地如此理解。

这样一个过程可以比喻为玩魔方,是的,你需要在规则的允许下,通过巧妙的旋转组合,使之转入某种特定状态,比如六面各自同色。那么你是否去过魔方的组装车间?你会发现那里的工人可不是按照你这样的规则在那进行这样的旋转。实际上,它们无非是将魔方的一个又一个组件直接地拼接为一个魔方,工人们之所以这么做,是因为它们最大也是唯一目标是尽快地以最高的效率完成魔方的组装。
在这里插入图片描述

我们这里呢,也不妨借助这一策略,因为对于AVL树的重平衡化而言,我们最终在乎的并不是所谓的技巧,而是在于这个过程的效率,我们来看一下,如何将魔方组装工人的那种策略用到我们这个问题上。

在这里插入图片描述具体来说,我们依然假设g就是当前最低的那个失衡祖先,并且同样地沿着那个最长的分支去考察gpv这祖孙三代,以下我们并不急于对它们进行旋转,而是首先做重命名,也就是说,按照它们在中序遍历序列中的次序自小到大重新命名为ab以及c。

对照我们此前所讲的各种情况,无论是zig-zag、zag-zig、zig-zig或者是zag-zag,你会发现在它们以下无非是最多4棵子树,那么我们也需要对这4 棵子树做重命名,而且命名的规则同样是参照中序遍历的次序,也就是 T 0 T_0 T0是最小的那棵树、 T 1 T_1 T1是次小的、 T 2 T_2 T2是较大的、 T 3 T_3 T3是最大的。

此时,如果我们依然按照中序遍历的次序将这两个序列混合起来就可以得到一个长度为7的序列,在这个序列中,三个节点abc必然是镶嵌于这4棵子树之间,实际上,无论是哪种具体的情况,讲过这样的重命名之后,按照中序遍历的次序,必然是从 T 0 T_0 T0到a,再从a到 T 1 T_1 T1、再从 T 1 T_1 T1到b、然后从b到 T 2 T_2 T2、再从 T 2 T_2 T2到c,最终由c到 T 3 T_3 T3。你应该不会觉得奇怪,因为这恰恰就是BST所谓的单调性

在这样一棵局部子树的具体体现,在调整之前,即便这棵子树是失衡的,它也依然是一棵BST,所以这个单调性应该自然满足,而在调整之后,尽管它已经恢复了平衡,但是这个单调性也依然是需要保持,因此,我么可以统一地将这三个顶点abc以及这4棵子树按照这样一个拓扑关系直接地拼接起来(如上图)。

具体来说,以a和c分别作为b的左和右孩子,而 T 0 T_0 T0 T 1 T_1 T1将作为a的左和右子树, T 2 T_2 T2 T 3 T_3 T3分别作为c的左和右子树,这样一种拼接是针对于三个节点以及下属4棵子树而言的,所以也称作3+4重构

在此,不妨稍作暂停,并对照此前所介绍的各种情况以及相应的调整算法,你应该会发现,无论是插入还是删除,无论是单旋还是双旋,最终的效果都应该是这样一种形式。

这也犹如无论魔方的最终状态如何,也无论你所设计的旋转方案具体如何,最终必然应该达到你心目中早已设计好的一个结局,对于魔方而言,一般都是六面各自同色,而对于我们的BBST而言,则是在此局部的重新平衡。

按照这样一个思路,我们可以更为概括而且更为深入地来理解并且记忆以上各种情况的处理手法,而更好消息是,按照这样一种理解,我们也可以更加简明更加高效而且更加安全鲁棒地来实现相应的重构算法

4.2 "3+4"实现

在这里,给出3+4重构算法的一种实现方式
在这里插入图片描述
作为这个算法的输入,我们的确需要提供三个节点abc以及4棵子树 T 0 T_0 T0 T 1 T_1 T1 T 2 T_2 T2 T 3 T_3 T3,以下我们可以通过这样一段非常规整的代码完成这3个顶点以及4棵子树之间的连接。可以看到,这样一种实现的思路非常的简明清晰,而且因为这段代码非常的规整,所以更加便于编写、调试以及维护和重用,出现错误的概率也会降到最低。

那么接下来需要解释的一个问题就是在以上各种情况下,我们如何来完成对3个节点以及4棵子树的重命名,从而以正确的参数形式转交给这样一个connect34例程呢?

4.3 rotateAt

现在这个曾经多次出现并引起我们非常好奇的rotateAt算法,终于到了可以揭开它神秘面纱的时候了。
在这里插入图片描述
它的传入参数只有一个,也就是在我们所关心的祖孙三代中作为孙辈的那个节点v,所以通过父亲引用,我们可以很便捷地找到p以及g。

于是接下来,我们只需分别判断p和v究竟是左孩子还是右孩子,就可以正确区分zig-zig,zig-zag以及zag-zig和zag-zag各种情况。而在每一种具体情况下,vpg究竟应该如何命名为abc以及它们属下的4棵子树究竟应该如何命名为 T 0 T_0 T0 T 1 T_1 T1 T 2 T_2 T2 T 3 T_3 T3都是固定的,我们甚至可以把这些情况汇总为一张表,我们不妨来考察这四种情况中的两种。

  1. 第一种也就是所谓的zig-zig的状态,无论v还是p都是左孩子,在这样一种情况下,你不难理解,它们的中序遍历序列自然应该是v-a、p-b和g-c,而4棵子树呢,也相应地应该是v的左子树、v的右子树、以及p的右子树和g的右子树,就像上图所画的那样。
  2. 再看所谓的zig-zag情况,也就是说,v是右孩子,而p却是左孩子,在这种情况下,同样按照中序遍历次序,不难确定,应该是p-a、v-b和g-c,而4棵子树呢,也自然应该是p的左子树、v的左子树、v的右子树以及g的右子树,在上图中,我们可以更清晰地看出这一点,也就是p的左子树、v的左子树、v的右子树以及g的右子树。

另外两种情况完全对称。

4.4 综合评价

最后我们来对AVL树的性能和特点做一个总体的评价

在这里插入图片描述
首选我们注意到AVL树具有极高的理论价值,因为它正面的告诉我们的确存在这样一种数据结构,可以在渐进logn的复杂度意义下兼顾所有的静态和动态操作,而且为此,我们的存储负担也不会有实质的增加。

当然AVL树的缺点也是非常明显的,这也将成为我们的动力,促使我们去不断地改进并提出更高更高效的数据结构。

那么AVL树的第一个缺点就在于它人为的引入了一个所谓的平衡因子概念,并且要求在这个数据结构中隐式地或者显示地存放,为此我们往往需要去改造数据项原有地基本结构,或者再做额外的封装,无论如何这一要求都过于做作,显得不是那么自然。
~  
那么针对这一问题,在下一章将首先引入所谓的伸展树,我们将会看到伸展树将无需记录和维护任何诸如平衡因子的指标,无论是显式的还是隐式的。
~  
另外,AVL树的实测性能与它的理论性能之间存在着较大的差距。尤其是正如我们所看到的它的删除操作要更为复杂,尽管knuth曾经指出这种最坏的情况以及较坏的情况出现的概率其实很低。平均而言,大概每五次操作才会引起一次旋转。

如果说以上的两个缺点都属于鸡蛋里挑骨头,那么第三个缺点却的确是致命的。因为我们已经看到,对于AVL树而言,它的插入操作和删除操作是非常不对等的,这种不对等就集中体现在每次操作之后所涉及的旋转调整次数。具体来说,每次插入操作之后,最多只需一轮调整,也就是常数次O(1),而在删除操作之后,为了使得全树重新恢复平衡,正如我们已经看到的,在最坏情况下,我们需要做多达logn次旋转调整。因此就全树中各节点之间的拓扑连接关系而言,在插入操作之后,可以保证变化量保持在常数的范围,而删除操作却未必能做到这样。

实际上,在很多高级的数据结构和算法中都对这种拓扑结构的变化量有严格的要求,具体来说,我们这里高达logn的变化量绝对是不能满足要求的。我们希望将它们控制到更低,比如在下一章将要介绍的红黑树,则可以将这个变化量严格控制在每次不超过常数0(1),无论对于插入操作还是删除操作都是如此。

相关文章:

【数据结构(邓俊辉)学习笔记】二叉搜索树04——AVL树

文章目录 1.重平衡1.1 AVL BBST1.2 平衡因子1.3 适度平衡1.4 接口1.5 失衡 复衡 2. 插入2.1 单旋2.2 双旋2.3 实现 3. 删除3.1 单旋3.2 双旋3.3 实现 4. (3 4)-重构4.1 "34"重构4.2 "34"实现4.3 rotateAt4.4 综合评价 1.重平衡 1…...

SpringMVC基础详解

文章目录 一、SpringMVC简介1、什么是MVC2、MVC架构模式与三层模型的区别3、什么是SpringMVC 二、HelloWorld程序1、pom文件2、springmvc.xml3、配置web.xml文件4、html文件5、执行Controller 三、RequestMapping注解1、value属性1.1、基础使用1.2、Ant风格(模糊匹配…...

SQL SERVER 设置端口

要在SQL Server中设置端口&#xff0c;可以通过SQL Server Configuration Manager来完成。以下是详细的步骤&#xff1a; 1. 打开SQL Server Configuration Manager 在Windows中&#xff0c;按 Win R 键打开运行窗口。输入 SQLServerManager<version>.msc 并按回车。例…...

华芯微特2024慕尼黑上海电子展预告

7月8日-7月10日&#xff0c;2024慕尼黑上海电子展在上海新国际博览中心举办。华芯微特展号:E4.4815&#xff0c;诚意邀请各位莅临参观。 公司介绍 华芯微特是一家由留美归国资深技术团队创立的中国芯片设计公司&#xff0c;是国家高新技术企业。2014年进军MCU产业&#xff0c;专…...

DETR End-to-End Object Detection with Transformers

End-to-End Object Detection with Transformers 论文链接&#xff1a;http://arxiv.org/abs/2005.12872 代码地址&#xff1a;https://github.com/facebookresearch/detr 一、摘要 提出了一种将目标检测视为直接集合预测问题的新方法。该方法简化了检测流程&#xff0c;有效…...

【后端面试题】【中间件】【NoSQL】ElasticSearch 节点角色、写入数据过程、Translog和索引与分片

中间件的常考方向&#xff1a; 中间件如何做到高可用和高性能的&#xff1f; 你在实践中怎么做的高可用和高性能的&#xff1f; Elasticsearch节点角色 Elasticsearch的节点可以分为很多种角色&#xff0c;并且一个节点可以扮演多种角色&#xff0c;下面列举几种主要的&…...

【TB作品】玩具电子琴,ATMEGA128单片机,Proteus仿真

题目 7 &#xff1a;玩具电子琴 基于单片机设计一能够发出中音八个音阶的音乐信号的电子琴&#xff0c;能够实现弹奏和音符显示功 能。 具有 8 个音阶按键&#xff0c;每按下一个按键时&#xff0c;所对应的 LED 点亮&#xff0c;音符进行显示。 具体要求如下&#xff1a; &…...

1974Springboot医院远程诊断管理系统idea开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot医院远程诊断管理系统是一套完善的信息系统&#xff0c;结合springboot框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库…...

SQL游标的应用场景及使用方法

SQL游标的应用场景及使用方法 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨SQL中游标的应用场景及使用方法。游标在SQL中是一种重要的数据…...

LLama-Factory使用教程

本文是github项目llama-factory的使用教程 注意&#xff0c;最新的llama-factory的github中训练模型中&#xff0c;涉及到本文中的操作全部使用了.yaml配置。 新的.yaml的方式很简洁但不太直观&#xff0c;本质上是一样的。新的readme中的.yaml文件等于下文中的bash指令 PS: …...

Java面试题:讨论在Java Web应用中实现安全的认证和授权机制,如使用Spring Security

在Java Web应用中&#xff0c;实现安全的认证和授权是至关重要的&#xff0c;Spring Security是一个强大的框架&#xff0c;可以简化这项工作。以下是详细讨论如何在Java Web应用中使用Spring Security实现安全的认证和授权机制。 Spring Security简介 Spring Security是一个…...

如何在Vue3项目中使用Pinia进行状态管理

**第一步&#xff1a;安装Pinia依赖** 要在Vue3项目中使用Pinia进行状态管理&#xff0c;首先需要安装Pinia依赖。可以使用以下npm命令进行安装&#xff1a; bash npm install pinia 或者如果你使用的是yarn&#xff0c;可以使用以下命令&#xff1a; bash yarn add pinia *…...

【初阶数据结构】深入解析队列:探索底层逻辑

&#x1f525;引言 本篇将深入解析队列:探索底层逻辑&#xff0c;理解底层是如何实现并了解该接口实现的优缺点&#xff0c;以便于我们在编写程序灵活地使用该数据结构。 &#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#…...

Go 语言环境搭建

本篇文章为Go语言环境搭建及下载编译器后配置Git终端方法。 目录 安装GO语言SDK Window环境安装 下载 安装测试 安装编辑器 下载编译器 设置git终端方法 总结 安装GO语言SDK Window环境安装 网站 Go下载 - Go语言中文网 - Golang中文社区 还有 All releases - The…...

javascript v8编译器的使用记录

我的机器是MacOS Mx系列。 一、v8源码下载构建 1.1 下载并更新depot_tools git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git export PATH/path/to/depot_tools:$PATH 失败的话可能是网络问题&#xff0c;可以试一下是否能ping通&#xff0c;连…...

C语言--vs使用调试技巧

1.什么是bug? 1.产品说明书中规定要做的事情&#xff0c;而软件没有实现。 2.产品说明书中规定不要做的事情&#xff0c;而软件确实现了。 3.产品说明书中没有提到过的事情&#xff0c;而软件确实现了。 4.产品说明书中没有提到但是必须要做的事情&#xff0c;软件确没有实…...

Spring Boot中的国际化配置

Spring Boot中的国际化配置 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将探讨如何在Spring Boot应用中实现国际化配置&#xff0c;使得应用能够轻松…...

WPF的IValueConverter用于校验和格式化TextBox的数字输入

在数据绑定&#xff08;Data Binding&#xff09;的上下文中&#xff0c;我们经常使用继承 IValueConverter 接口的类&#xff0c;用于在源值和目标值之间进行转换。该接口定义了两个方法&#xff1a;Convert 和 ConvertBack&#xff0c;这两个方法分别用于从源值到目标值的转换…...

SQL Server的守护神:Always On 高可用性详解

&#x1f6e1;️ SQL Server的守护神&#xff1a;Always On 高可用性详解 在企业级应用中&#xff0c;数据的可用性和业务连续性至关重要。SQL Server的Always On功能提供了一个高可用性解决方案&#xff0c;确保数据库服务的持续运行和快速故障恢复。本文将详细解释SQL Serve…...

Eureka入门

Eureka是一种服务发现工具&#xff0c;广泛应用于微服务架构中。它主要由Netflix开源&#xff0c;帮助服务在分布式系统中自动注册和发现。以下是Eureka的基本入门指南。 前提条件 在开始之前&#xff0c;确保你已经安装了以下软件&#xff1a; JDK 8或更高版本Maven或Gradl…...

高阶面试-netty部分

介绍下netty Netty 是一个基于 Java 的异步事件驱动的网络应用框架&#xff0c;提供了用于快速开发高性能、高可扩展性的协议服务器和客户端的工具 BIO、NIO、AIO 的区别是什么 BIO blocking io&#xff0c;同步阻塞IO&#xff0c;比较简单&#xff0c;为每个请求分配一个线…...

在 C++的跨平台开发中,如何处理不同操作系统和编译器之间的细微差异,以确保程序能够稳定且高效地运行?

在 C 的跨平台开发中&#xff0c;处理不同操作系统和编译器之间的细微差异是非常重要的。以下是一些处理差异的技巧&#xff1a; 使用条件编译&#xff1a;使用预处理指令&#xff0c;根据不同的操作系统和编译器来编写不同的代码。 #if defined(_WIN32)// Windows 特定代码 …...

独孤思维:脑子不好使,副业稳赚钱

01 副业&#xff0c;贴身级模仿。 比如独孤最近在测试dy虚拟资料项目。 跑了三个多月。 赚了点下小钱。 从最开始的自动生成视频&#xff0c;到后来的抽帧优化&#xff0c;再到先做的矩阵玩法。 一直都在迭代。 是独孤脑子好使吗&#xff1f; 恰恰相反。 正式因为独孤…...

【数据结构】(C语言):二叉搜索树

二叉搜索树&#xff1a; 树不是线性的&#xff0c;是层级结构。基本单位是节点&#xff0c;每个节点最多2个子节点。有序。每个节点&#xff0c;其左子节点都比它小&#xff0c;其右子节点都比它大。每个子树都是一个二叉搜索树。每个节点及其所有子节点形成子树。可以是空树。…...

泛微开发修炼之旅--23基于ecology自研的数据库分页组件(分页组件支持mysql、sqlserver、oracle、达梦等)

一、使用场景 ecology二开开发过程中&#xff0c;经常要使用到分页查询&#xff0c;随着信创项目的到来&#xff0c;各种国产数据库的出现&#xff0c;对于数据库分页查询兼容何种数据库&#xff0c;就迫在眉睫。 于是&#xff0c;我自己基于ecology开发了一个分页插件&#…...

《昇思25天学习打卡营第4天 | mindspore Transforms 数据变换常见用法》

1. 背景&#xff1a; 使用 mindspore 学习神经网络&#xff0c;打卡第四天&#xff1b; 2. 训练的内容&#xff1a; 使用 mindspore 的常见的数据变换 Transforms 的使用方法&#xff1b; 3. 常见的用法小节&#xff1a; 支持一系列常用的 Transforms 的操作 3.1 Vision …...

【Python时序预测系列】基于LSTM实现多输入多输出单步预测(案例+源码)

这是我的第312篇原创文章。 一、引言 单站点多变量输入多变量输出单步预测问题----基于LSTM实现。 多输入就是输入多个特征变量 多输出就是同时预测出多个标签的结果 单步就是利用过去N天预测未来1天的结果 二、实现过程 2.1 读取数据集 dfpd.read_csv("data.csv&qu…...

git客户端工具之Github,适用于windows和mac

对于我本人&#xff0c;我已经习惯了使用Github Desktop,不同的公司使用的代码管理平台不一样&#xff0c;就好奇Github Desktop是不是也适用于其他平台&#xff0c;结果是可以的。 一、克隆代码 File --> Clone repository… 选择第三种URL方式&#xff0c;输入url &…...

ai除安卓手机版APP软件一键操作自动渲染去擦消稀缺资源下载

安卓手机版&#xff1a;点击下载 苹果手机版&#xff1a;点击下载 电脑版&#xff08;支持Mac和Windows&#xff09;&#xff1a;点击下载 一款全新的AI除安卓手机版APP&#xff0c;一键操作&#xff0c;轻松实现自动渲染和去擦消效果&#xff0c;稀缺资源下载 1、一键操作&…...

Unity获取剪切板内容粘贴板图片文件文字

最近做了一个发送消息的unity项目&#xff0c;需要访问剪切板里面的图片文字文件等&#xff0c;翻遍了网上的东西&#xff0c;看了不是需要导入System.Windows.Forms&#xff08;关键导入了unity还不好用&#xff0c;只能用在纯c#项目中&#xff09;&#xff0c;所以我看了下py…...

利用谷歌云serverless代码托管服务Cloud Functions构建Gemini Pro API

谷歌在2024年4月发布了全新一代的多模态模型Gemini 1.5 Pro&#xff0c;Gemini 1.5 Pro不仅能够生成创意文本和代码&#xff0c;还能理解、总结上传的图片、视频和音频内容&#xff0c;并且支持高达100万tokens的上下文。在多个基准测试中表现优异&#xff0c;性能超越了ChatGP…...

极狐GitLab 17.0 重磅发布,100+ DevSecOps功能更新来啦~【一】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab &#xff1a;https://gitlab.cn/install?channelcontent&utm_sourcecsdn 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署…...

python实现符文加、解密

在历史悠久的加密技术中&#xff0c;恺撒密码以其简单却有效的原理闻名。通过固定的字母位移&#xff0c;明文可以被转换成密文&#xff0c;而解密则是逆向操作。这种技术不仅适用于英文字母&#xff0c;还可以扩展到其他语言的字符体系&#xff0c;如日语的平假名或汉语的拼音…...

【解释】i.MX6ULL_IO_电气属性说明

【解释】i.MX6ULL_IO_电气属性说明 文章目录 1 Hyst1.1 迟滞&#xff08;Hysteresis&#xff09;是什么&#xff1f;1.2 GPIO的Hyst. Enable Field 参数1.3 应用场景 2 Pull / Keep Select Field2.1 PUE_0_Keeper — Keeper2.2 PUE_1_Pull — Pull2.3 选择Keeper还是Pull 3 Dr…...

02-《石莲》

石 莲 石莲&#xff08;学名&#xff1a;Sinocrassula indica A.Berger&#xff09;&#xff0c;别名因地卡&#xff0c;为二年生草本植物&#xff0c;全株无毛&#xff0c;具须根。花茎高15-60厘米&#xff0c;直立&#xff0c;常被微乳头状突起。茎生叶互生&#xff0c;宽倒披…...

MySQL之聚簇索引和非聚簇索引

1、什么是聚簇索引和非聚簇索引&#xff1f; 聚簇索引&#xff0c;通常也叫聚集索引。 非聚簇索引&#xff0c;指的是二级索引。 下面看一下它们的含义&#xff1a; 1.1、聚集索引选取规则 如果存在主键&#xff0c;主键索引就是聚集索引。如果不存在主键&#xff0c;将使…...

Web后端开发之前后端交互

http协议 http ● 超文本传输协议 &#xff08;HyperText Transfer Protocol&#xff09;服务器传输超文本到本地浏览器的传送协议 是互联网上应用最为流行的一种网络协议,用于定义客户端浏览器和服务器之间交换数据的过程。 HTTP是一个基于TCP/IP通信协议来传递数据. HTT…...

520. 检测大写字母 Easy

我们定义&#xff0c;在以下情况时&#xff0c;单词的大写用法是正确的&#xff1a; 全部字母都是大写&#xff0c;比如 "USA" 。 单词中所有字母都不是大写&#xff0c;比如 "leetcode" 。 如果单词不只含有一个字母&#xff0c;只有首字母大写&#xff0…...

vue的跳转传参

1、接收参数使用route,route包含路由信息&#xff0c;接收参数有两种方式,params和query path跳转只能使用query传参,name跳转都可以 params&#xff1a;获取来自动态路由的参数 query&#xff1a;获取来自search部分的参数 写法 path跳,query传 传参数 import { useRout…...

docker配置镜像源

1&#xff09;打开 docker配置文件 sudo nano /etc/docker/daemon.json 2&#xff09;添加 国内镜像源 {"registry-mirrors": ["https://akchsmlh.mirror.aliyuncs.com","https://registry.docker-cn.com","https://docker.mirrors.ustc…...

MySQL高级-SQL优化-insert优化-批量插入-手动提交事务-主键顺序插入

文章目录 1、批量插入1.1、大批量插入数据1.2、启动Linux中的mysql服务1.3、客户端连接到mysql数据库&#xff0c;加上参数 --local-infile1.4、查询当前会话中 local_infile 系统变量的值。1.5、开启从本地文件加载数据到服务器的功能1.6、创建表 tb_user 结构1.7、上传文件到…...

认识100种电路之振荡电路

在电子电路领域&#xff0c;振荡是一项至关重要的功能。那么&#xff0c;为什么电路中需要振荡&#xff1f;其背后的原理是什么&#xff1f;让我们一同深入探究。 【为什么需要振荡电路】 简单来说&#xff0c;振荡电路的存在是为了产生周期性的信号。在众多电子设备中&#…...

SSH 无密登录配置流程

一、免密登录原理 非对称加密&#xff1a; 由于对称加密的存在弊端&#xff0c;就产生了非对称加密&#xff0c;非对称加密中有两个密钥&#xff1a;公钥和私钥。公钥由私钥产生&#xff0c;但却无法推算出私钥&#xff1b;公钥加密后的密文&#xff0c;只能通过对应的私钥来解…...

Python自动化运维 系统基础信息模块

1.系统信息的收集 系统信息的收集&#xff0c;对于服务质量的把控&#xff0c;服务的监控等来说是非常重要的组成部分&#xff0c;甚至是核心的基础支撑部分。我们可以通过大量的核心指标数据&#xff0c;结合对应的检测体系&#xff0c;快速的发现异常现象的苗头&#xff0c;进…...

如何安装和配置Monit

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 关于 Monit Monit 是一个有用的程序&#xff0c;可以自动监控和管理服务器程序&#xff0c;以确保它们不仅保持在线&#xff0c;而且文…...

【redis】redis分片集群基础知识

1、基本概念 1.1定义 分片&#xff1a;数据按照某种规则&#xff08;比如哈希&#xff09;被分割成多个片段&#xff08;或分片&#xff09;&#xff0c;每个片段被称为一个槽&#xff08;slot&#xff09;。槽是Redis分片集群中数据的基本单元。节点&#xff1a;Redis分片集…...

Python 面试【★★★★】

欢迎莅临我的博客 &#x1f49d;&#x1f49d;&#x1f49d;&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…...

Knife4j 2.2.X 版本 swagger彻底禁用

官方文档配置权限&#xff1a;https://doc.xiaominfo.com/v2/documentation/accessControl.html#_3-5-1-%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E5%B1%8F%E8%94%BD%E8%B5%84%E6%BA%90 通常有时候我们碰到的问题如下&#xff1a; 在开发Knife4j功能时,同很多开发者经常讨论的问…...

linux下mysql的定时备份

备份是容灾的基础&#xff0c;是指为了防止系统出现操作或系统故障导致数据丢失&#xff0c;而将全部或部分数据集合从应用主机的硬盘或阵列复制到其他的存储介质的过程为什么备份 硬件故障软件故障误操作病毒入侵保留历史记录灾难性事件 存储介质 光盘磁带硬盘磁盘阵列DAS:直接…...

【13】地址-比特币区块链的地址

1. 比特币区块链的地址 这就是一个真实的比特币地址:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa。这是史上第一个比特币地址,据说属于中本聪。 比特币地址是完全公开的,如果你想要给某个人发送币,只需要知道他的地址就可以了。实际上,所谓的地址,只不过是将公钥表示成人类可读…...

【HICE】web服务器搭建4

自定义多个ip地址访问 1.下载httpd协议&#xff1a;dnf install httpd -y 2.编辑vhost.conf cd /etc/httpd cd /conf.d <directory /www> allowoverride none require all granted </directory> <virtualhost 192.168.244.130:80> documentroot /www s…...

理解论文笔记:基于AHP和模糊综合评价的无线传感器网络可维护性评估方法

作为一个研0的娃,这是我认真读的第一篇论文,想着笔记让自己能看懂。如有侵权,请联系删除。 I. INTRODUCTION 介绍 主要介绍了无线传感器网络可维护性研究的重要性和必要性,并对下面的各章进行了总结。 翻译:第二部分简要介绍了无线传感器网络的维护,并对影响系统的因素…...

Unity扩展编辑器功能的特性

1.添加分组标题 用于在Unity的Inspector视图中为属性或变量组创建一个自定义的标题或头部&#xff0c;有助于在Inspector中组织和分类不同的属性&#xff0c;使其更易于阅读和管理。 [Header("Common Properties")] public float MouseSensitivity 5; public float…...

编译原理 第二章下: 推导,规约,句型句子,语言,文法分类,二义性

文章目录 2.3 推导2.3.1 直接推导/直接规约2.3.2 推导/规约2.3.3 规范推导 2.4 句型和句子2.5 语言2.6 文法的分类2.6.1 0型文法2.6.2 1型文法2.6.3 2型文法2.6.4 3型文法 2.7 推导语法树的构造2.8 递归规则和递归文法2.9 文法的二义性2.9.1 有关文法的实用限制 2.3 推导 2.3.…...

【Verilog HDL-1】基本、向量、模块

HDL习题 1 阻塞型赋值‘’与非阻塞型赋值‘<’ 阻塞型赋值 b a ba ba&#xff1a;适用于纯组合电路 非阻塞型赋值 b < a b<a b<a&#xff1a;适用与时序逻辑电路 2 wire线型,assign连续赋值 wire a,b,c; assign b a; assign c a;与编程语言不同&#xff…...

Linux CentOS 环境 MySQL 主从复制集群搭建

环境说明 MySQL版本8.4.0 操作系统 Linux CentOS 7.9 官网文档 https://dev.mysql.com/doc/refman/8.4/en/replication-configuration.html 以下代码片段中带分号都是在MySQL命令行( mysql -uroot -p)中执行 1. 首先在两个节点上安装数据库 参考 Linux CentOS安装MySQL8.0 …...

底盘革新+M9同款雷达,问界M7Ultra升级点都在这

20万级的SUV,8个月获得超过18万的用户,这样辉煌的成绩你几乎很难在汽车圈找到第二家,问界M7国民SUV名副其实。???但华为与赛力斯绝不止步于此,5月31日晚,问界M7 Ultra正式上市,次共推出四款车型,售价区间为28.98-32.98万元。与此同时北汽新能源与华为合作的享界S9也正…...

彻底乱了!5米长豪华车,原价近30万,现仅售20万出头,还要啥凯美瑞?

今天说的这款车就是凯迪拉克CT5。凯迪拉克CT5车长接近5米,全系标配2.0T+10AT动力系统,整体性价比超高,实力剑指宝马3系、奥迪A4L。作为一款新美式豪华格调轿车——凯迪拉克CT5在中国市场销量超20万辆,运动轿车价值新标杆诞生!作为一款美系豪华中级轿车,凯迪拉克CT5近年来…...

华为鸿蒙智行五一假期全系车型大定破11000台

随着五一假期的结束,华为智选车业务鸿蒙智行公布了整个假期的“销售成绩单”:5月1日-5 月5 日全系车型大定突破11000 台。在假期的前四天,也就是5月1日-4日,鸿蒙智行全系车型大定突破 8600 台,意味着 5月5日一天内新增了超过2400台的大定订单。根据此前公布的数据,鸿蒙智行…...

Python--List列表

list列表⭐⭐ 1高级数据类型 Python中的数据类型可以分为&#xff1a;数字型&#xff08;基本数据类型&#xff09;和非数字型&#xff08;高级数据类型&#xff09; ●数字型包含&#xff1a;整型int、浮点型float、布尔型bool、复数型complex ●非数字型包含&#xff1a;字符…...

习近平将出席中国—阿拉伯国家合作论坛第十届部长级会议开幕式并发表主旨讲话

新华社北京5月26日电 外交部发言人华春莹26日宣布:经中阿双方共同商定,中国—阿拉伯国家合作论坛第十届部长级会议将于5月30日在北京举行。国家主席习近平将出席会议开幕式并发表主旨讲话。中共中央政治局委员、外交部长王毅将同阿方主席、毛里塔尼亚外长马尔祖克共同主持会议…...

于AI对话 --如何更好的使用AI工具

文章目录 于AI对话 --如何更好的使用AI工具1、认识AI工具&#xff1a;2、对话原则&#xff1a;3、提问步骤&#xff1a;4、AI可以学习什么&#xff1f;5、提问技巧&#xff1a;1、提出假设性问题:2、&#xff08;鼓励引导式提问&#xff09;跨学科思考:举个例子&#xff1a; 3、…...