AVL树gydF4y2Ba
一个gydF4y2BaAVL树gydF4y2Ba是the的变体gydF4y2Ba二进制搜索树gydF4y2Ba.像二叉搜索树一样,它由“根”和“叶”节点组成。每个节点最多有两个子节点,其中左子节点小于父节点,右子节点大于父节点。gydF4y2Ba
但二元搜索树可以是不平衡或平衡的。如果其左子树和右子子树的深度不同,则一棵树是平衡的gydF4y2Ba最多1gydF4y2Ba.每次操作后,AVL树都平衡。AVL的操作速度更快,因为它始终是平衡的。AVL树是第一个自平衡树结构。它们通常被超越gydF4y2Ba红黑树gydF4y2Ba自从此以来。gydF4y2Ba
内容gydF4y2Ba
概述gydF4y2Ba
AVL树的操作与gydF4y2Ba二叉搜索树gydF4y2Ba.当插入一个元素时,我们可以使用二分查找来快速找到它想要的位置。从根元素开始,将节点与新元素进行比较。如果新元素较小,则对节点的左子元素执行相同的过程。如果它更大,则使用右子节点。在下面的GIF中,要插入的元素是1。从根节点5开始,将1与特定节点进行比较。在这个例子中,它更小,所以它向左移动到3。它也比3小,所以它找到了它想要的位置,作为3的左子结点。gydF4y2Ba
在这个GIF中gydF4y2Ba深度gydF4y2Ba将每个节点的值写在节点的旁边。记住AVL树必须保持平衡。这意味着一个节点的左子树和它的右子树的深度可以不同gydF4y2Ba最多1gydF4y2Ba.gydF4y2Ba
现在让我们看看插入后,AVL树需要平衡它的情况。第15号将插入。这将导致8(具有深度0的树)的左子树(一棵树为0)和8(带有深度2的树)的右子树与高度大于1.所以,需要改变其结构以反映其改变。gydF4y2Ba
那里发生了什么事?在AVL树中有两种旋转,右旋转和左旋转。何时使用这些旋转可能很棘手,但类型本身本质上是图形化的。观察上面GIF中的树如何自我重组。旋转发生在节点8上。进行左旋转,我们这样说是因为节点是逆时针运动的。gydF4y2Ba
类型的旋转gydF4y2Ba
如上所述,有两种类型的旋转,左旋转和右旋转。然而,诀窍在于知道要使用哪个旋转以及旋转哪个节点。一般有4种情况:gydF4y2Ba
好的好的情况下gydF4y2Ba
这是我们在gydF4y2Ba第二个GIFgydF4y2Ba.在这个例子中,我们有一棵不平衡的树,它看起来像下图:gydF4y2Ba
在这种情况下,我们只需要对不平衡子树的根节点进行简单的左旋转,在这种情况下是1。左旋有以下协议:gydF4y2Ba
- 节点2gydF4y2Ba成为新的根。gydF4y2Ba
- 节点1gydF4y2Ba获取所有权gydF4y2Ba节点2gydF4y2Ba的左子结点作为它的右子结点(在本例中为null)。gydF4y2Ba
- 节点2gydF4y2Ba获取所有权gydF4y2Ba节点1gydF4y2Ba作为它的左子结点。gydF4y2Ba
现在,树是平衡的gydF4y2Ba节点2gydF4y2Ba.gydF4y2Ba
留下,例gydF4y2Ba
左-左情况是右-右情况的镜像。在这个场景中,需要在不平衡树的根节点上进行右旋转。你可以想象一个左-左的情况gydF4y2Ba节点3gydF4y2Ba作为根,gydF4y2Ba节点2gydF4y2Ba作为gydF4y2Ba节点3gydF4y2Ba离开了孩子,和gydF4y2Ba节点1gydF4y2Ba作为gydF4y2Ba节点2gydF4y2Ba左子。协议如下:gydF4y2Ba
- 节点2gydF4y2Ba成为新的根。gydF4y2Ba
- 节点3gydF4y2Ba获取所有权gydF4y2Ba节点1gydF4y2Ba的右子结点作为左子结点(在本例中为null)。gydF4y2Ba
- 节点2gydF4y2Ba获取所有权gydF4y2Ba节点3gydF4y2Ba作为它的右子结点。gydF4y2Ba
左边/右边的情况gydF4y2Ba
下面两种情况稍微有点棘手。左右情况如下图所示。gydF4y2Ba
正如您可以看到的那样,从根本上,有一个右孩子和一个左孩子(因此名称)。这棵树不平衡。然而,一个简单的左转旋转不会让我们有任何好处,因为它将简单地翻转树仍然不平衡。相反,我们必须做两件事来实现这种平衡。gydF4y2Ba
- 在根的右子节点上执行右旋转gydF4y2Ba
- 在root上执行左旋转gydF4y2Ba
首先进行右旋转gydF4y2Ba节点3gydF4y2Ba.现在,我们回到了我们的gydF4y2Ba是的是的gydF4y2Ba的情况。然后,像以前一样,对根执行左旋转,gydF4y2Ba节点1gydF4y2Ba.现在我们的树平衡了。gydF4y2Ba
左右的情况下gydF4y2Ba
这个案子是前一个案子的镜像。协议如下:gydF4y2Ba
- 对根的左子节点执行左旋转gydF4y2Ba
- 在根上执行正确的旋转gydF4y2Ba
在这种情况下,gydF4y2Ba节点3gydF4y2Ba是根,gydF4y2Ba节点1gydF4y2Ba是它的左子结点,然后呢gydF4y2Ba节点2gydF4y2Ba将gydF4y2Ba节点1gydF4y2Ba右边的孩子。左转gydF4y2Ba节点1gydF4y2Ba会把我们留在gydF4y2Ba没有留下gydF4y2Ba案例,右转gydF4y2Ba节点3gydF4y2Ba将平衡AVL树。gydF4y2Ba
复杂性gydF4y2Ba
AVL树由于其平衡性表现得非常好。在搜索中,AVL树的执行方式与gydF4y2Ba二叉搜索树gydF4y2Ba.找到一个项目需要gydF4y2Ba 时间。gydF4y2Ba
您可能认为遍历AVL树很耗时。毕竟,如果你想找到下一个最大的节点,你可能需要回溯到树的根结点,然后再到另一个子树。然而,使用gydF4y2Ba平摊分析gydF4y2Ba,AVL可以显示为线性gydF4y2Ba 时间。为什么?要遍历整棵树,您必须每次进入和退出每条边。因为有gydF4y2Ba 在平衡树中的边缘,这意味着会有gydF4y2Ba 平摊遍历数总共是2。gydF4y2Ba
对于插入和删除,必须考虑到为键找到正确的空间以及重新平衡过程。因为它是二叉树,它会gydF4y2Ba 为新节点找到正确的位置,或者找到我们想要删除的节点。从这里开始,我们必须回溯我们的步骤,一直到根,以确保AVL树的不变量没有被破坏——在任何一点上,一个节点的左右子树的高度都不超过1。然而,我们回溯的次数是有严格限制的;这也是gydF4y2Ba - 我们只是以同样的方式拉出树。旋转本身是一个恒定的时间操作,因此它确实是渐近复杂性的因素。gydF4y2Ba
参见:gydF4y2Ba大O符号gydF4y2Ba.gydF4y2Ba
平均gydF4y2Ba | |
空间gydF4y2Ba | |
搜索gydF4y2Ba | |
遍历gydF4y2Ba | *gydF4y2Ba |
插入gydF4y2Ba | |
删除gydF4y2Ba |
*平摊分析gydF4y2Ba