一、概念及性质
AVL树又称为高度平衡的二叉搜索树,是一个“加上了额外平衡条件的二叉搜索树”所以插入的规则是按照二叉搜索树来的。
AVL数具有以下性质:
1. 左子树和右子树的高度之差的绝对值不超过1
2. 树中的每个左子树和右子树都是AVL树
3. 每个节点都有一个平衡因子(balance factor–bf),任一节点的平衡因子是-1,0,1。(每个节点的平衡因子等于右子树的高度减去左子树的高度 )
二、插入操作
插入操作实现和搜索二叉树的插入操作是相同的,需要注意的是,在插入完成后需要进行场景的分类,以便进行对应的旋转以及调节平衡因子操作。对于调节的过程才是关键!
具体实现代码如下:
bool Insert(const K& key, const V& value)
{
if (_root == NULL)
{
_root = new Node(key, value);
return true;
}
Node* parent = NULL;
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
cur = new Node(key, value);
if (key < parent->_key)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
while (parent)
{
if (cur == parent->_left)
parent->_bf -= 1;
else
parent->_bf += 1;
if (parent->_bf == 0)
return true;
else if (parent->_bf == -1
|| parent->_bf == 1)
{
cur = parent;
parent = cur->_parent;
}
else if (parent->_bf == -2
|| parent->_bf == 2)
{
if (parent->_bf == 2)
{
if (cur->_bf == 1)
RotateL(parent);
else
RotateRL(parent);
}
else
{
if (cur->_bf == -1)
RotateR(parent);
else
RotateLR(parent);
}
}
else
{
assert(false);
}
}
return true;
}
三、旋转调节平衡
由于AVL树是一个平衡搜索二叉树,具有自己的特性。那么就肯定会出现不符合AVL树的场景,此时就需要进行旋转以及调节平衡因子操作,由于只有“插入节点至根节点”路径上的各节点可能改变平衡状态,所以调账其中深度最深的节点便可使整个树重新平衡,从而保证整个AVL树的性质。对于破坏AVL树性质需要旋转调节,可具体分为以下几种旋转方式:
注:以下我们可将破坏平衡最深节点定为X,也就是进行操作的parent节点
<1>右旋转:右旋转对应情况为插入节点为于X的左子节点的左子树。我们可以想象subL本来在parent节点的右侧,通过向右旋的操作,成为了新的parent节点,所以为右旋转。
右旋中分为以下两种情况:
①subLR和parent->right都为NULL:
![这里写图片描述](https://img-blog.csdn.net/20171116083359842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Nzc3N1dXV1dTY2Ng==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
②subLR和parent->right都不为NULL:
![这里写图片描述](https://img-blog.csdn.net/20171116083636008?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Nzc3N1dXV1dTY2Ng==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
在右旋中需要注意以下几点:
1:注意parent->_parent,若存在则需要再判断 parent是祖父节点的左孩子还是右孩子,再对应连接到subL上,若不存在则此时parent为根节点,则需要将subL->_parent置NULL,让subL成为根节点;
2:注意subLR节点,若为NULL,则不能让subLR->parent还指向parent节点,但是parent指向subLR不影响;
3:在旋转调节完成后需要将subL和parent的平衡因子置为0。
具体实现如下:
void RotateR(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
parent->_left = SubLR;
if (SubLR != NULL)
SubLR->_parent = parent;
Node* Pparent = parent->_parent;
SubL->_right = parent;
parent->_parent = SubL;
if (Pparent == NULL)
_root = SubL;
else if (Pparent->_left == parent)
Pparent->_left = SubL;
else
Pparent->_right = SubL;
SubL->_parent = parent;
parent->_bf = SubL->_bf = 0;
}
<2>左旋转:左旋转对应情况为插入节点为于X的右子节点的右子树,与右旋情况十分类似。
左旋中同样分为以下两种情况:
①subRL和parent->left同时为NULL:
![这里写图片描述](https://img-blog.csdn.net/20171116085633968?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Nzc3N1dXV1dTY2Ng==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
②subRL和parent->left都不为NULL:
![这里写图片描述](https://img-blog.csdn.net/20171116090412352?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Nzc3N1dXV1dTY2Ng==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
所需要注意的和左旋是一样的,实现如下:
void RotateL(Node* parent)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
parent->_right = SubRL;
if (SubRL != NULL)
SubRL->_parent = parent;
Node* Pparent = parent->_parent;
SubR->_left = parent;
parent->_parent = SubR;
if (Pparent == NULL)
{
_root = SubR;
SubR->_parent = NULL;
}
else if (Pparent->_left == parent)
Pparent->_left = SubR;
else
Pparent->_right = SubR;
SubR->_parent = Pparent;
SubR->_bf = parent->_bf = 0;
}
<3>右左双旋:右左双旋适用场景为插入节点位于X的右子节点的左子树。
第一种情况:subRL->_bf == 0;
![这里写图片描述](https://img-blog.csdn.net/20171116094159335?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Nzc3N1dXV1dTY2Ng==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
第二种情况:subRL->_bf == 1;
![这里写图片描述](https://img-blog.csdn.net/20171116101258193?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Nzc3N1dXV1dTY2Ng==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
第三种情况:subRL->_bf == -1;
![这里写图片描述](https://img-blog.csdn.net/20171116101932622?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Nzc3N1dXV1dTY2Ng==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
左右旋转和右左旋转其实就是对两个节点分别进行左单旋和右单旋,记录好平衡因子以便旋转完成后调节。实现代码如下:
void RotateRL(Node* parent)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
int bf = SubRL->_bf;
RotateR(SubR);
RotateL(parent);
if (bf == 1)
{
parent->_bf = -1;
SubR->_bf = SubRL->_bf = 0;
}
else if (bf == 1)
{
SubR->_bf = 1;
parent->_bf = SubRL->_bf = 0;
}
else if (bf == 0)
SubRL->_bf = SubR->_bf = parent->_bf = 0;
else
assert(false);
}
<4>左右旋转:
左右旋转场景为:插入点位于X的左子节点的右子树。有了上面的基础,左右旋转也就很容易能分析出来,在此只列举出来对应的三种情况和实现代码。
第一种情况:subRL->_bf == 0;
![这里写图片描述](https://img-blog.csdn.net/20171116103109089?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Nzc3N1dXV1dTY2Ng==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
第二种情况:subRL->_bf == 1;
![这里写图片描述](https://img-blog.csdn.net/20171116105235040?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Nzc3N1dXV1dTY2Ng==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
第三种情况:subRL->_bf == -1;
![这里写图片描述](https://img-blog.csdn.net/20171116105018126?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Nzc3N1dXV1dTY2Ng==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
实现如下:
void RotateLR(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
int bf = SubLR->_bf;
RotateL(SubLR);
RotateR(parent);
if (bf == 1)
{
parent->_bf = SubLR->_bf = 0;
SubL->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 1;
SubLR->_bf = SubL->_bf = 0;
}
else if (bf == 0)
parent->_bf = SubL->_bf = SubLR->_bf = 0;
else
assert(false);
}