前言
AVL树是一种自平衡的二叉搜索树,它在插入和删除操作后能够通过旋转操作来保持树的平衡。AVL树是由苏联数学家G.M. Adelson-Velsky和E.M. Landis于1962年提出的,其名称即来源于他们的姓氏的首字母。
AVL树的基本特性
-
平衡因子
在AVL树中,每个节点都存储一个值,并包含左子树和右子树。对于每个节点,它的左子树和右子树的高度差称为平衡因子,要么为0,要么为1或-1。
以下是平衡二叉树示例:
当高度差超过允许范围时,二叉树将不平衡:
- 平衡调整:当插入或删除节点导致某个节点的平衡因子超过允许范围时,AVL树会通过旋转操作来恢复树的平衡。
- 自平衡性:AVL树中的每个节点都会经过平衡调整,从而使得整棵树始终保持平衡状态。
旋转操作
当插入或删除节点时,AVL树会根据平衡因子的变化情况选择适当的旋转操作来调整树的结构。
AVL树的旋转操作包括四种情况:
左旋(LL旋转):当一个节点的左子树高度较大时,在该节点的左子树上进行左旋转。
左旋是将一个节点的右子树提升为根节点,并将原根节点降为左子节点的过程。
实现步骤:
- 将当前节点的右子节点设为新的根节点。
- 将新根节点的左子树设为当前节点的右子树。
- 将当前节点设为新根节点的左子树。
右旋(RR旋转):当一个节点的右子树高度较大时,在该节点的右子树上进行右旋转。
右旋是将一个节点的左子树提升为根节点,并将原根节点降为右子节点的过程。
实现步骤:
- 将当前节点的左子节点设为新的根节点。
- 将新根节点的右子树设为当前节点的左子树。
- 将当前节点设为新根节点的右子树。
先左后右旋(LR旋转):当一个节点的左子树高度较大,并且该节点的左子节点的右子树高度较大时,在该节点的左子树上先进行左旋转,然后在原节点上进行右旋转。
先左后右旋是通过对节点进行两次旋转操作来保持平衡,即先进行一次左旋,然后再进行一次右旋。
实现步骤:
- 对当前节点的左子树进行左旋操作。
- 对当前节点进行右旋操作。
先右后左旋(RL旋转):当一个节点的右子树高度较大,并且该节点的右子节点的左子树高度较大时,在该节点的右子树上先进行右旋转,然后在原节点上进行左旋转。
先右后左旋是通过对节点进行两次旋转操作来保持平衡,即先进行一次右旋,然后再进行一次左旋。
实现步骤:
- 对当前节点的右子树进行右旋操作。
- 对当前节点进行左旋操作。
AVL树的实现
AVL树是在二叉搜索树的基础上实现的,其操作大同小异,只是AVL的节点存在高度,如何保持树的平衡是其关键。
-
AVL树储存结构
//AVl树节点结构体
typedef struct Node {
int data;//数据
struct Node* left;//左孩子指针
struct Node* right;//右孩子指针
int height;//节点高度
}Node;
-
获得节点高度
//获得节点高度
int GetHeight(Node* node) {
if (node == NULL)
return 0;
return node->height;
}
-
计算平衡因子
//计算节点平衡因子
int GetBF(Node* node) {
if (node == NULL)
return 0;
//左子树高度减右子树高度
return GetHeight(node->left) - GetHeight(node->right);
}
-
更新节点高度
//更新节点高度
void UpdatHeight(Node* node) {
int leftheight = GetHeight(node->left);//左子树高度
int rightheight = GetHeight(node->right);//右子树高度
//取高度大的那个+1
node->height = (leftheight > rightheight ? leftheight : rightheight) + 1;
}
-
创建节点
//创建一个新节点
Node* CreateNode(int data) {
Node* newnode = malloc(sizeof(Node));
newnode->data = data;
newnode->height = 1;
newnode->left = NULL;
newnode->right = NULL;
return newnode;
}
-
左旋右旋
//右旋操作
Node* RotateRight(Node* y) {
Node* x = y->left;//x指向当前节点y的左子树
Node* t = x->right;//t指向x的右子树
x->right = y;//y作为x右子树
y->left = t;//x原来的右子树作为y左子树
//调整高度
UpdatHeight(x);
UpdatHeight(y);
return x;//x节点成为根节点
}
//左旋操作
Node* RotateLeft(Node* x) {
Node* y = x->right;//y指向x右子树
Node* t = y->left;//t指向y左子树
y->left = x;//x作为y的左子树
x->right = t;//y原来的左子树作为x的右子树
//调整高度
UpdatHeight(x);
UpdatHeight(y);
return y;//y成为根节点
}
-
插入
//插入节点
Node* InsertNode(Node* node, int data) {
//若当前节点为空,创建一个节点
if (node == NULL) {
return CreateNode(data);
}
//插入值小于当前节点,插入到左子树
if (data < node->data) {
node->left = InsertNode(node->left, data);
}
//插入值大于当前节点,插入到右子树
else if (data > node->data) {
node->right = InsertNode(node->right, data);
}
//值相等,不插入
else {
return node;
}
//更新节点高度
UpdatHeight(node);
//平衡调整
//获得平衡因子
int BF = GetBF(node);
//元素插入左孩子左子树,LL情况,右旋
if (BF > 1 && data < node->data) {
return RotateRight(node);
}
//元素插入右孩子右子树,RR情况,左旋
if (BF < -1 && data>node->data) {
return RotateLeft(node);
}
//元素插入左孩子右子树,LR情况,先对左子树左旋,再对根节点右旋
if (BF > 1 && data > node->data) {
node->left = RotateLeft(node->left);
return RotateRight(node);
}
//元素插入右孩子左子树,RL情况,先对右子树右旋,再对根节点左旋
if (BF < -1 && data < node->data) {
node->right = RotateRight(node->right);
return RotateLeft(node);
}
return node;
}
-
查找最小值
//查找最小值节点
Node* MinValNode(Node* node) {
Node* t = node;
while (t->left != NULL)
t = t->left;
return t;
}
-
查找
//查找节点
Node* searchnode(Node* root, int data) {
//节点为空或找到值返回节点
if (root == NULL || root->data == data) {
return root;
}
//查找值比当前节点小,继续查找左子树
if (data < root->data) {
return searchnode(root->left, data);
}
//继续查找右子树
return searchnode(root->right, data);
}
-
删除
//删除节点
Node* DeleteNode(Node* root, int data) {
//若未找到要删除节点
if (root == NULL) {
return root;
}
//查找左子树
if (data < root->data) {
root->left = DeleteNode(root->left, data);
}
//查找右子树
else if (data > root->data) {
root->right = DeleteNode(root->right, data);
}
//若找到
else {
//若要删除节点最多只有一个子树
if (root->left == NULL || root->right == NULL) {
Node* temp = root->left ? root->left : root->right;
if (temp == NULL) {
temp = root;
root = NULL;
}
else {
*root = *temp;
}
free(temp);
}
//若要删除节点有两个子树
else {
//找到右子树最小的孩子作为根节点
Node* temp = MinValNode(root->right);
root->data = temp->data;//复制数据
root->right = DeleteNode(root->right, temp->data);//删除该右孩子
}
}
//若根节点为空,直接返回
if (root == NULL) {
return root;
}
//else
//更新节点高度
UpdatHeight(root);
//平衡调整
int BF = GetBF(root);
//LL情况,右旋
if (BF > 1 && data < root->data) {
return RotateRight(root);
}
//RR情况,左旋
if (BF < -1 && data>root->data) {
return RotateLeft(root);
}
//LR情况,先对左子树左旋,再对根节点右旋
if (BF > 1 && data > root->data) {
root->left = RotateLeft(root->left);
return RotateRight(root);
}
//RL情况,先对右子树右旋,再对根节点左旋
if (BF < -1 && data < root->data) {
root->right = RotateRight(root->right);
return RotateLeft(root);
}
return root;//返回该节点
}
-
释放内存
//释放节点内存
void freenode(Node* node) {
if (node == NULL) {
return;
}
freenode(node->left);//递归释放左子树内存
freenode(node->right);//递归释放右子树内存
free(node);
}
总结
AVL树适合用于需要高效查找、删除和删除操作,并且对数据的平衡性要求较高的场景。
AVL树虽然具有自平衡的优点,但也存在一些缺点:
- 需要频繁的平衡操作:为了维持树的平衡性,AVL树在每次插入或删除节点后都需要进行旋转操作。这可能导致频繁的调整和旋转,增加了操作的时间复杂度和开销。
- 更多的空间需求:相比于非平衡二叉搜索树,AVL树需要额外的平衡因子(通常是一个整数)来维护每个节点的平衡因子值。这种额外的存储需求会占用更多的内存空间。
- 插入和删除操作相对耗时:由于AVL树需要保持严格的平衡,插入和删除操作可能涉及多次的旋转操作,而这些旋转操作需要更新大量节点上的平衡因子。相对于其他非严格平衡的树结构,AVL树的插入和删除操作通常更加耗时。
- 对局部性的不利影响:AVL树的平衡操作会导致整棵树的结构发生变化,这可能会破坏原本的局部性(locality),影响缓存的命中率。在某些情况下,这可能导致性能下降。
- 限制性的平衡要求:AVL树要求左右子树的高度差不超过1,这种严格的平衡要求在某些场景下可能会导致树的高度相对较高,增加了查找操作的时间复杂度。
需要根据具体的应用场景和需求来选择合适的数据结构,考虑到数据集的特征、操作频率以及对性能的要求。如果不需要绝对的平衡性,其他自平衡二叉搜索树(如红黑树)可能是更好的选择。