AVL树
二叉平衡树(Balanced Binary Tree)是一类特定的二叉搜索树(BST),其主要特点是通过各种方式保持树的平衡,以确保在最坏情况下操作的时间复杂度为O(log n)。
主要类型的二叉平衡树
- AVL树:
- 定义: 任何节点的两个子树的高度差(平衡因子)最多为1。
- 操作: 插入和删除操作后,可能需要旋转操作来恢复平衡。
- 红黑树:
- 定义: 一种每个节点都有颜色(红色或黑色)的二叉搜索树,满足特定的颜色规则。
- 规则:
- 每个节点要么是红色,要么是黑色。
- 根节点是黑色。
- 每个叶子节点(NIL节点)是黑色。
- 如果一个节点是红色,则它的两个子节点都是黑色。
- 从任何节点到其每个叶子节点的所有路径都包含相同数量的黑色节点(黑色高度)。
- Splay树:
- 定义: 一种自适应的二叉搜索树,通过访问节点时的旋转操作将被访问的节点移动到树的根部。
- 特点: 没有严格的平衡条件,但访问频繁的节点会被提升到根部,从而提高其访问效率。
- B树 和 B+树:
- 定义: 多路平衡搜索树,常用于数据库和文件系统中。
- 特点: 每个节点可以有多个子节点,树的高度保持较低,适合处理大量的数据。
平衡树的优点
- 保证对数时间复杂度: 平衡树的高度被限制在对数级别,使得查找、插入和删除操作的时间复杂度都能保持在O(log n)。
- 提高性能: 通过维护平衡,避免了退化成链表的情况,从而提升了操作性能。
本篇,我们重点来剖析一下,AVL树
- AVL树的概念
在此之前,我们已经学习过二叉搜索树,虽然二叉搜索树可以有效的提高查找效率,但是,当数据有序或者接近有序的二叉搜索树,那将会退化成单边树,在执行查找元素功能时相当于在顺序表中查找,效率低下,由此,两位俄罗斯的数学家G.M.Adelson-Velskii
和E.M.Landis
在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度
基于上文,我们可以概括出:
一颗AVL树,要么是空树,要么是具有这些性质的二叉搜索树:
- 左右子树均为AVL树;
- 左右子树高度之差(被称为平衡因子)的绝对值不超过1;
BF
平衡因子 = 右子树高度 – 左子树高度
如果一颗二叉搜索树是高度平衡的,它就是AVL树,如果它有n个节点,其高度可以保持在O(log2n),时间复杂度O(log2n);
2.AVL树的操作
2.1 AVL树的插入
注意:对于AVL树的插入,因为它是要结合AVL树的旋转的,所以在本文中,AVL树的插入和AVL树的旋转合起来才是完整的插入过程
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树. 那么AVL树的插入可以分为两步:
- 按照二叉搜索树的方式插入新节点
- 调整平衡因子
若向平衡二叉树中插入一个节点后破坏了平衡二叉树的平衡性,首先从根节点到该新插入节点的路径之逆向根节点方向找到第一个失去平衡的节点,然后以该失衡节点和它相邻的刚查找过的两个节点构成调整子树,使之成为新的平衡子树。当失去平衡的最小子树被调整为平衡子树后,原来其他所有不平衡子树无需调整,整个二叉排序树就又成了一个平衡二叉树。
失去平衡的最小子树是指以离插入节点最近,且平衡因子绝对值大于1的节点作为根的子树。
2.2 AVL树的旋转
(1)LL型平衡旋转法
由于在A的左孩子B的左子树上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行一次顺时针旋转操作。 即将A的左孩子B向右上旋转代替A作为根结点,A向右下旋转成为B的右子树的根结点。而原来B的右子树则变成A的左子树。
(2)RR型平衡旋转法
由于在A的右孩子C 的右子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行一次逆时针旋转操作。即将A的右孩子C向左上旋转代替A作为根结点,A向左下旋转成为C的左子树的根结点。而原来C的左子树则变成A的右子树。
(3)LR型平衡旋转法
由于在A的左孩子B的右子数上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行两次旋转操作(先逆时针,后顺时针)。即先将A结点的左孩子B的右子树的根结点D向左上旋转提升到B结点的位置,然后再把该D结点向右上旋转提升到A结点的位置。即先使之成为LL型,再按LL型处理。
如图中所示,红笔圈起来的部分我们先将其调整为平衡时,然后再将其以根节点连接到A的左子树上,然后变为LL型,然后再像前面一样的操作,处理成平衡型;
(4)RL型平衡旋转法
由于在A的右孩子C的左子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行两次旋转操作(先顺时针,后逆时针),即先将A结点的右孩子C的左子树的根结点D向右上旋转提升到C结点的位置,然后再把该D结点向左上旋转提升到A结点的位置。即先使之成为RR型,再按RR型处理。
如图所示,即先将圆圈部分先调整为平衡树,然后将其以根结点接到A的左子树上,此时成为RR型,再按RR型处理成平衡型。
平衡化靠的是旋转。参与旋转的是3个节点(其中一个可能是外部节点NULL),旋转就是把这3个节点转个位置。注意的是,左旋的时候p->right一定不为空,右旋的时候p->left一定不为空,这是显而易见的。
如果从空树开始建立,并时刻保持平衡,那么不平衡只会发生在插入删除操作上,而不平衡的标志就是出现bf == 2或者 bf == -2的节点。
2.3 AVL树的删除
- 采用二叉排序树的删除方法来删除;
- 沿着根节点找到被删除节点的路线,逆着逐层向上查找,对不平衡的点进行旋转操作,更改祖先节点的平衡因子
- 注意: 查找过程中,一旦发现某个祖先*p失衡,立马进行调整。那么我们可以让待删除节点在*p的左子树中,那样,在失衡情形下,就只取决于*p的右孩子,比如:
- *p1的平衡因子为1,我们则去做RL调整;
- *p1的平衡因子为-1,则做RR调整;
- *p1的平衡因子为0,则两种皆可。
待删除节点在*p右子树,与之类似
- 如果调整后,子树高度降低,上面过程要继续,直到根节点为止。即,删除一次节点可能引起多次调整,而不是插入时的最多一次调整。
【删除其实实质上就是插入的逆操作】
我们先来看待删除节点有两个孩子节点的情况:
对于上面三种情况,我们最终将其转化成节点只有一个或没有孩子的节点,那么成功删除之后,*p节点会被影响,失去平衡,这时我们需要去调节,还是上文那句话:
【删除其实实质上就是插入的逆操作】
站在删除的角度,删除左节点,也可以看作是给右边插入节点,所以我们进行判断是否需要调整时,也可以用插入来分析。
分别是四种情况:
共有下面这四种:
R
L
LR
RL
2.4 AVL节点查找
和二叉排序树完全相同。
3.AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log2n 。
但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。 因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合
代码实现
AVL_tree.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef enum
{
EH = 0,
LH = 1,
RH = -1,
}bu_t;
typedef int ElemType;
typedef struct BSTNode
{
ElemType key;
int bf;
struct BSTNode* lchild;
struct BSTNode* rchild;
}BSTNode,*BSTree;
// 接口函数
void InOrderTraverse(BSTree root);
void PreOrderTraverse(BSTree root);
// 右旋
void R_Rotate(BSTree* p);
// 左旋
void L_Rotate(BSTree* p);
// 左平衡
void LeftBalance(BSTree* T);
// 右平衡
void RigehtBalance(BSTree* T);
// 插入AVL
bool InsertAVL(BSTree* t, ElemType e, bool* taller);
// 删除AVL节点
bool DeleteAVL(BSTree* t, ElemType e, bool* shorter);
// 查找
BSTree searchAVL(BSTree t, ElemType key);
static void destroy(BSTree* t);
// 销毁AvL
void destroyAVL(BSTree root);
AVL_tree.c
#include"AVL_tree.h"
void InOrderTraverse(BSTree root) {
if (root != NULL) {
InOrderTraverse(root->lchild);
printf("%d\t", root->key);
InOrderTraverse(root->rchild);
}
}
void PreOrderTraverse(BSTree root) {
if (root != NULL) {
printf("%d\t", root->key);
PreOrderTraverse(root->lchild);
PreOrderTraverse(root->rchild);
}
}
// 右旋
void R_Rotate(BSTree* p) {
BSTree lc = (*p)->lchild;
(*p)->lchild = lc->rchild;
lc->rchild = *p;
*p = lc;
}
// 左旋
void L_Rotate(BSTree* p) {
BSTree rc = (*p)->rchild;
(*p)->rchild = rc->lchild;
rc->lchild = *p;
*p = rc;
}
// 左平衡
void LeftBalance(BSTree* T) {
BSTree lc = (*T)->lchild;
BSTree rd = lc->rchild;
switch (lc->bf)
{
case LH:
(*T)->bf = lc->bf = EH;
R_Rotate(T);
break;
case RH:
switch (rd->bf)
{
case LH:
(*T)->bf = RH;
lc->bf = EH;
break;
case EH:
(*T)->bf = lc->bf = EH;
break;
case RH:
(*T)->bf = EH;
lc->bf = LH;
break;
}
rd->bf = EH;
L_Rotate(&((*T)->lchild));
R_Rotate(T);
break;
}
}
// 右平衡
void RigehtBalance(BSTree* T) {
BSTree rc = (*T)->rchild;
BSTree ld = rc->lchild;
switch (rc->bf)
{
case RH:
(*T)->bf = rc->bf = EH;
L_Rotate(T);
break;
case LH:
switch (ld->bf)
{
case RH:
(*T)->bf = LH;
rc->bf = EH;
break;
case EH:
(*T)->bf = rc->bf = EH;
break;
case LH:
(*T)->bf = EH;
rc->bf = RH;
break;
}
ld->bf = EH;
R_Rotate(&((*T)->rchild));
L_Rotate(T);
break;
}
}
// 插入AVL
bool InsertAVL(BSTree* t, ElemType e, bool* taller) {
if (t == NULL) {
return false;
}
if (*t == NULL) {
*t = (BSTree)malloc(sizeof(BSTNode));
if (NULL == *t) {
return false;
}
(*t)->key = e;
(*t)->lchild = (*t)->rchild = NULL;
(*t)->bf = EH;
*taller = true;
}
else {
if (e == (*t)->key) {
*taller = false;
return false;
}
if (e < (*t)->key) {
if (InsertAVL(&((*t)->lchild), e, taller) == false) {
return false;
}
if (*taller) {
switch ((*t)->bf)
{
case LH:
LeftBalance(t);
*taller = false;
break;
case EH:
(*t)->bf = LH;
*taller = true;
break;
case RH:
(*t)->bf = EH;
*taller = false;
break;
}
}
}
else {
if (InsertAVL(&((*t)->rchild), e, taller) == false) {
return false;
}
if (*taller) {
switch ((*t)->bf)
{
case RH:
RigehtBalance(t);
*taller = false;
break;
case EH:
(*t)->bf = RH;
*taller = true;
break;
case LH:
(*t)->bf = EH;
*taller = false;
break;
}
}
}
}
return true;
}
// 删除节点
bool DeleteAVL(BSTree* t, ElemType e, bool* shorter) {
if (*t == NULL) {
*shorter = false;
return false;
}
if (e < (*t)->key) {
if (!DeleteAVL(&((*t)->lchild), e, shorter)) {
return false;
}
if (*shorter) {
switch ((*t)->bf)
{
case LH:
RigehtBalance(t);
*shorter = false;
break;
case RH:
(*t)->bf = EH;
*shorter = false;
break;
case EH:
(*t)->bf = RH;
*shorter = true;
break;
}
}
}
else if (e > (*t)->key) {
if (!DeleteAVL(&((*t)->rchild), e, shorter)) {
return false;
}
if (*shorter) {
switch ((*t)->bf)
{
case RH:
LeftBalance(t);
*shorter = false;
break;
case LH:
(*t)->bf = EH;
*shorter = false;
break;
case EH:
(*t)->bf = LH;
*shorter = true;
break;
}
}
}
else {
// 删除具体节点
if ((*t)->lchild && (*t)->rchild) {
// 节点有两个孩子
BSTree minNode = (*t)->rchild;
while (minNode->lchild)
{
minNode = minNode->lchild;
}
(*t)->key = minNode->key;
DeleteAVL(&((*t)->rchild), minNode->key, shorter);
if (*shorter) {
switch ((*t)->bf)
{
case RH:
LeftBalance(t);
*shorter = false;
break;
case LH:
(*t)->bf = EH;
*shorter = false;
break;
case EH:
(*t)->bf = LH;
*shorter = true;
break;
}
}
}
else {
// 节点只有一个孩子或者没孩子
BSTree oldNode = *t;
*t = (*t)->lchild ? (*t)->rchild : (*t)->rchild;
free(oldNode);
*shorter = true;
}
}
return true;
}
// 查找
BSTree searchAVL(BSTree t, ElemType key) {
while (t)
{
if (t->key == key) {
return t;
}
else if (t->key < key) {
t = t->rchild;
}
else {
t = t->lchild;
}
return t;
}
}
static void destroy(BSTree* t) {
if (*t != NULL) {
destroy(&((*t)->lchild));
destroy(&((*t)->rchild));
free(*t);
*t = NULL;
}
return;
}
// 销毁AvL
void destroyAVL(BSTree root) {
if (root != NULL) {
destroy(&root);
}
return;
}
1.c
#include"AVL_tree.h"
void Test() {
BSTree root = NULL;
BSTree r;
bool taller = false;
bool shorter = false;
int arr[] = { 20,44,25,95,28 };
int length = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < length; i++)
{
// 插入AVL
InsertAVL(&root, arr[i], &taller);
}
printf("中序遍历为: \n");
InOrderTraverse(root);
printf("\n先序遍历为: \n");
PreOrderTraverse(root);
printf("\n\n请输入需要查询的元素:\n");
int num;
scanf_s("%d", &num);
printf("\n查询元素为:\n");
r = searchAVL(root, num);
if (r) {
printf("%d", r->key);
}
else {
printf("未找到该元素");
}
// 测试删除操作
DeleteAVL(&root, 24, &shorter);
printf("\ninorder traverse after deletion\n");
InOrderTraverse(root);
destroyAVL(root);
root = NULL; // 置空防止野指针
}
void Test01() {
BSTree root = NULL;
BSTree r;
bool taller = false;
bool shorter = false;
int arr[] = { 13, 24, 37, 90, 53 };
int length = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < length; i++)
{
// 插入AVL
InsertAVL(&root, arr[i], &taller);
}
printf("中序遍历为: \n");
InOrderTraverse(root);
printf("\n先序遍历为: \n");
PreOrderTraverse(root);
printf("\n\n请输入需要查询的元素:\n");
r = searchAVL(root, 37);
if (r) {
printf("%d\n", r->key);
}
else {
printf("not find!\n");
}
// 测试删除操作
DeleteAVL(&root, 24, &shorter);
printf("\ninorder traverse after deletion\n");
InOrderTraverse(root);
destroyAVL(root);
root = NULL;
}
int main() {
//Test();
Test01();
return 0;
}
测试结果: