1.AVL树简介
AVL树是一种自平衡的二叉搜索树,一颗典型的二叉搜索树(Binary Search Tree,以下简称BST)应该满足Key(left) < Key(root) < Key(right)。即左子树上所有的键值都小于根节点的键值,根节点的键值小于所有右子树上的键值(左右子树都不为空的话)。也正是因为这种特性,如果对于一般的BST不做任何平衡操作的话,在构建BST的时候左右子树的高度会出现严重不均衡的情况,例如:按照升序(降序)输入序列会造成最极端的不平衡情况。
图1 升序构建的BST 图2 降序构建的BST 图3 平衡的BST
1.1一些概念
为了解决这种情况,平衡的BST概念就应运而生了。其中AVL树是最先发明的自平衡二叉搜索树。自平衡的概念就是在插入、删除的过程中,树会自动平衡自身。在这里我们需要规定一下平衡的概念。一颗平衡二叉树,它的左右子树的高度差不能超过1,这个性质应用到这颗树的任意一个节点都要成立。树的高度的概念:从根节点出发,寻找到离它最远的叶节点所经过的路径,如果一个节点为空,它的高度为-1。高度的概念也可以应用到每一个节点。对于图1,节点1的高度为2,节点2的高度为1,节点3的高度为0.对于图3,节点1和节点3的高度都为0,节点2的高度为1。由此我们又可以引申出节点的平衡因子这个概念。一个节点的平衡因子是:height(x->left)-height(x->right),这里就不再举例细说了。
2.具体操作
2.1左旋、右旋、平衡
AVL树最重要的操作其实说穿了只有两个:左旋和右旋。我们所说的左旋右旋是针对某个节点而言。对于上文图1,将节点1向左旋转就形成了图3,对于图2 ,将节点3进行右旋就形成了图3。当某一个节点的平衡因子大于1时,要将该节点进行右旋;当某一节点的平衡因子小于-1时,要将该节点进行左旋。这里说法不够严谨,具体细节我们到平衡操作时再细说。平衡因子大于1,说明左子树高度至少比右子树高度大2,小于-1,右子树高度比左子树至少大2。具体旋转应该怎么操作呢,我们来看一个例子。
图4 左子树高的不平衡BST
图5 右旋后平衡的BST
对比上面两幅图,我们发现不平衡发生在节点x,它的平衡因子为2,根据上文所说,那么从大方向上而言,它需要进行右旋。右旋过后如图5所示,我们发现,前后两颗树,只有被标记的三个节点的相对位置发生了变化,其他节点相对位置没有变化。用代码来表示就是
/*经由下面的3个步骤,原先x的左节点变为了新树的根节点,原先的x节点变为了新树左节点,原先x左节点的右节点变成了x节点的左孩子,还请好好理解*/
Node* t = x->left;
x->left = t->right;
t->right = x;
右旋操作就是这样,左旋操作很类似,我们以同样的方式来举例说明。
图6 右子树高的不平衡BST(图画的有点问题,x的balance=-2,x->right的balance=-1)
图7 左旋后平衡的BST
同样的,左旋时,相对位置发生变化的也只有被标记出来的3个节点。所以用代码来表示就是
Node* t = x->right;
x->right = t->left;
t->left = x;
和右旋的代码结构一模一样,只不过是对称了一下而已。
理解了左旋和右旋,那我们再来理解平衡就简单很多了。大多数讲解AVL树平衡的资料都讲平衡(旋转)操作分为了4类,LL型,LR型,RL型,RR型。我这里只想分为左旋和右旋两大类来介绍。其实和那4类是一样的。
第一大类:右旋
根据我们上文所讲,当一个节点的平衡因子大于1的时候,它需要进行右旋才能平衡。但是我们没有考虑该节点(x)的左孩子节点(x->left)的平衡因子,如果balanceFactor(x->left) > 0,那么这就对应着图4 所描述的情况,这种情况下,我们只需将x节点进行一次右旋就能得到平衡的BST了。
那如果balanceFactor(x->left) < 0呢,这时候只对x节点进行一次右旋就能平衡了吗?我们不妨来看一看。
图8 balanceFactor(x->left) < 0的不平衡BST
对于这样的不平衡二叉树,如果只对x节点进行一次右旋将会导致什么结果呢?试一试
图9 只进行一次右旋后形成的BST
只通过一次右旋,我们可以很清楚地看到新的树仍然是不平衡的,新树的根节点的balance = -2。可能有人有疑问了,因为我们上文提到过,当一个节点的平衡因子小于-1时,需要对它进行左旋才能平衡,那么对于这样的一棵树再进行一次左旋能否平衡呢?答案是不能,因为如果再将新的根节点进行左旋,就会得到原先的不平衡的BST,还是无法平衡。这颗树的平衡操作我们在讨论左旋时再具体解释,现在我们着重关心一下,对于图8所示的不平衡的BST,如何让它平衡呢?
正确的操作应该是先将x->left进行一次左旋,这样就形成了如同图4一样的BST,之后再将x节点进行一次右旋,这就平衡了。我们一步一步来看看,首先将x->left进行一次左旋。
图10 先进行一次左旋后形成的BST
然后再将根节点进行一次右旋
图11 第二次进行右旋后形成的平衡的BST
至此,关于右旋的平衡我们就讨论完了。
第二大类:左旋
有了右旋平衡的基础,再讨论左旋平衡思路就清晰很多了。
同理,我们提到过如果一个节点的平衡因子小于-1,就应该对它进行左旋,但是我们没有考虑该节点(x)的右孩子节点(x->right)的平衡因子,如果balanceFactor(x->right) < 0,这种情况对应的就是图6所描述的情况,我们只需要进行一次左旋即可。
如果balanceFactor(x->right) > 0呢?这种情况就是形同图9所示的树。根据我们对右旋平衡的讨论,我们可以很容易地推测出,如果再这种情况下还是只进行一次左旋操作的话,将得到形同图8所示的树。
正确的操作应该是先将x->right进行一次右旋,然后对新树的根节点进行一次左旋就能得到一颗平衡的BST了。这里我就不给出具体的图示了,如果大家能够理解关于右旋平衡的讨论,那么左旋平衡应该不难自己画图理解。
如果我们事先已经实现了左旋和右旋的函数(实际的代码中确实会事先实现这两个函数),那么我们的balance函数可以这么写:
Node* balance(Node* x){
if(balanceFactor(x) < -1){ //balanceFactor函数用来取得一个节点的平衡因子
if(balanceFactor(x->right) > 0){
x->right = rotateRight(x->right);
}
x = rotateLeft(x);
}
else if(balanceFactor(x) > 1){
if(balanceFactor(x->left) < 0){
x->left = rotateLeft(x->left);
}
x = rotateRight(x);
}
return x;
}
3.插入
有了上述关于平衡的理解,再谈插入就很简单了。我们put函数是这样声明的。Node* put(Node* x, Key key,Value val);当新插入一个键值对时,会和当前节点比较。如果要插入的键值较小,就递归地在左子树中插入;如果较大,就递归地在右子树中插入;如果相等,就直接改变当前节点的val即可;如果当前节点为空,直接返回一个新节点;最后调用balance函数进行平衡。忘了说一句,我所实现的AVL树的节点是存储的一对键值对,用键值来比较。由于递归,就实现了AVL树自平衡性的性质。
图12 一个简单的插入例子
代码表示
Nodee* put(Node* x, Key key, Value val){
//如果为空,直接创建一个新节点返回
if(x == NULL) return new Node(key,val,0,1);
if(key < x->key) x->left = put(x->left,key,val); //递归
else if(x->key < key) x->right = put(x->right,key,val); //递归
else {x->val = val; return x;}
//更新大小和高度
x->N = 1+size(x->left) + size(x->right);
x->h = 1+max(height(x->left),height(x->right));
return balance(x); //平衡
}
4.删除
删除的情况要稍微复杂一点。不过如果了解二叉搜索树的删除原理,那么AVL树的删除也就不难理解了,唯一的区别就是多了一个平衡操作。如果要删除的节点只有一个子节点或者没有子节点,那么这种情况是很简单的,直接返回该节点的另一个子节点就行(如果没有子节点就直接返回了NULL)。现在假定我们有如下所示的一颗AVL树。
图13 一颗AVL树
1.假定我们要删除键值为A或者键值为E的节点,这两个节点的共同之处在于它们都只有一个子节点。要删除它们很简单,只需要将原先指向它们的连接重新指向它们的另一个非空的孩子节点即可。(叶节点的删除包含在这两种情况中了,不再单独讨论)如下图
图14 删除两个节点后的AVL树
原理很简单,代码实现也很简单,删除后再进行balance操作,对于这个例子,删除后仍然是AVL树,所以不用平衡了。
2.如果我们删除的节点包含两个子节点那该怎么办呢?例如,要删除键值为I的节点。其实这个二叉搜索树的删除方法一样,我们在要删除的节点的右孩子节点中寻找到一个在右子树中键值最小的节点,把该节点用最小的那个节点替代。在这个例子中,就是把键值为I的节点用键值为J 的节点替代。这样做的原因是要维护树的有序性。因为是在右子树中找的节点,所以这个节点肯定比原先节点的左子树中的所有的节点的键值都要大,又因为是右子树中键值最小的节点,所以剩下的右子树的所有节点的键值都比这个节点的键值要大。这个应该不难理解。寻找到最小节点很容易,那么怎么删除呢?其实删除最小节点可以归类到第一种情况中,因为含有最小键值的节点最多只有一个右孩子节点(原因大家可以花几秒钟考虑一下)。所以这就相当于删除一个叶节点或者删除一个只含有右孩子节点的节点。在第一种情况中我们已经分析过了。删除后的树如下图:
图15 再删除一个含有两个子节点的节点
最后不要忘了对该节点进行balance操作,我们给的例子删除后不需要balance,但是实际上删除节点很有可能会破坏树的平衡性。删除的代码如下:
Node* deleteMin(Node* x){
if(x->left == NULL) return x->right;
x->left = deleteMin(x->left);
x->N = 1+size(x->left) + size(x->right);
x->h = 1 + max(height(x->left),height(x->right));
return balance(x);
}
Node* deleteKey(Node* x,Key key){
if(x == NULL) return NULL;
if(key < x->key)
x->left = deleteKey(x->left,key); //递归删除
else if(x->key < key){
x->right = deleteKey(x->right,key); //递归删除
}
else{
//只有右节点
if(x->left == NULL){
return x->right;
}
//只有左节点
else if(x->right == NULL){
return x->left;
}
//有两个孩子节点
else{
Node* t = x;
x = min(t->right); //找到右子树中键值最小的节点
x->right = deleteMin(t->right); //把那个最小的节点删除
x->left = t->left; //和上一个步骤一起完成了替换操作
}
}
x->N = 1+size(x->left) + size(x->right);
x->h = 1 + max(height(x->left),height(x->right));
return balance(x);
}
代码和算法思想大部分都是参考《算法》(第4版),但是这本书并没有给出AVL树的实现,只实现了红黑树,不过配套的教学网站上给出了AVL树的实现。下面是完整代码:
#include
#include
using namespace std;
template >
class Node{
public:
Key key;
Value val;
int h; //ÒԞÜڵãΪžùœÚµãµÄÊ÷µÄžß¶È
int N; //×ÓÊ÷ŽóС
Node* left;
Node* right;
Node(Key key, Value val, int h, int N){
this->key = key;
this->val = val;
this->h = h;
this->N = N;
}
};
template >
class AVLTree{
private:
Node* root = NULL;
int max(int a, int b){
return ((a > b) ? a:b);
}
int height(Node* x){
if(x == NULL) return -1;
return x->h;
}
int size(Node* x){
if(x == NULL) return 0;
return x->N;
}
Node* rotateLeft(Node* x){
Node* t = x->right;
x->right = t->left;
t->left = x;
x->N= 1 + size(x->left) + size(x->right);
x->h = 1+max(height(x->left),height(x->right));
t->h = 1+max(height(t->left),height(t->right));
return t;
}
Node* rotateRight(Node* x){
Node* t = x->left;
x->left = t->right;
t->right = x;
t->N = x->N;
x->N= 1 + size(x->left) + size(x->right);
x->h = 1+max(height(x->left),height(x->right));
t->h = 1+max(height(t->left),height(t->right));
return t;
}
int balanceFactor(Node* x){
return height(x->left) - height(x->right);
}
Node* balance(Node* x){
if(balanceFactor(x) < -1){
if(balanceFactor(x->right) > 0){
x->right = rotateRight(x->right);
}
x = rotateLeft(x);
}
else if(balanceFactor(x) > 1){
if(balanceFactor(x->left) < 0){
x->left = rotateLeft(x->left);
}
x = rotateRight(x);
}
return x;
}
Node* put(Node* x, Key key, Value val){
if(x == NULL) return new Node(key,val,0,1);
if(key < x->key) x->left = put(x->left,key,val);
else if(x->key < key) x->right = put(x->right,key,val);
else {x->val = val; return x;}
x->N = 1+size(x->left) + size(x->right);
x->h = 1+max(height(x->left),height(x->right));
return balance(x);
}
Node* deleteMin(Node* x){
if(x->left == NULL) return x->right;
x->left = deleteMin(x->left);
x->N = 1+size(x->left) + size(x->right);
x->h = 1 + max(height(x->left),height(x->right));
return balance(x);
}
Node* deleteMax(Node* x){
if(x->right == NULL) return x->left;
x->right = deleteMax(x->right);
x->N = 1 + size(x->left) + size(x->right);
x->h = 1 + max(height(x->left),height(x->right));
return balance(x);
}
Node* min(Node* x){
if(x->left == NULL) return x;
return min(x->left);
}
Node* max(Node* x){
if(x->right == NULL) return x;
return max(x->right);
}
Value get(Node* x,Key key){
if(x == NULL ) return NULL;
if(x->key < key) return get(x->right,key);
else if(key < x->key) return get(x->right,key);
else return x->val;
}
Node* deleteKey(Node* x,Key key){
if(x == NULL) return NULL;
if(key < x->key)
x->left = deleteKey(x->left,key);
else if(x->key < key){
x->right = deleteKey(x->right,key);
}
else{
//Ö»ÓÐÓҜڵã
if(x->left == NULL){
return x->right;
}
//Ö»ÓÐ×óœÚµã
else if(x->right == NULL){
return x->left;
}
//ÓÐÁœžöº¢×Ӝڵã
else{
Node* t = x;
x = min(t->right);
x->right = deleteMin(t->right);
x->left = t->left;
}
}
x->N = 1+size(x->left) + size(x->right);
x->h = 1 + max(height(x->left),height(x->right));
return balance(x);
}
void printTree(Node* x){
if(x == NULL) return;
printTree(x->left);
cout << x->key << " " << x->val << endl;
printTree(x->right);
}
public:
AVLTree(){
}
//žß¶È
int height(){
return height(root);
}
//ŽóС
int size(){
return size(root);
}
//²åÈ뺯Êý
void put(Key key, Value val){
root = put(root,key,val);
}
//getº¯Êý
Value get(Key key){
return get(root,key);
}
bool contains(Key key){
return get(key) != NULL;
}
bool isEmpty(){
return root==NULL;
}
//minº¯Êý
Key min(){
return min(root)->key;
}
Key max(){
return max(root)->key;
}
//deleteMinº¯Êý
void deleteMin(){
root = deleteMin(root);
}
void deleteMax(){
root = deleteMax(root);
}
void deleteKey(Key key){
root = deleteKey(root,key);
}
void printTree(){
printTree(root);
}
};
int main(){
AVLTree st ;
st.put("S",1);
st.put("T",2);
st.put("A",3);
st.printTree();
st.deleteKey("S");
st.printTree();
}