本片基于上一篇实现的普通二叉搜索树BST
实现更进一步的AVL
树,AVL
树在普通二叉搜索树的基础上添加了树的平衡操作。
基础概念
节点的平衡因子:节点的左子树高度与右子树高度之差。
失衡:节点的平衡因子绝对值大于1。
平衡操作:通过左旋或右旋将失衡的节点的平衡因子绝对值恢复至小于等于1。
左旋:以下图为例,对节点A进行左旋,就是进行如下操作:
- A成为其右子B的左子
- B原来的左子T1成为A的右子
右旋:对节点A进行右旋,就是进行如下操作:
- 节点A成为其左子B的右子
- B原来的右子T2成为A的左子
上面的左旋和右旋已经涵盖了失衡中的2
种情况,即A
、B
、C
是在一个方向上的,下面说明不在一个方向时的旋转方法。
右左旋:如下图左所示,A
、B
、C
不在同一个方向,此时,只需进行两次旋转:
- 对
B
进行右旋操作 - 对
A
使用左旋操作
这里画图的时候画错了,B和C应该互换。
左右旋:与上面的右左旋同理,不再赘述。
代码实现
注意:本节的实现基于上一篇实现的二叉搜索树BST
。
API
AVL
树继承了普通了二叉搜索树BST
,与BST
唯一不同的仅仅是在插入和删除后添加了平衡操作。
#define balancefactor(node) (getHeight(node->left) - getHeight(node->right))
#define isBlanced(node) (balancefactor(node) < 2 && balancefactor(node) > -2)
class AVL: public BST
{
protected:
Node* rotateAt(Node* node);
Node* connect34(Node* a, Node* b, Node* c,
Node* T0, Node* T1, Node* T2, Node* T3);
public:
Node*& insert(int data);
bool remove(int data);
};
由于旋转操作比较复杂且情况也比较多,具体实现中我们不使用旋转操作使二叉树复衡,而使用更加简单的、通用的3+4重构方法。
balancefactor()
与isBalanced()
为计算平衡因子与判断节点是否平衡的宏。insert
和remove
即AVL
树的插入和删除操作,与BST
唯一不同的是添加了平衡操作。rotateAt
与connect34
即3+4重构,实现平衡操作。
方法的实现
connect34
首先介绍3+4重构使AVL
树恢复平衡的方法。
当失衡发生时,记参与旋转操作的3个节点为A
、B
、C
,大小关系为A
<B
<C
,同时,记这三个节点的4棵子树为T0
、T1
、T2
和T3
,且这四棵子树中的节点满足T0
<T1
<T2
<T3
,那么我们只要按下图所示的样子重新组装,新组装的子树一定满足AVL
树的条件:
你可以画一个失衡的树进行上面的操作,具体地查看一下。
代码实现:
传入参数:3个节点和4棵子树。
返回值:新树的根节点,供调用者将重构完成的子树与整棵树进行链接,拿上面的图来说,返回的就是B
。
Node* AVL::connect34(Node* a, Node* b, Node* c,
Node* T0, Node* T1, Node* T2, Node* T3)
{
a->left = T0; if (T0) T0->parent = a;
a->right = T1; if (T1) T1->parent = a;
updateHeight(a);
c->left = T2; if (T2) T2->parent = c;
c->right = T3; if (T3) T3->parent = c;
updateHeight(c);
b->left = a; a->parent = b;
b->right = c; c->parent = b;
updateHeight(b);
return b;
}
rotateAt
该方法调用上面的connect34
并指定重构子树的父节点
返回值:重构子树的根节点。
代码实现:
代码中g
为失衡节点,p
为g
的子,v
为p
的子。
Node* AVL::rotateAt(Node* v)
{
Node* p = v->parent, * g = p->parent;
// 视4种不同情况调用connect
if (p->isLeft()) {
if (v->isLeft()) {
p->parent = g->parent; // 指定重构子树父节点
// 重构并返回根节点
return connect34(v, p, g, v->left, v->right, p->right, g->right);
}
else {
v->parent = g->parent;
return connect34(p, v, g, p->left, v->left, v->right, g->right);
}
}
else {
if (v->isRight()) {
p->parent = g->parent;
return connect34(g, p, v, g->left, p->left, v->left, v->right);
}
else {
v->parent = g->parent;
return connect34(g, v, p, g->left, v->left, v->right, p->right);
}
}
}
insert
这个insert
相比于BST
中的insert
添加了插入后的平衡操作(调用上面的3+4重构)。
要注意的是,在平衡过后,子树的高度与未插入节点前是一样的,因此只需进行至多一次平衡操作。
Node*& AVL::insert(int data)
{
Node*& node = search(data);
if (node) return node;
++size_;
node = new Node(data, hot_); // 插入新节点
if (!hot_) root_ = node; // 此时插入的节点为根节点,更新root_
// 重平衡
for (Node* p = node->parent; p; p = p->parent) {
if (!isBlanced(p)) { // 如果发现失衡
// 先记录要被重构的子树的父亲
Node* temp = p->parent;
// 重构
Node* newp = rotateAt((p->getTallerChild())->getTallerChild());
// 更新重构子树在其父亲的子节点中的位置
if (temp) {
if (p == temp->left) temp->left = newp;
else temp->right = newp;
}
// 如果重构子树根为整树的根,那么更新root_
if (p == root_) root_ = newp;
// 重构后子树高度与未插入前相同,因此不用更新高度
break;
}
else {
updateHeight(p);
}
}
return node;
}
remove
同样在删除后添加了平衡操作,与插入不同的是,子树平衡后高度与未删除前不一定相同,因此更高的祖先可能失衡,需要进行O(logn)
次的平衡操作。
bool AVL::remove(int data)
{
Node*& target = search(data);
if (!target) return false;
removeAt(target);
--size_;
// 重平衡
for (Node* p = target->parent; p; p = p->parent) {
if (!isBlanced(p)) { // 如果发现失衡
// 先记录要被旋转的子树的父亲
Node* temp = p->parent;
// 旋转并返回该子树的新树根
Node* newp = rotateAt((p->getTallerChild())->getTallerChild());
// 更新子树在其父亲的子节点中的位置
if (temp) {
if (p == temp->left) temp->left = newp;
else temp->right = newp;
}
// 如果旋转子树根为整树的根,那么更新root_
if (p == root_) root_ = newp;
updateHeight(p); // 高度更新
}
}
return true;
}
所有二叉搜索树的代码已上传github
,sir, this way.