一、定义:
我们知道二叉查找树,有很强的排序、查找、删除、插入的能力,但是,随着插入和删除的次数变多,二叉查找树的工作的效率有可能的变得没那么好了,因为二叉查找树的工作效率依赖于树的高度,树越高效率越差(同样多的结点的情况下),因此,就需要尽可能地降低树的高度。引入AVL树的目的就是为了提高二叉查找树的效率,在每次向二叉树插入或删除结点的时候,尽可能地使二叉查找树保持平衡,平衡状态下树的高度是最低的。
维基百科:
上图给出了,AVL树的概念和平衡因子的概念(左子树的高度-右子树的高度)
二、分析:
首先空树是AVL树,当我们先AVL树插入或删除结点的AVL树可能会失去平衡,AVL树失去平衡的情况有以下四种:
1、左左(LL
)不平衡情况:
在根结点的较高的左子树的左子树上插入了结点,使得根的平衡因子大于1
而失去平衡,或者是在删除一个结点后,使得原本就更高的左子树的左边更高,这种情况要作右单旋(右边矮做右单旋)操作
以下假设T1、T2、T3
是高度相同的满二叉树:
y y x
/ \ / \ Right Rotation / \
x T3 --------> x T3 - - - - - - - > T1 y
/ \ insert O / \ / / \
T1 T2 T1 T2 O T2 T3
/
O
Node *x = y->left;
y->left = x->right;
x->right = y;
2、右右(RR
)不平衡情况:
这个和LL
对称,它是原较高的右子树的右子树更高了,这种情况要进行左单旋(左边矮做左单旋)操作
以下假设T1、T2、T3
是高度相同的满二叉树:
y x x
/ \ / \ / \
x T3 T1 y <------- T1 y
/ \ \ < - - - - - - - / \ insert O / \
T1 T2 o Left Rotation T2 T3 T2 T3
\
O
Node* y = x->right;
x->right = y->left;
y->left = x;
3、左右(LR
)不平衡情况:
原本较高的左子树(断句)的右子树高度更高了,这种情况要进行先左单,后右单的双旋操作
以下假设T1、T4
高度为h
、T2、T3
的高度h-1
:
z z z x
/ \ / \ / \ / \
y T4 -------> y T4 Left Rotate (y) x T4 Right Rotate(z) y z
/ \ insert O / \ - - - - - - - - -> / \ - - - - - - - -> / \ / \
T1 x T1 x y T3 T1 T2 T3 T4
/ \ / \ / \ /
T2 T3 T2 T3 T1 T2 O
/ /
O O
上面图
假设在以z
为根较高的左子树y
的右子树x
上插入一个结点O
(x
的左右子女上都可以),首先y
结点的平衡因子为-2
,失去平衡,这里先做个左单旋,做完后(不管做不做)z
的平衡因子为2
,要做个右单旋。
代码类似于:
leftRotation(y);
rightRotation(z);
因为y是z的左子女,所以可以写成:
z->left = leftRotation(z->left);
return rightRotation(z);
leftRotation
和rightRotation
返回调整后的根结点。
4、RL
不平衡情况:和LR
不平衡对称
在原本较高的右子树(断句)的左子树高度更高了导致的不平衡,要先右单旋后左单旋。
以下假设T1、T4
高度为h
、T2、T3
的高度h-1
:
z z z x
/ \ / \ / \ / \
T1 y ----------> T1 y Right Rotate (y) T1 x Left Rotate(z) z y
/ \ insert O / \ - - - - - - - - -> / \ - - - - - - - -> / \ / \
x T4 x T4 T2 y T1 T2 T3 T4
/ \ / \ / / \ /
T2 T3 T2 T3 O T3 T4 O
/
O
代码类似于:
z->left = rightRotation(z->left);
return leftRotation(z);
在插入或删除某个结点后,只有出现了这四种情况会导致AVL树暂时失去平衡,而相应的解决办法也给出了。因此,在代码实现的时候,每次插入删除的时候,都要从插入删除位置沿双亲位置向上回溯,检查以每个双亲为根时AVL树有没有失去平衡,失去了是哪种情况就对的就用那种方法调整。所以插入删除的时候可以用递归的方法来实现,插入和删除一个结点后就回溯到上一层,检查,然后继续回溯,直到没问题的时候。
小总结:
假设定义以自底向上第一个平衡因子为2或者-2
为根的树为最小不平衡AVL树。那么它不平衡的原因就四种:
设根为root
,这四种情况是:
不平衡情况 | 说明 | 平衡因子特点 | 解决办法 |
---|---|---|---|
LL | root的左子树比右子树高2,并且左子树的左子树比右子树高1或相等(插入时不会,删除时可能会) | 此时root的平衡因子为2,左子树的平衡因子为1或0 | 右单旋 |
LR | root的左子树比右子树高2,并且左子树的右子树比左子树高1(其实相等也可以,但我把相等的情况放上面去了) | 此时root的平衡因子为2,左子树的平衡因子为-1 | 先左单旋后右单旋 |
RR | root的右子树比左子树高2,并且右子树的右子树比左子树高1或相等(与LL同理) | 此时,root的平衡因子为-2,右子树的平衡因子为-1或0 | 左单旋 |
RL | root的右子树比左子树高2,并且右子树的左子树比右子树高1 | 此时,root的平衡因子为-2,root的右子树平衡因子为1 | 先右单旋后左单旋 |
这个表很重要,插入和删除时就是根据这个来调整不平衡的。
左边低左单旋,右边低右单旋。这个自己理解理解图就好。
三、代码实现:
1、结点结构:
class Node
{
public:
int key;
int height;
Node *left,*right;
Node(int _key)
{
key = _key;
height = 1;
left = right = NULL;
}
};
2、树的高度:
int height(Node *N)
{
if(N == NULL)
{
return 0;
}
return N->height;
}
3、max函数:返回大的值
int max(int x,int y)
{
return x > y ? x : y;
}
4、计算以某结点为根的平衡因子:左子树的高度-右子树的高度
int getBalance(Node *root) //获得以root为根的子树的平衡因子
{
if(root == NULL)
{
return 0;
}
return height(root->left) - height(root->right);
}
5、左单旋:
Node* leftRotation(Node *x)
{//左单旋(左边矮),又称RR旋转(在较高的右子树的右子树插入结点导致失去平衡 )
Node* y = x->right;
x->right = y->left;
y->left = x;
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return y; //返回调整后的根结点
}
6、右单旋:
Node* rightRotation(Node *y)
{ //右单旋(右边矮),又称LL旋转(在较高的左子树的左子树插入结点导致失去平衡)
Node *x = y->left;
y->left = x->right;
x->right = y;
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x; //返回调整后的根结点
}
7、插入:
插入完后执行以下步骤:
1)当前节点如果是插入结点的祖先之一,那么要更新当前结点的高度。
2)获取当前结点的平衡因子(左子树的高度–右子树的高度)。
3)如果平衡因子大于1,则当前结点不平衡,我们处于“左左”情况或“左右”情况。要检查是左左情况还是左右情况,请获取左子树的平衡因子。如果左子树的平衡因子大于或等于0,则为LL
情况[因为大于等于0说明左子树的左子树的高度大于或等于左子树的右子树的高度,大于0时是
LL情况没问题,等于0时,为方面当作LL情况来处理(LL一次左单旋就够了,LR得先左旋后右旋)]
,否则为LR
情况。下同
4)如果平衡因子小于-1,则当前节点不平衡,我们处于“右右”情况或“右左”情况。要检查是右右情况还是右左情况,请获取右子树的平衡因子。如果右子树的平衡因子小于或等于0,则为RR
情况,否则为RL
情况。
Node* insert(Node *root,int key)
{
if(root == NULL)
{
return new Node(key);
}
if(key < root->key)
{
root->left = insert(root->left, key);
}
else if(key > root->key)
{
root->right = insert(root->right, key);
}
else //key == root->key不插入
{
return root;
}
root->height = 1 + max(height(root->left),height(root->right));
int balance = getBalance(root); //检查平衡因子
//LL情况 -->右单旋
if(balance > 1 && getBalance(root->left) >= 0)
{//LL ---> 右单旋
return rightRotation(root);
}
if(balance > 1 && getBalance(root->left) < 0)
{//LR ----->先左单旋后右单旋
root->left = leftRotation(root->left);
return rightRotation(root);
}
if(balance < -1 && getBalance(root->right) <= 0)
{//RR---->左单旋
return leftRotation(root);
}
if(balance < -1 && getBalance(root->right) > 0)
{//RL---->先右单旋后左单旋
root->right = rightRotation(root->right);
return leftRotation(root);
}
return root; //没有失去平衡
}
8、删除:这个和二叉查找树一样,当有待删结点有两个子女时,要把它转换成删一个子女的情况(找右子树下值关键码最小的结点来代替被删(中序后继),或者左子树下关键码最大的(中序前继))。
删完后还有以下步骤:
1)当前节点如果是已删除结点的祖先之一,那么要更新当前结点的高度。
2)获取当前结点的平衡因子(左子树的高度–右子树的高度)。
3)如果平衡因子大于1,则当前结点不平衡,我们处于“左左”情况或“左右”情况。要检查是左左情况还是左右情况,请获取左子树的平衡因子。如果左子树的平衡因子大于或等于0,则为LL
情况[因为大于等于0说明左子树的左子树的高度大于或等于左子树的右子树的高度,大于0时是
LL情况没问题,等于0时,为方面当作LL情况来处理(LL一次左单旋就够了,LR得先左旋后右旋)]
,否则为LR
情况。下同
4)如果平衡因子小于-1,则当前节点不平衡,我们处于“右右”情况或“右左”情况。要检查是右右情况还是右左情况,请获取右子树的平衡因子。如果右子树的平衡因子小于或等于0,则为RR
情况,否则为RL
情况。
Node *minValueNode(Node *node)
{//中序右子树下第一个结点(右子树下key值最小)
Node *current = node;
while(current->left != NULL)
{
current = current->left;
}
return current;
}
Node* deleteNode(Node* root,int key)
{
if(root == NULL)
{
return root;
}
if(key < root->key)
{
root->left = deleteNode(root->left, key);
}
else if(key > root->key)
{
root->right = deleteNode(root->right, key);
}
else
{
if((root->left == NULL) || (root->right == NULL))
{
Node* temp = root->left?
root->left:
root->right;
if(temp == NULL) //如果没有子女
{
temp = root; //temp 保存要删除结点
root = NULL; // root 置空
}
else //有一个子女
{
*root = *temp; //把temp的值拷给root,temp代替被删
}
delete temp;
}
else //如果有两个子女-->找个只有一个子女或者没有子女的子孙,
{ //把它的值存到当前结点,然后把这个子孙删了
Node *temp = minValueNode(root->right);
root->key = temp->key;
root->right = deleteNode(root->right,
temp->key);
}
}
//删完后root == NULL就不用检查以root为根的子树平不平衡了
if(root == NULL)
{
return root;
}
root->height = 1 + max(height(root->left),
height(root->right));
int balance = getBalance(root);
if(balance > 1 && getBalance(root->left) >= 0)
{//LL ---> 右单旋
return rightRotation(root);
}
if(balance > 1 && getBalance(root->left) < 0)
{//LR ----->先左单旋后右单旋
root->left = leftRotation(root->left);
return rightRotation(root);
}
if(balance < -1 && getBalance(root->right) <= 0)
{//RR---->左单旋
return leftRotation(root);
}
if(balance < -1 && getBalance(root->right) > 0)
{//RL---->先右单旋后左单旋
root->right = rightRotation(root->right);
return leftRotation(root);
}
return root; //返回删完后调整后的根
}
9、测试代码:
#include<iostream>
#include<vector>
#include<string>
using namespace std;
class Node
{
public:
int key;
int height;
Node *left,*right;
Node(int _key)
{
key = _key;
height = 1;
left = right = NULL;
}
};
int height(Node *N)
{
if(N == NULL)
{
return 0;
}
return N->height;
}
int max(int x,int y)
{
return (x > y )? x : y;
}
int getBalance(Node *root) //获得以root为根的子树的平衡因子
{
if(root == NULL)
{
return 0;
}
return height(root->left) - height(root->right);
}
/*
y x
/ \ Right Rotation / \
x T3 - - - - - - - > T1 y
/ \ < - - - - - - - / \
T1 T2 Left Rotation T2 T3
*/
Node* leftRotation(Node *x)
{//左单旋(左边矮),又称RR旋转(在较高的右子树的右子树插入结点导致失去平衡 )
Node* y = x->right;
x->right = y->left;
y->left = x;
//更新x,y的高度,必须先x,后y,因为x是y的子树
x->height = max(height(x->left), height(x->right)) + 1;
y->height = max(height(y->left), height(y->right)) + 1;
return y; //返回调整后的根结点
}
Node* rightRotation(Node *y)
{ //右单旋(右边矮),又称LL旋转(在较高的左子树的左子树插入结点导致失去平衡)
Node *x = y->left;
y->left = x->right;
x->right = y;
//更新x,y的高度,必须先y,后x,因为y是x的子树
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x; //返回调整后的根结点
}
Node* insert(Node *root,int key)
{
if(root == NULL)
{
return new Node(key);
}
if(key < root->key)
{
root->left = insert(root->left, key);
}
else if(key > root->key)
{
root->right = insert(root->right, key);
}
else //key == root->key不插入
{
return root;
}
root->height = 1 + max(height(root->left),height(root->right));
int balance = getBalance(root); //检查平衡因子
//LL情况 -->右单旋
if(balance > 1 && getBalance(root->left) >= 0)
{//LL ---> 右单旋
return rightRotation(root);
}
if(balance > 1 && getBalance(root->left) < 0)
{//LR ----->先左单旋后右单旋
root->left = leftRotation(root->left);
return rightRotation(root);
}
if(balance < -1 && getBalance(root->right) <= 0)
{//RR---->左单旋
return leftRotation(root);
}
if(balance < -1 && getBalance(root->right) > 0)
{//RL---->先右单旋后左单旋
root->right = rightRotation(root->right);
return leftRotation(root);
}
return root; //没有失去平衡
}
Node *minValueNode(Node *node)
{//中序右子树下第一个结点(右子树下key值最小)
Node *current = node;
while(current->left != NULL)
{
current = current->left;
}
return current;
}
Node* deleteNode(Node* root,int key)
{
if(root == NULL)
{
return root;
}
if(key < root->key)
{
root->left = deleteNode(root->left, key);
}
else if(key > root->key)
{
root->right = deleteNode(root->right, key);
}
else
{
if((root->left == NULL) || (root->right == NULL))
{
Node* temp = root->left?
root->left:
root->right;
if(temp == NULL) //如果没有子女
{
temp = root; //temp 保存要删除结点
root = NULL; // root 置空
}
else //有一个子女
{
*root = *temp; //把temp的值拷给root,temp代替被删
}
delete temp;
}
else //如果有两个子女-->找个只有一个子女或者没有子女的子孙,
{ //把它的值存到当前结点,然后把这个子孙删了
Node *temp = minValueNode(root->right);
root->key = temp->key;
root->right = deleteNode(root->right,
temp->key);
}
}
//删完后root == NULL就不用检查以root为根的子树平不平衡了
if(root == NULL)
{
return root;
}
root->height = 1 + max(height(root->left),
height(root->right));
int balance = getBalance(root);
if(balance > 1 && getBalance(root->left) >= 0)
{//LL ---> 右单旋
return rightRotation(root);
}
if(balance > 1 && getBalance(root->left) < 0)
{//LR ----->先左单旋后右单旋
root->left = leftRotation(root->left);
return rightRotation(root);
}
if(balance < -1 && getBalance(root->right) <= 0)
{//RR---->左单旋
return leftRotation(root);
}
if(balance < -1 && getBalance(root->right) > 0)
{//RL---->先右单旋后左单旋
root->right = rightRotation(root->right);
return leftRotation(root);
}
return root;
}
void printBST(Node *root, int k)
{
if(root != NULL)
{
printBST(root->right,k+5);
for(int i = 0; i < k; i++)
{
cout<<" ";
}
cout<<root->key<<endl;
printBST(root->left,k+5);
}
}
int main()
{
Node *root = NULL;
root = insert(root,10);
root = insert(root,5);
root = insert(root,11);
root = insert(root,3);
root = insert(root,6);
root = insert(root,12);
root = insert(root,2);
root = insert(root,9);
printBST(root,0);
cout<<"------LL---------"<<endl;
root = deleteNode(root,12);
printBST(root,0);
return 0;
}
结果图:
特意找个删除后是root的左子树的平衡结点是0(左子树的左右子树高度相等),当LL
处理没问题。
写完了,刚了3个下午应该算搞懂了。如果哪里错了,还请各位指出哦!