前言:在前面我们学习过了二叉搜索树,我们知道如果想在树中查找结点,时间复杂度是o(N),但是在二叉树中查找结点时间复杂度是o(logN),但在二叉树中有特殊的情况。当树中的数据有序或接近有序,二叉树就会退化成单支树,查找元素就相当于在顺序表中查找,大大减慢了查找的效率。
为了解决这种情况,两位俄罗斯数学家G.M.Adelson-Velskii和E.M.Landis提出一个解决该问题的方法,每当二叉搜索树插入新结点,如果能保持左右子树的高度差绝对值不超过1,从而降低树的高度,从而减少平均搜索长度。
AVL树的定义:前面我们讲了AVL树背景来源,那么什么是AVL树呢?AVL树需要满足左右子树高度差(简称平衡因子)的绝对值不超过1,它的左右子树也满足该性质。
平衡因子=右子树的高度-左子树的高度
AVL树结点的定义:
AVL树的性质我们已经知道了,那么现在我们要来定义AVL树的结点了
定义AVL树前,我们需要知道AVL树中需要有什么,我们需要有指向左右子树结点的指针,还有指向当前结点的父节点的指针(为了方便后面的插入,这里使用三叉链进行实现AVL树)还有为了平衡树的高度的平衡因子,还有存放的数据。
template<class K,class V>
//设置为公用的可以在类外访问
struct AVLTreeNode
{
AVLTreeNode<K, V>* _parent;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
//平衡因子
int _bf;
//存放的数据
pair<K, V> _kv;//不理解pair用法的老铁可以去查一下文档
//构造函数
AVLTreeNode(const pair<K, V>& kv)
:_parent(nullptr)
: _left(nullptr)
: _right(nullptr)
: _bf(0)
: _kv(kv)
{};
};
AVL树的插入:
我们已经搞定了AVL树结点的定义,那么我们来到了AVL树结点最难的地方,AVL树结点的插入。
我们知道AVL树是在二叉搜索树的基础上增加了平衡因子,那么插入数据也是和二叉搜索树插入一样,由于笔者在上一篇博客已经讲过二叉搜索树插入的思路了,这里就不在讲述了,直接上代码了。
template<class K,class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
//AVL树插入
bool Insert(const pair<K, V>& kv)
{
//先按二叉搜索规则插入
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
else
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv < kv)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv > kv)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
//把cur和parent连接起来
//如果cur的key比parent的key大,那么就插入到右边
if (cur->_kv.first > parent->_kv.first)
{
parent->_right = cur;
//还需要把新的结点和父节点链接起来
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
}
}
private:
Node* _root = nullptr;
};
到这里AVL树的插入结点就完成了,但是别忘了AVL树还有平衡因子,由于我们在叶子结点插入了数据,那么必然会导致AVL树平衡因子的变化,那么我们需要调整这些变化的平衡因子。
更新平衡因子
在插入新的结点前,平衡因子只有-1/0/1这三种情况
如果我们在右子树插入新的结点,那么右子树的平衡因子会如何变化呢,同理左子树会如何变化呢?我们来画画图看看吧。
//更新平衡因子
//只要parent结点存在,我们就需要继续更新
while (parent)
{
//如果插入的结点是parent的右节点
//那么parent的平衡因子+1
if (cur == parent->_right)
{
parent->_bf++;
}
//如果插入的结点是parent的左节点
//那么parent的平衡因子-1
else
{
parent->_bf--;
}
}
当平衡因子在插入后为0
//更新后parent的平衡因子等于0了
//表示平衡了就直接跳出循环了
if (parent->_bf == 0)
break;
当平衡因子在插入后为1/-1
//更新完后parent的平衡因子等于-1和1时
//我们还需要判断上面结点的平衡因子有没有超过1
else if (parent->_bf == -1 || parent->_bf == 1)
{
cur = parent;
parent = parent->_parent;
}
当平衡因子在插入前为2/-2
当平衡因子为正负2时,分为四种情况,我会分别画图讲解这四种情况
1.左单旋**(新插入的结点在右子树)**
//左单旋
//该树是三叉链实现
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//先把parent的右节点指向subRL
//如果subRL不为空
//再让subRL的parent指向parent
parent->_right = subRL;
if (subRL)
{
subRL->_parent = parent;
}
//再链接parent和subR
subR->_left = parent;
//先保存好parent的parent
//等会可能会用到
Node* ppNode = parent->_parent;
//如果parent是根结点
//那么就让subR做新的根结点
//如果是子树的根结点,那么就让subR的parent
//指向parent的parent
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
//当parent是ppNode的左结点
if (parent == ppNode->_left)
ppNode->_left = subR;
else
ppNode->_right = subR;
//再让subR的parent指向ppNode
subR->_parent = ppNode;
}
//更新subR和parent的平衡因子为0
subR->_bf = parent->_bf = 0;
}
2.右单旋(新插入结点在左子树)
让parent->left指向subR->right,再让subR->right->指向parent
//右单旋
void RotateR(Node* parent)
{
Node* subR = parent->_left;
Node* subRL = subR->_right;
parent->_left = subRL;
if (subRL)
{
subRL->_parent = parent;
}
subRL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == ppNode->_left)
ppNode->_left = subR;
else
ppNode->_right = subR;
subR->_parent = ppNode;
}
subR->_bf = parent->_bf = 0;
}
3.右左双旋(在左子树的右侧插入结点)
//右左双旋
void RotateRL(Node* parent)
{
Node* subL = parent->_left;
Node* subRL = subL->_right;
//先保留subRL的平衡因子
//通过判断subRL的平衡因子来确定是在
//subRL的那边子结点插入的
int bf = subRL->_bf;
//让parent->rgiht先右旋转
RotateR(parent->right);
//再让parent左旋转
RotateL(parent);
//更新平衡因子
//计算插入结点后的平衡因子
if (bf == 1)
{
subRL->_bf = 0;
subL->_bf = -1;
parent->_bf = 0;
}
else if (bf == -1)
{
subRL->_bf = 0;
subL->_bf = 0;
parent->_bf = 1;
}
else if(bf==0)
{
parent->_bf = 0;
subL->_bf = 0;
subRL->_bf = 0;
}
}
4.左右双旋(在右子树的左侧插入结点)
//左右双旋(
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);
if (bf == 1)
{
subL->_bf = -1;
parent->_bf = 0;
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;
}
}
到这里,关于平衡因子等于-2/2的情况已经全部考虑到了。
接下来我们需要把左单旋和右单旋和左右单旋和右左单旋放进bf==-2/bf==2的代码中。
else if (parent->_bf == -2 || parent->_bf == 2)
{
//parent==2表示右边高
if (parent->_bf == 2)
{
//如果cur结点的bf==1
//表示右边子树高,需要左单旋
if (cur->_bf == 1)
{
RotateL(parent);
}
//表示cur先是右子树高
//再是左子树高
//需要右左双旋
else if (cur->_bf == -1)
{
RotateRL(parent);
}
}
//parent==-2表示左边高
else if (parent->_bf == 2)
{
//如果cur的bf==-1
//表示左子树高,需要右单旋
if (cur->_bf == -1)
{
RotateR(parent);
}
//如果cur的bf==-1
//表示左子树高
//再是右子树高
//需要左右单旋
else if (cur->_bf == -1)
{
RotateLR(parent);
}
}
//旋转完成后,parent所在树恢复到了插入结点前的高度
//所以对parent的上层结点没有影响,就可以跳出循环了
break;
}
到这里为止,AVL树的插入已经完成了,由于AVL树删除和插入非常相似,笔者就不在这里再分享了。
验证AVL树
我们需要分为两步来验证AVL
1.先验证AVL树是否是二叉搜索树
如果中序遍历该树成功,那么就是二叉搜素树
2.再验证AVL树是否平衡
每个子树的高度差绝对值不超过1
验证的代码
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
//中序递归遍历
//先递归左树,再递归右树
_InOrder(root->_left);
cout << root->_kv.first<< ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
int Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
//判断AVL树是否平衡
bool _IsBalance(Node* root)
{
if (root == nullptr)
return true;
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
//只要右子树-左子树高度差不超过1就是平衡
return abs(leftHeight - rightHeight) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
bool IsBalance()
{
return _IsBalance(_root);
}
那么我们就测试一下吧
void TestAVLTree()
{
int a[] = { 16,3,7,11,9,26,18,14,15 };
AVLTree<int, int> t;
for (auto e : a)
{
t.Insert(make_pair(e,e));
}
t.InOrder();
cout << t.IsBalance() << endl;
}
我们可以看到AVL树正确的打印出来了,并且返回1表示AVL树平衡了。
总结:
AVL树相对于二叉搜索树更加复杂,希望看完这篇博客的老铁能对AVL树插入有了深刻的理解!