AVL tree:称为高度平衡的二叉搜索树(左右子树的高度差不超过1),是1962年有俄罗斯的数学家G.M.Adel'son-Vel'skii和E.M.Landis提出来的。它能保持二叉树的高度平衡,尽量降低二叉树的高度,减少树的平均搜索长度。
AVL树的性质: 1. 左子树和右子树的高度之差的绝对值不超过1
2. 树中的每个左子树和右子树都是AVL树
3. 每个节点都有一个平衡因子(balance factor--bf),任一节点的平衡因子是-1,0,1。(每个节点的平衡因子等于右子树 高度减去左子树的高度)
AVL树是在二叉搜索树(每个节点的左子树小于它的值,右子树大于它的值)的基础上建立出来的,我们都知道二叉搜索树增删查的时间复杂度为O(N)(最坏情况:每个节点只有左子树或者右子树),例如:
这种极端的情况下树是高度不平衡的。在二叉搜索树的基础上,我们如果使左右子树的高度差不超过1,即达到高度平衡的状态,这时候增删查的时间复杂度就达到了O(logN)(注:在这里我所提到的所有logN是以2为底数的),那么这样一颗高效的树结构我们应该怎么去实现呢?我们究竟怎样控制它才能使它在插入删除的时候,继续保持高度平衡的状态呢?这是构建AVL树的关键点。
为了让它保持高度平衡的特性,我们引入了平衡因子,每个节点的平衡因子只可能有三个取值-1(表示右子树高度-左子树高度=-1,即左子树的的高度比右子树的高度高1),0(左子树和右子树的高度相同),1(右子树比左子树高1);每次插入/删除一个节点,我们都需要向上更新一下新增节点/删除节点的祖先的平衡因子,一旦发现某个祖先的平衡因子变为2或者-2,我们就需要进行旋转来使这棵树保持AVL树的特性。
那么,问题又来了,我们需要怎样去旋转呢?
我总结了下面几种情况,我们先来考虑一下几种比较简单情况下的旋转(以向树中插入一个节点为例子):
1.黄色方框表示我们要插入的元素有可能放的位置
可以看出如果我们要插入的元素的值小于10,那么这棵树依然是一颗平衡树,不需要进行旋转;假设现在我们想插入30这个元素,这个元素应该放在c这个位置处,这时候我们向上更新平衡因子,10的平衡因子达到了2,这时候我们需要进行旋转了。旋转过程如下:
这样是不是就成功了,这种旋转我们称为左单旋
2.
此时,我们我们如果插入的元素比30大那没有问题,如果我们想插入一个元素10呢?30的平衡因子就会变为-2,这时候树又不平衡了,我们需要进行旋转方式如下:
这种旋转方式我们称之为右单旋。
3.
我想插入50,现在我们要插入的值比10大但是比20小,是不是应该放在b处,这个时候我们的树变成了这样:
这个时候需要进行旋转了。
先进行右单旋: 再左单旋:
这种旋转方式我们称为右左双旋。
4.
现在我们想要插入元素25,即在b这个位置进行插入
需要进行旋转,过程如下:
先进行左单旋: 再进行右单旋:
这种旋转我们称为左右双旋。
将这几种旋转我们再来统一的捋一捋:
1.左单旋,将g作为10的右子树,将以10为父节点的这颗子树作为20的左子树完成旋转,△的节点表示可能存在亦可能不存在
2.右单旋,把节点f作为30的左子树,然后将以30这个节点为父节点的子树作为20的右子树完成旋转
3.右左双旋,先以节点30进行右单旋,再以节点10进行左单旋
4.左右双旋,先以节点10进行左单旋,再以节点30进行右单旋
我们现在已经知道了旋转的过程,那么怎样来实现这颗AVL树呢?
以插入一个节点为例,步骤如下:
1.先构建二叉搜索树
2.插入一个节点,更新平衡因子,判断是否平衡
a)右边增加一个节点,父亲的平衡因子+1
b)左边增加一0个节点,父亲的平衡因子-1
如果父亲的平衡因子等于,则不需要向上更新,说明树的整体高度没变
如果父亲的平衡因子变为1或者-1,则需要向上进行更新,因为此时树的变了
如果父亲的平衡因子变为-2或者2,则需要根据具体情况进行相应旋转
过程出来了,代码怎么实现呢?为了向上更新平衡因子,很显然我们需要一个三叉链,即每个节点有三个指针:_left,_right,_parent,
下面我们来完成每种情况伪代码的编写。
对于左单旋的情况:
Node* ppNode=parent->_parent;
Node* subR=parent->_right;
Node* subRL=subR->_left;
parent->_right=subRL;//此处有大坑,如果subR不存在,需要将parent->_right置空
if(subRL)
{
subRL->_parent=parent;
}
subR->_left=parent;
parent->_parent=subR;
if(_root==parent)
{
_root=subR;
subR->_parent=NULL;
}
else
{
if(ppNode->_left==parent)
{
ppNode->_left=subR;
}
else
{
ppNode->_right=subR;
}
subR->_parent=ppNode;
}
subR->_bf=0;
parent->_bf=0;
对于右单旋的情况:
Node* subL=parent->_left;
Node* subLR=subL->_right;
Node* PPNode=parent->_parent;
parent->_left=subLR;
if(subLR)
{
subLR->_parent=parent;
}
subL->_right=parent;
parent->_parent=subL;
if(_root==parent)
{
_root=subL;
subL->_parent=NULL;
}
else
{
if(PPNode->_left==parent)
{
PPNode->_left=subL;
}
else
{
PPNode->_right=subL;
}
subL->_parent=PPNode;
}
subL->_bf=0;
parent->_bf=0;
对于右左双旋就是右单旋和左单旋的结合,左右双旋就是左单旋和右单旋的结合,这里不在进行分析。
完整的代码如下:
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
template<class K,class V>
struct AVLTreeNode{
K _k;
V _v;
AVLTreeNode<K,V>* _left;
AVLTreeNode<K,V>* _right;
AVLTreeNode<K,V>* _parent;
int _bf;
AVLTreeNode(const K& key,const V& val)
:_k(key)
,_v(val)
,_left(NULL)
,_right(NULL)
,_parent(NULL)
,_bf(0)
{}
};
template<class K,class V>
class MyAVL{
typedef AVLTreeNode<K,V> Node;
public:
MyAVL()
:_root(NULL)
{}
void _InOrder(Node* root)
{
if(root==NULL)
{
return;
}
_InOrder(root->_left);
cout<<root->_k<<" ";
_InOrder(root->_right);
}
void InOrder()
{
return _InOrder(_root);
}
bool Insert(const K& key,const V& val)
{
if(_root==NULL)
{
_root=new Node(key,val);
return true;
}
Node* cur=_root;
Node* parent=NULL;
while(cur!=NULL)
{
if(key<cur->_k)
{
parent=cur;
cur=cur->_left;
}
else if(key>cur->_k)
{
parent=cur;
cur=cur->_right;
}
else
{
return false;
}
}
cur=new Node(key,val);
if(parent->_k>key)
{
parent->_left=cur;
cur->_parent=parent;
}
else
{
parent->_right=cur;
cur->_parent=parent;
}
while(parent)
{
//如果插入节点为右节点,则父亲的平衡因子+1
if(cur==parent->_right)
{
parent->_bf++;
}
else//为左节点,则父亲的平衡因子-1
{
parent->_bf--;
}
//如果父节点的平衡因子变为0,说明原来是-1或者1,树的高度没变,不用向上更新平衡因子
if(parent->_bf==0)
{
break;
}
//如果父亲的平衡因子变为-1或者1,说明原来的平衡因子为0,树的高度变了,则需要向上更新
//平衡因子
else if(parent->_bf==-1||parent->_bf==1)
{
cur=parent;
parent=cur->_parent;
}
else//父亲平衡因子变为-2或者2,子树不再是AVL树,需要进行旋转来平衡
{
if(parent->_bf==2)
{
if(cur->_bf==1)
{
RotateL(parent);
}
else
{
RotateRL(parent);
}
}
else
{
if(cur->_bf==-1)
{
RotateR(parent);
}
else
{
RotateLR(parent);
}
}
break;
}
}
return true;
}
bool IsBlanceTree()
{
int height=0;
return _IsBlanceTree(_root,height);
}
bool _IsBlanceTree(Node* root,int& height)
{
if(root==NULL)
{
height=0;
return true;
}
int leftheight=0;
if(_IsBlanceTree(root->_left,leftheight)==false)
{
return false;
}
int rightheight=0;
if(_IsBlanceTree(root->_right,rightheight)==false)
{
return false;
}
if(rightheight-leftheight!=root->_bf)
{
cout<<"平衡因子异常"<<root->_k<<endl;
return false;
}
height=leftheight>rightheight?leftheight+1:rightheight+1;
return abs(leftheight-rightheight)<2;
}
void RotateR(Node* parent)
{
Node* subL=parent->_left;
Node* subLR=subL->_right;
Node* PPNode=parent->_parent;
parent->_left=subLR;
if(subLR)
{
subLR->_parent=parent;
}
subL->_right=parent;
parent->_parent=subL;
if(_root==parent)
{
_root=subL;
subL->_parent=NULL;
}
else
{
if(PPNode->_left==parent)
{
PPNode->_left=subL;
}
else
{
PPNode->_right=subL;
}
subL->_parent=PPNode;
}
subL->_bf=0;
parent->_bf=0;
}
void RotateL(Node* parent)
{
Node* ppNode=parent->_parent;
Node* subR=parent->_right;
Node* subRL=subR->_left;
parent->_right=subRL;//此处有大坑,如果subR不存在,需要将parent->_right置空
if(subRL)
{
subRL->_parent=parent;
}
subR->_left=parent;
parent->_parent=subR;
if(_root==parent)
{
_root=subR;
subR->_parent=NULL;
}
else
{
if(ppNode->_left==parent)
{
ppNode->_left=subR;
}
else
{
ppNode->_right=subR;
}
subR->_parent=ppNode;
}
subR->_bf=0;
parent->_bf=0;
}
void RotateRL(Node* parent)
{
Node* subR=parent->_right;
Node* subRL=subR->_left;
int bf=subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
if(bf==0)
{
subRL->_bf=parent->_bf=subR->_bf=0;
}
else if(bf==1)
{
subRL->_bf=0;
parent->_bf=-1;
subR->_bf=0;
}
else if(bf==-1)
{
subRL->_bf=0;
parent->_bf=0;
subR->_bf=1;
}
else
{
assert(false);
}
}
void RotateLR(Node* parent)
{
Node* subL=parent->_left;
Node* subLR=subL->_right;
int bf=subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if(bf==0)
{
subL->_bf=subLR->_bf=parent->_bf=0;
}
else if(bf==1)
{
parent->_bf=0;
subLR->_bf=0;
subL->_bf=-1;
}
else if(bf==-1)
{
parent->_bf=1;
subL->_bf=0;
subLR->_bf=0;
}
else
{
assert(false);
}
}
private:
Node* _root;
};
void TestAVLTree()
{
MyAVL<int,int> t;
//int arr[]={4,2,6,1,3,5,15,7,16};
int arr[]={16,3,7,11,9,26,18,14,15};
for(int i=0; i<sizeof(arr)/sizeof(arr[0]); ++i)
{
t.Insert(arr[i],i);
cout<<arr[i]<<" bf:"<<t.IsBlanceTree()<<endl;
}
t.InOrder();
}
运行结果如下:
有没有发现代码中包含了一个常见的笔试题:判断一颗二叉搜索树是否平衡?
思路很简单,同时递归左右子树并且记录左右子树的高度,一旦发现高度之差的绝对值大于1则说明这棵树不是平衡树。