我们希望对于二叉查找树,除MakeEmpty外的操作都花费O(logN)时间,但是,由于我们在进行删除操作时,总是用右子树中的一个节点来代替被删除元素,这就会导致在一系列删除之后,左子树的深度要明显的深于右子树,在25万次随机插入/删除之后,图中的树看起来明显的不平衡。
当树不再平衡后,那么每次操作的时间就可能不再是O(logN),这对于性能而言是有害的。一个解决方法就是:要有一个称为平衡的附加结构条件,任何节点的深度不能过深。
AVL树是一种典型的平衡二叉树。
AVL树是带有平衡条件的二叉查找树。这个平衡条件必须要容易保持,最简单的想法是使左右子树具有相同的高度,这种想法并不要求树的深度要浅,另一种想法是要求每一节点的左右子树都具有相同的高度,这个平衡条件虽然保证了树的深度浅,但是实现起来困难,所以在放宽条件后,给出了一种平衡条件,即一棵AVL树是每个节点的左右子树高度最多差1的二叉查找树,如图
而这样的树就不是一棵AVL树,因为它的右子树高度为0,而左子树的高度为2.
当插入一个新的节点时,就可能会造成平衡性的破坏,但是,总可以通过对树的一些简单操作来恢复其平衡状态,我们称这种操作为旋转。在插入以后,只有从插入节点到根节点的路径上的节点的平衡可能被改变,因为只有这些节点的子树发生了变化,当沿着这条路径更新平衡信息时,可以找到一个节点的新平衡破坏了AVL的条件(即最深的节点),在这个节点上进行旋转就可以重新平衡二叉树。
我们把破坏平衡条件的最深的那个节点称为alpha。由于任意节点最多有两个孩子,所以当不平衡时,左右子树的高度差为2,不平衡有四种情况:
1.对alpha的左儿子的左子树进行一次插入
2.对alpha的左儿子的右子树进行一次插入
3.对alpha的右儿子的左子树进行一次插入
4.对alpha的右儿子的右子树进行一次插入
情形1和4是镜像对称的,2和3是镜像对称的,但它们的实现在程序上还是四种情形。情形1和4可以看作是插入在外面的情况,可以用一次单旋转来调整。3和2可以看作是插入在内部的情况,可以用一次双旋转来调整。下面介绍单旋转和双旋转:
单旋转:
情形1如图,节点k2不满足平衡条件,那么只需将k1移动到k2的位置,并使k1的右子树成为k2的左子树,k2成为k1的右子树即可。这个过程可以想象成树是柔软的,捏住k1节点进行抖动,在重力的作用下,k1就变成了新的根。
情形4如图,节点k2不满足平衡条件,那么只需要将k1移动到k2的位置,并使k1的左子树成为k2的右子树,而k2成为k1的左子树即可。
双旋转:
情形2如图,节点k3不满足平衡条件,只需要使k2移动到k3的位置,k2的左子树成为k1的右子树,k2的右子树成为k3的左子树,k1成为k2的左子树,k3成为k2的右子树即可。
情形3如图,节点k3不满足平衡条件,只需要使k2移动到k3的位置,k2的左子树成为k3的右子树,k2的右子树成为k1的左子树,k1成为k2的右子树,k3成为k2的左子树即可。
通过实践可以发现,双旋转实际上可以由两次单旋转来实现,所以在编程中,双旋转只需要直接直接调用两次单旋转即可实现。
AVL树的具体实现:
结构定义:
typedef struct AVLTreeNode {
int val;
int height;
struct AVLTreeNode* left;
struct AVLTreeNode* right;
}AVLTreeNode;
//获得结点高度,因为要处理NULL的情况,所以需要写一个模块来完成
int GetHeight(AVLTreeNode* p) {
if (!p)
return 0;
else
return p->height;
}
旋转操作:
//左旋
AVLTreeNode* LeftSingleRotate(AVLTreeNode* root) {
AVLTreeNode* ret = root->right;
root->right = ret->left;
ret->left = root;
root->height = max(GetHeight(root->left), GetHeight(root->right)) + 1;
return ret;
}
//右旋
AVLTreeNode* RightSingleRotate(AVLTreeNode* root) {
AVLTreeNode* ret = root->left;
root->left = ret->right;
ret->right = root;
root->height = max(GetHeight(root->left), GetHeight(root->right)) + 1;
return ret;
}
//右左旋
AVLTreeNode* RightLeftDoubleRotate(AVLTreeNode* root) {
root->right = RightSingleRotate(root->right);
root = LeftSingleRotate(root);
return root;
}
//左右旋
AVLTreeNode* LeftRightDoubleRotate(AVLTreeNode* root) {
root->left = LeftSingleRotate(root->left);
root = RightSingleRotate(root);
return root;
}
插入操作:
//实现平衡二叉树的增删改查,二叉树的建立由向空树中插入实现
//假设树中不能有相等的元素
AVLTreeNode* Insert(AVLTreeNode* root, int val) {
if (root == NULL) {
AVLTreeNode* tmp = (AVLTreeNode*)malloc(sizeof(AVLTreeNode));
if (tmp == NULL) {
perror("malloc");
return NULL;
}
tmp->val = val;
tmp->left = tmp->right = NULL;
root = tmp;
}
else {
if (val == root->val) {
return root;
}
else {
if (val > root->val) {
root->right = Insert(root->right, val);
if (GetHeight(root->right) - GetHeight(root->left) == 2) {
if (val > root->right->val) {
root = LeftSingleRotate(root);
}
else {
root = RightLeftDoubleRotate(root);
}
}
}
else {
root->left = Insert(root->left, val);
if (GetHeight(root->left) - GetHeight(root->right) == 2) {
if (val < root->left->val) {
root = RightSingleRotate(root);
}
else {
root = LeftRightDoubleRotate(root);
}
}
}
}
}
root->height = max(GetHeight(root->left), GetHeight(root->right)) + 1;
return root;
}
删除操作:
//删除
//删除分为三种情况:删除结点无子树,有一个子树,有两个子树
//对于无子树和一个子树的情况,删除后并不会改变当前结点以下结点的高度,所以失衡最早发生在当前结点
//对于两个子树而言,实际上转换为删除有一个子树或者无子树的情况
//所以对于失衡的判断或高度的更新可以放在本模块结尾
AVLTreeNode* Delete(AVLTreeNode* root, int val) {
if (root == NULL) {
printf("树为空或删除元素不存在\n");
return NULL;
}
else {
if (root->val == val) {
if (root->left == NULL && root->right == NULL) {
free(root);
root = NULL;
}
else if (root->left == NULL||root->right == NULL) {
AVLTreeNode* ret = root->left ? root->left : root->right;
free(root);
root = ret;
}
else {
//找到root的中序前驱
AVLTreeNode* tmp = root->left;
while (tmp->right)
tmp = tmp->right;
root->val = tmp->val;
root->left = Delete(root->left, root->val);
}
}
else if (root->val > val) {
root->left = Delete(root->left, val);
}
else {
root->right = Delete(root->right, val);
}
}
if (root) {
//判断这个结点是否失衡
if (abs(GetHeight(root->left) - GetHeight(root->right)) == 2) {
//失衡后判断可以转换为哪种插入情况
//向左子树插入的情况
if (GetHeight(root->left) > GetHeight(root->right)) {
//判断是左左还是左右,可以根据左子树的左右子树的高度判断,当相等时认为是左左,因为单旋转效率较高
if (GetHeight(root->left->left) >= GetHeight(root->left->right)) {
root = RightSingleRotate(root);
}
else {
root = LeftRightDoubleRotate(root);
}
}
else {
//判断是右右还是右左
if (GetHeight(root->right->right) >= GetHeight(root->right->left)) {
root = LeftSingleRotate(root);
}
else {
root = RightLeftDoubleRotate(root);
}
}
}
root->height = max(GetHeight(root->left), GetHeight(root->right)) + 1;
}
return root;
}
根据关键字查找某个结点:
//查找某个值的结点
AVLTreeNode* FindKey(AVLTreeNode* root, int key) {
if (!root) {
printf("树为空或元素不存在\n");
return NULL;
}
else {
if (root->val == key) {
return root;
}
else if (root->val > key) {
return FindKey(root->left, key);
}
else {
return FindKey(root->right, key);
}
}
}
测试代码:
#include <stdio.h>
#include <stdlib.h>
//中序遍历
void InOrderPrint(AVLTreeNode* root) {
if (root) {
InOrderPrint(root->left);
printf("%d ", root->val);
InOrderPrint(root->right);
}
}
int main() {
AVLTreeNode* root = NULL;
for (int i = 0; i <= 10; i++) {
root = Insert(root, i);
}
for (int i = 0; i <= 10; i++) {
printf("查找%d的结果为:%d\n", i, FindKey(root, i)->val);
}
/*for (int i = 0; i <= 10; i++) {
root = Delete(root, i);
InOrderPrint(root);
printf("\n----------------------\n");
}*/
return 0;
}