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的树)和右子树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
- 对根执行左旋转gydF4y2Ba
首先进行右旋转gydF4y2Ba节点3gydF4y2Ba.现在,我们回到gydF4y2Ba对,gydF4y2Ba的情况。然后,像以前一样,对根执行左旋转,gydF4y2Ba节点1gydF4y2Ba.现在我们的树平衡了。gydF4y2Ba
左右的情况下gydF4y2Ba
这个案子是前一个案子的镜像。协议如下:gydF4y2Ba
- 对根的左子节点执行左旋转gydF4y2Ba
- 对根执行右旋转gydF4y2Ba
在这种情况下,gydF4y2Ba节点3gydF4y2Ba是根,gydF4y2Ba节点1gydF4y2Ba是它的左子结点,然后呢gydF4y2Ba节点2gydF4y2Ba将gydF4y2Ba节点1gydF4y2Ba孩子的权利。左旋转gydF4y2Ba节点1gydF4y2Ba会把我们留在gydF4y2Ba没有留下gydF4y2BaCase,右转ongydF4y2Ba节点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