什么是AVL树?
二叉搜索树在插入时,可能会变成单支树,如图所示,这样的单支树在搜索时效率低,无法体现出二叉搜索树的优势,因此我们需要在插入的同时,调整二叉搜索树的高度,使每个节点的左右子树的高度差不超过1,这样的二叉搜索树称为 AVL树。
AVL的实现
结点的定义:
AVL树的结点引入平衡因子(balance factor),平衡因子记录当前结点左右子树的高度差,_bf = 右子树的高度 - 左子树的高度,可以更方便的调整每个节点左右子树的高度。引入父亲节点,方便调整高度的时候向上遍历。
template<class K ,class V>
struct AVLTreeNode
{
AVLTreeNode(const pair<K,V>& kv)
:_kv(kv)
,_bf(0)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
{ }
pair<K,V> _kv;
int _bf;//平衡因子
AVLTreeNode<K, V>* _left;//左子树
AVLTreeNode<K, V>* _right;//右子树
AVLTreeNode<K, V>* _parent;//父亲节点
};
插入 Insert :
平衡因子更新规则:
由于 _bf = 右子树的高度 - 左子树的高度,假设 cur 是要插入的结点,parent 是 cur 的父亲节点,
1、当 cur 在 parent 的左边 插入时,左子树的高度+1,故 parent->_bf --
2、当 cur 在 parent 的右边 插入时,右子树的高度+1,故 parent->_bf ++
当我们插入一个结点时,需要更新 parent 的 _bf:
1、当 parent->_bf == 0 时,说明插入前该子树左右高度相差1,插入后该子树左右高度相等,该子树已经平衡了,无需向上更新
2、 当 parent->_bf == 1/-1 时,说明插入前该子树左右高度相等,插入后该子树左右高度相差1,此时parent 需要向上走,继续更新平衡因子
3、当 parent->_bf == 2/-2 时,说明插入前该子树左右高度相差 1,插入后该子树左右高度相差 2,该子树已经不平衡了,需要对该子树进行旋转
旋转 :
左单旋 RotateL:
假设该子树的根节点为 parent,它的右子树的根节点为 subR,subR 的左子树为 subRL.
右右:即在parent 的右子树(subR)的右子树插入结点。如图,a\b\c原本的高度为 h ,当我们在 60 的右子树 c 插入结点时,c 的高度变为 h+1,60 的平衡因子变为1,30的平衡因子变为2,这时我们需要调节根为30的树的高度,使每个节点左右子树的高度不超过1。
调整方式,即左单旋(把 subR 的左上角的结点 parent 旋转下来,作为 subR 的右子树),如下:
让 subRL 作为 30 的右子树,30 作为 60 的左子树,60 作为当前树的根。
因为修改了当前子树的根节点,现在需要更新该子树的根,令 parent 的父亲节点为 ppnode,让 ppnode 的孩子节点指向新的根,
1、当 parent 不是AVL的根时
a. 如果新的根在 ppnode 的左边,ppnode->_left = subR
b.如果新的根在 ppnode 的右边,ppnode->_right = subR
2、如果 parent 就是AVL树的根,即 ppnode 为空,我们需要修改树的根为 subR
最后对 parent / subR 的平衡因子进行修改。
void RotateL(Node* parent)
{//左单旋,要转下来的结点作为parent
Node* subR = parent->_right;//父亲节点的右
Node* subRL = subR->_left;
parent->_right = subRL;
if(subRL)//避免空指针的解引用
subRL->_parent = parent;
subR->_left = parent;
//先设好ppnode,因为下一句会修改parent的父亲节点,导致ppnode不是我们预想的
Node* ppnode = parent->_parent;
parent->_parent = subR;
if (parent == _root)
{//如果转下来的结点是根
_root = subR;
subR->_parent = nullptr;
}
else
{//如果转下来的结点不是根
if (ppnode->_left == parent)//parent在ppnode的左边
{
ppnode->_left = subR;
subR->_parent = ppnode;
}
else//parent在ppnode的右边
{
ppnode->_right = subR;
subR->_parent = ppnode;
}
}
//修改平衡因子
subR->_bf = 0;
parent->_bf = 0;
}
右单旋 RotateR :
假设该子树的根节点为 parent,它的左子树的根节点为 subL,subR 的左子树为 subLR.
左左:即在parent 的左子树(subL)的左子树插入结点。如图,a\b\c原本的高度为 h ,当我们在 30 的左子树 a 插入结点时,a 的高度变为 h+1,30 的平衡因子变为 -1,60 的平衡因子变为 -2,这时我们需要调节该子树的高度。
调整方式,即右单旋(把 subL 的右上角的结点 parent 旋转下来,作为 subR 的右子树),如下:
思路参考左单旋:
void RotateR(Node* parent)
{//右单旋,要转下来的结点作为parent
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if(subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppnode = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
subL->_parent == nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subL;
subL->_parent = ppnode;
}
else
{
ppnode->_right = subL;
subL->_parent = ppnode;
}
}
parent->_bf = 0;
subL->_bf = 0;
}
右左双旋 RotateRL :
如图,假设插入前 b/c 的高度为 h-1,a/d 的高度为 h ,
当在 b 插入结点时(左图),b 的高度变为 h,导致 60 的平衡因子变为 -1,90 的平衡因子变为 -1,30 的平衡因子变为 -2 ; 当在 c 插入结点时(右图),c 的高度变为 h,导致 60 的平衡因子变为 1,90 的平衡因子变为 -1,30 的平衡因子变为 2,此时仅通过左单旋/右单旋 无法使该子树平衡,需要右左双旋来解决。
1、当 a/b/c/d 的高度都不为 0 时,先对 90 的子树进行右单旋,此时子树仍不平衡,再对 30 的子树进行左单旋,通过两次旋转后,该子树平衡
2、当 a/b/c/d 的高度都为 0 时,即 60 就是新增的结点时,同样引发右左双旋。
3、平衡因子的修改
上面三种情况旋转之后,parent / subR / subRL 的平衡因子的结果是不一样的,怎么区分这三种情况呢?
通过观察可知,subRL 的平衡因子在旋转前分别是 1 / -1 / 0,我们可以通过 subRL 旋转前的平衡因子来划分这三种情况
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 = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
assert(false);
}
左右双旋 RotateLR :
思路可以参考右左双旋:
1、当 a/b/c/d 的高度都不为 0 时,先对 30 的子树进行左单旋,再对 90 的子树进行右单旋
2、当 a/b/c/d 的高度都为 0 时, 60 就是新增的结点,和右左双旋的思路类似
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);
if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
assert(false);
}
插入的实现:
如何判断是什么旋转类型?
结合上面的图片观察可得,我们通过 parent / cur 的平衡因子来判断。
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{//根为空
_root = new Node(kv);
return true;
}
//根不为空
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{//找要插入的位置
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
cur = new Node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;//插入在右
}
else
{
parent->_left = cur;//插入在左
}
cur->_parent = parent;//记得处理父亲结点
while (parent)//一直更新到结点为空
{
if (parent->_left == cur)
{
--parent->_bf;
}
else
{
++parent->_bf;
}
if (parent->_bf == 0)
{//二叉树已经平衡
return true;
}
else if (parent->_bf == -1 || parent->_bf == 1)
{//插入前parent->_bf为0
cur = parent;//向上更新
parent = parent->_parent;
}
else if (parent->_bf == -2 || parent->_bf == 2)
{
//右单旋
if (parent->_bf == -2 && cur->_bf == -1)
RotateR(parent);
else if (parent->_bf == 2 && cur->_bf == 1)
RotateL(parent);//左单旋
else if (parent->_bf == -2 && cur->_bf == 1)
RotateLR(parent);//左右双旋
else
RotateRL(parent);//右左双旋
break;
}
else//插入前AVL树就有问题
assert(false);
}
return true;
}
AVL的高度 Height :
通过递归来计算树的高度,我们先递归左子树的高度,把递归的结果存为 leftHeight,再递归右子树的高度,把递归的结果存为 rightHeight,接着比较左右子树的高度,返回高的子树的高度 +1,这里 +1,加的是根节点自己。
size_t _Height(Node* root)
{
if (root == nullptr)
return 0;
size_t leftHeight = _Height(root->_left);
size_t rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
//+1,加的是根结点自己
}
size_t Height()
{
return _Height(_root);
}
AVL 树的结点的个数 Size :
和计算高度的思路类似,计算个数也用递归实现,先计算左子树的结点的个数,再计算右子树的结点的个数,返回左右子树结点的个数的和 +1,这里 +1 同样加的是根节点自己。
size_t _Size(Node* root)
{//返回AVL树的结点的个数
if (root == nullptr)
return 0;
return _Size(root->_left) + _Size(root->_right) + 1;//+1,加的是根结点自己
}
size_t Size()
{
return _Size(_root);//同样需要套一层
}
判断AVL树是否平衡 IsBalance :
层层递归,先根据左右子树的高度来判断根的左右子树是否平衡,再判断当前根的子树是否平衡,
1、如果左右子树的高度差超过2,或者左右子树的高度差和根的平衡因子不相等,则该子树不平衡,返回 false,并且通过 || 的判断,层层递归 false 上去;
2、如果该子树平衡,则更新树的高度,并且返回 true,继续判断其他子树。
bool _IsBalance(Node* root, int& height)
{
if (root == nullptr)
{
height = 0;
return true;
}
int leftheight = 0;//左子树的高度
int rightheight = 0;//右子树的高度
if (!_IsBalance(root->_left, leftheight)
|| !_IsBalance(root->_right, rightheight))
{//左右子树有一个返回false,则直接返回假,并且层层返回假
return false;
}
if (abs(rightheight - leftheight) >= 2)
{//高度差超过2
cout << "不平衡" << endl;
return false;
}
if ((rightheight - leftheight) != root->_bf)
{//高度差和平衡因子不相等
cout << root->_kv.first<< "平衡因子错误:" << endl;
return false;
}
height = (leftheight > rightheight ? leftheight+1 : rightheight+1);
return true;
}
bool IsBalance()
{
int height = 0;
return _IsBalance(_root, height);
}