AVL树在数据结构中应该算是比较难的一种数据结构了,因为其中有多种插入情况而分成了不同的情况。
一、概念
AVL树是一种高度平衡的二叉搜索树,是为了降低二叉树的高度减少平均搜索长度。
相比于“二叉搜索树”,它的特点是在AVL树中任何结点的左右子树高度差不超过1。
AVL树如果右N个结点,其高度为lgN,平均搜索时间复杂度为O(lgN).
二、AVL树的性质
- 左子树和右子树的高度之差的绝对值不超过1.
- 每棵树的左右子树都是AVL树。
- 每个节点都有一个平衡因子,任何一个结点的平衡因子是-1、0、1。
三、AVL树的实现
实现一: AVLtree结构是三叉结构(包含了左右结点和父亲结点),同时拥有平衡因子,包含了key,value值。
实现二: 结点的实现与实现一一样,不同的是它采用了自己的高度值作为判别方式。
(1)AVL树的结点
AVL树的结点包含了左右指针和父亲指针,同时有key、value以及代表平衡的平衡因子
template <class K,class V>
struct AVLtreeNode
{
AVLtreeNode()
{}
AVLtreeNode(const K& k,const V&v)
:_key(k)
,_value(v)
,_pLeft(NULL)
,_pRight(NULL)
,_pParent(NULL)
{}
K _key;
V _value;
int _bf;
AVLtreeNode<K, V>* _pLeft;
AVLtreeNode<K, V>* _pRight;
AVLtreeNode<K, V>* _pParent;
};
(2)AVL树的插入
由于插入了结点,结点的平衡因子变为-2或者2,此时进行了旋转,旋转完可以结束更新,因为此时的平衡因子变为原来的平衡因子(增加一个结点,平衡因子加一,降低一层高度,平衡因子减一)。
插入主要分为一下几种情况:
- 根结点为空。
- 根结点不为空,有子树,此时需要找到插入结点,如果插入结点存在,则插入失败,如果需要结点不存在,则插入。
但是我们细分又有以下几种情况
图一:
图二:
由上面两幅图我们知道我们需要判别的有:
- 根结点是否是存在或者为为子树
- 较高右子树的左边结点存在或者不存在(较高左子树的右边结点存在或者不存在)
- 插入结点是在较高右子树的右侧左边还是右边插入。(插入结点实在较高左子树的左侧左边插入还是右边插入)
如上两幅图是我们需要左右单旋的插入方式。
图三:
图四:
如上两幅图是我们需要考虑左右单旋或者右左单旋的情况。
方法:
定义两个指针变量parent和cur,此处的实现和二叉搜索树一样,用while(cur)找到插入结点,我们需要注意的是,三叉链和二叉链不一样的是parent得链接起来。
注意:
一个结点的平衡因子由0变为1或者-1一定是增加一个结点。接着更新平衡因子,等于1或者-1更新,等于2或者-2时旋转。
(3)AVL树的旋转
AVL树的旋转主要分为左单旋、右单旋、左右单旋、右左单旋四种情况,下面我们来一一分析。
3.1 左单旋
我们假想a、b、c可能为子树、结点或者为空(这样包含了所有的选项),当我们在c或者b中插入结点时,那个结点或那一层的高度就增加了1,于是10号结点的平衡因子便不满足性质,于是要进行调整。
调整如下:
按照图中黑线所示,左旋相当与把30的结点向上提。
下面给出一个具体的事例:
如上图所示,40的结点平衡因子由2变为0,其他结点也变得平衡,因此达到了我们的目的。
3.2 右单旋
我们假想a、b、c可能为子树、结点或者为空(这样包含了所有的选项),当我们在c或者b中插入结点时,那个结点或那一层的高度就增加了1,于是10号结点的平衡因子便不满足性质,于是要进行调整。
调整如下:
按照图中黑线所示,右旋相当与把30的结点向上提。
下面给出一个具体的事例:
如上图所示,右单旋把40的结点的平衡因子由-2变为0,其他结点的平衡因子也变得平衡,因此达到了我们的目的。
3.3 左右单旋
如上图所示,10、20、30形成了一条折现,此时我们需要进行双旋来达到降低平衡因子的目的。我们按照图中所示的红线蓝线进行链接便能调整平衡因子。
当我们选择插入在20的左边时,20的左边平衡因子变为h,20的右边平衡因子还是h-1,因此可以计算出30号结点的平衡因子为1,10号结点的平衡因子为0,20号结点因为旋转后是这个树的根结点,所以平衡因子为0。
反之,我们插入在20的右边,20的右边平衡因子为h,20左边平衡因子h-1,30号结点的平衡因子为0,10号结点的平衡因子为-1,20号结点因为旋转后是这个树的根结点,所以平衡因子为0。
下面给出一个具体事例:
左右双旋我们只需要调用左单旋和右单旋的函数即可,左单旋传入的是parent的左边,即30结点,右单旋时候传入的是40号结点即可达成左右单旋的目的。
最后,我们在调用了左右单旋的函数后只需要更改结点的平衡因子即可。
3.4 右左单旋
如上图所示,10、20、30形成了一条折现,此时我们需要进行双旋来达到降低平衡因子的目的。我们按照图中所示的红线蓝线进行链接便能调整平衡因子。
当我们选择插入在20的左边时,20的左边平衡因子变为h,20的右边平衡因子还是h-1,因此可以计算出30号结点的平衡因子为0,10号结点的平衡因子为1,20号结点因为旋转后是这个树的根结点,所以平衡因子为0。
反之,我们插入在20的右边,20的右边平衡因子为h,20左边平衡因子h-1,30号结点的平衡因子为-1,10号结点的平衡因子为0,20号结点因为旋转后是这个树的根结点,所以平衡因子为0。
下面给出一个具体事例:
左右双旋我们只需要调用左单旋和右单旋的函数即可,左单旋传入的是parent的左边,即70结点,右单旋时候传入的是50号结点即可达成左右单旋的目的。
最后,我们在调用了左右单旋的函数后只需要更改结点的平衡因子即可。
针对以上情况我们便得出以下结论:
根据平衡因子判断旋转方式,parent和parent的子节点的平衡因子分为四种情况:
- 都为负数,此时采用右旋,parent的bf为-2。
- 都为正数,此时采用左旋,parent的bf为1。
- 符号相异,parent的平衡因子为负数,此时采用左右双旋。(parent的bf为-2,cur的bf为1)
- 符号相异,parent的平衡因子为正数,此时采用右左双旋。(parent的bf为2,cur的bf为-1)
左单旋移动的三个节点,一个是parent一个是subR一个是subRL。
右单旋移动的三个结点,一个是parent一个是subL一个是subLR。
四、判断是否平衡
我们判断AVL树是否平衡不能通过它的平衡因子来判断,因为平衡因子是我们自己所确定的,所以我们只能根据AVL树的性质,通过左右子树高度差的绝对值来确定是否是平衡的。
我们判断是否平衡有两种方式,一种是非递归调用的,一种是递归调用的。
非递归调用的实现原理是:写一个Height函数确定传入结点的高度,同时我们确定根结点左右子树的高度,看绝对值是否小于2,接着看它的左子树和右子树。
递归调用的实现原理:定义左子树高度为0,右子树高度为0,把高度用引用来当作函数的参数,从叶子结点开始算起,一直向上算,直到算到根结点位置,此处需要注意的是求高度的三目运算符的最后左右子树高度选取一定要+1,不然永远是0,最后再判断左减右高度的绝对值是否小于2即可。
非递归调用版本时间复杂度是O(N^2),递归调用版本时间复杂度是O(N),因为非递归调用多次重复计算了高度,所以我们便采用递归版本的判断是否平衡。
五、代码实现
#include <iostream>
#include <assert.h>
using namespace std;
template <class K,class V>
struct AVLtreeNode
{
AVLtreeNode()
{}
AVLtreeNode(const K& k,const V&v)
:_key(k)
,_value(v)
,_pLeft(NULL)
,_pRight(NULL)
,_pParent(NULL)
{}
K _key;
V _value;
int _bf;
AVLtreeNode<K, V>* _pLeft;
AVLtreeNode<K, V>* _pRight;
AVLtreeNode<K, V>* _pParent;
};
template <class K,class V>
class AVLtree
{
typedef AVLtreeNode<K, V> Node;
public:
bool Insert(const K&k, const V&v)
{
if (NULL == _pRoot)
{
Node temp = new Node(k, v);
_pRoot = temp;
return true;
}
else
{
Node temp = new Node(k, v);
Node cur = _pRoot;
Node parent = NULL;
while (cur)
{
if (k > cur->_key)
{
parent = cur;
cur = cur->_pRight;
}
else if (k < cur->_key)
{
parent = cur;
cur = cur->_pLeft;
}
else
return false;
}
//此处cur==NULL找到插入结点
cur = temp;
if (parent->_key<cur->_key)
{
//插入结点在左边
parent->_pLeft = cur;
}
else
{
//插入节点在右边
parent->_pRight = cur;
}
cur->_pParent = parent;
while (parent)
{
if (parent->_pLeft == cur)
parent->_bf--;
if (parent->_pRight == cur)
parent->_bf++;
if (parent->_bf == 0)
break;
else if ((parent->_bf == 1) || (parent->_bf == -1))
{
cur = parent;
parent = cur->_pParent;
}
else//平衡因子变为-2或者2
{
int bf = parent->_bf < 0 ? -1 : 1;
//parent平衡因子和cur平衡因子相同只可能为单旋转
if (bf == cur->_bf)
{
if (bf == -1)//加入结点在左边要右单旋
RotateR(parent);
else
RotateL(parent);
}
else
{
if (bf == -1)
RotateLR(parent);
else
RotateRL(parent);
}
break;
}
}
}
return true;
}
void RotateR(Node* parent)
{
Node* subL = parent->_pRight;
Node* subLR = subL->_pRight;
parent->_pLeft = subLR;
//此处判断subLR是否存在
if (subLR)
subLR->_pParent = parent;
subL->_pRight = parent;
Node* pparent = parent->_pParent;
parent->_pParent = subL;
if (pparent)
{
if (pparent->_pLeft == parent)
pparent->_pLeft = subL;
else
pparent->_pRight = subL;
subL->_pParent = pparent;
}
else
{
_pRoot = subL;
subL->_pParent = NULL;
}
subL->_bf = 0;
parent->_bf = 0;
}
void RotateL(Node* parent)
{
Node* subR = parent->_pRight;
Node* subRL = subR->_pLeft;
parent->_pRight = subRL;
if (subRL)
subRL->_pParent = parent;
subR->_pLeft = parent;
Node* pparent = parent->_pParent;
parent->_pParent = subR;
if (pparent)
{
if (pparent->_pLeft = parent)
parent->_pLeft = subR;
else
pparent->_pRight = subR;
subR->_pParent = pparent;
}
else
{
_pRoot = subR;
subR->_pParent = NULL;
}
subR->_bf = 0;
parent->_bf = 0;
}
//左右单旋时注意首先左旋的是parent的左节点
void RotateLR(Node* parent)
{
Node* subL = parent->_pLeft;
Node* subLR = subL->_pRight;
int bf = subLR->_bf;
RotateL(parent->_pLeft);
RotateR(parent);
if (bf == 0)//这种情况subLR是新增结点
parent->_bf = subL->_bf = 0;
else if (bf == 1)//subLR的右边为新增结点
{
subL->_bf = -1;
parent->_bf = 0;
}
else if (bf == -1)//subLR的左边为新增结点
{
subL->_bf = 0;
parent->_bf = 1;
}
subLR->_bf = 0;///最后更新平衡因子
}
void RotateRL(Node* parent)
{
Node* subR = parent->_pRight;
Node* subRL = subR->_pLeft;
int bf = subRL->_bf;
RotateR(parent->_pRight);
rotateL(parent);
if (bf == 0)
parent->_bf = subR->_bf = 0;
else if (bf == 1)
{
subL->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subL->_bf = 1;
parent->_bf = 0;
}
subRL->_bf = 0;
}
int Height(Node* Root)
{
if (Root == NULL)
return 0;
int left = Height(Root->_pLeft);
int right = Height(Root->_pRight);
return lefr > right ? left + 1 : right + 1;
}
//普通判断是否是平衡的
bool IsBalance(Node* Root)
{
if (Root == NULL)
return false;
int left = Height(Root->_pLeft);
int right = Height(Root->_pRight);
return abs(left - right) < 2
&& _IsBalance(Root->_pLeft)
&& _IsBalance(Root->_pRight);
}
//递归调用判断是否是平衡的
//时间复杂度为O(N)避免了重复算高度,直接遍历所有节点。
bool _IsBalance(Node* Root, int & heght)
{
if (Root == NULL)
{
heght = 0;
return true;
}
int leftHeight = 0;
if (_IsBalance(Root->_pLeft, leftHeight) == false)
return false;
int rightHeight = 0;
if (_IsBalance(Root->_pLeft, rightHeight) == false)
return false;
height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
//因为平衡因子小于2
return abs(leftHeight - rightHeight) < 2;
}
void Inorder()
{
return _Inorder(_pRoot);
cout << endl;
}
private:
void _Inorder(Node* Root)
{
if (Root == NULL)
return;
_Inorder(Root->_pLeft);
cout << Root->_key << " ";
_Inorder(Root->_pRight);
}
Node* _pRoot;
};
特殊场景:
{4,2,6,1,3,5,15,7,16,14};
此种场景如果没有更新好平衡因子便会导致不一样。
测试用例
我们用如下数组来初始化AVL树更能达到目的,因为包含了所有的旋转。
{16,3,7,11,9,26,18,14,15};
如下图: