目录
一,什么是AVL树
二,AVL树的实现
结构体
insert
左单旋
右单旋
双旋
双旋右边高
双旋左边高
最终实现的插入函数
遍历
判断平衡
一,什么是AVL树
在之前,我们已经了解到了二叉搜索树,提到过它的搜索效率,如果二叉搜索树是一个单支树,那么在查找时,效率此时最低,查找效率和链表一样O(N),而想要提高搜索二叉树的效率,就需要平衡搜索二叉树两端的字数,他们的高度如果能一样,那么查找的效率就是O(logN),效率会提升很多。如何去平衡搜索二叉树呢?俄罗斯的两位数学家G.M.Adelson-Velskii 和E.M.Landis在1962年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度,这样的树我们称为AVL树。
AVL树具有以下的性质:
.它的左右子树都是AVL树
.左右子树的高度差(平衡因子)的绝对值小于等于1--(-1,0,1)
二,AVL树的实现
结构体
template<class k, class v>struct AVLTreeNode
{
AVLTreeNode<k, v>* _left;
AVLTreeNode<k, v>* _right;
AVLTreeNode<k, v>* _parent;//增加了一个 parent,用来找平衡因子
pair<k, v> _kv;//pair表示我们的两个数据(key,value)
int _bf;//平衡因子 balance_factor
//构造函数
AVLTreeNode(const pair<k, v>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _bf(0)
{}
};
template<class k, class v>struct AVLTree
{
typedef AVLTreeNode< k, v> Node;
.......
private:
Node*eoot=nullptr;
};
insert
这里的插入与我们之前的搜索二叉树基本一致,不同的这里我们用的是kv模型,在插入完之后,对于AVL树,我们还需要平衡它,那么如何去平衡它呢?
首先对于一个搜索二叉树,我们在插入一个新节点后,他可能会影响它的祖先的平衡因子(高度差的绝对值).
在这里,我们规定新增节点在平衡因子左边减一,在右边平衡因子加一。
这里主要是插入后,处理父亲的平衡因子为1和大于1的时候的情况。
增加节点后,父亲的平衡因子变成一,即高度发生变化,我们需要向上更新祖先的平衡因子,主要操作就是,记录当前节点为parent,重新赋值parent为上一个祖先。
其次对于旋转调整,分为两种情况:
左单旋
左单旋,右子树整体比左子树高,我们需要去调整使得这里祖先的平衡因子小于2。
主要实现:我们将新增节点的parent的parent左节点重新链接到根的右子树上去,在使得我们的parent成为新的根节点,即可实现。
这里,我们可以记录这个节点60为subR,60节点的左孩子为subRL,在传入参数的时候传入父亲节点(parent)。以这样的方式去总结实现左单旋:
//实现左单旋
void rotateL(Node* parent)
{
//找到subR
Node* subR = parent->_right;
Node* subRL = subR->_left;
//重新链接
parent->_right = subRL;
subR->_left = parent;
//重新给出parent为subR
parent->_parent = subR;
Node* ppNode = parent->_parent;
//subRL的父亲为现在的父亲,需要注意的是这里的sunRL可能为空
if(subRL)
subRL->_parent = parent;
//除了以上链接,还有parent的_parent节点重新与新的parent链接
//判断这里的parent是否为根,如果为根,我们的_parent为空
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
//此时的_parent是存在的
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
//旋转完成,修改平衡因子,这里变化的也只有parent与subR这两个节点的平衡因子
parent->_bf = subR->_bf = 0;
}
右单旋
除了右边比左边高的情况,当然还有左边比右边高的情况,实现右单旋与左单旋的思路一样。
此时我们记录30这个节点为subL,30的右节点为subLR,此时对应的parent也就是节点60,然后以左单旋相同的思路实现右单旋:
//实现右单旋
void rotateR(Node* parent)
{
//找到subR
Node* subL = parent->_left;
Node* subLR = subL->_right;
//重新链接
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
Node* ppNode = parent->_parent;
subL->_right = parent;
//重新给出parent为subR
parent->_parent = subL;
//链接parent的_parent节点重新与新的parent链接
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
//此时的_parent是存在的
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
//旋转完成,修改平衡因子,这里变化的也只有parent与subR这两个节点的平衡因子
parent->_bf = subL->_bf = 0;
}
双旋
除了上述提到的插入情况,新增节点在右树的最右(右边高),左数的最左(左边高),我们分别
通过左旋和右旋的方式降低树的高度,但是当新增节点在右数的左边,左树的右边,那么该如何调整?
双旋右边高
我们再以右边高为情况, 变一个样式,详细的画一下图:
用两次旋转的方式改变树的高度,
void rotateRL(Node*parent)
{
//这里关键的是平衡因子的修改
Node* subR = parent->_right;
Node* subRL = subR->_left;
//以subRL平衡因子做判断
int bf = subRL->_bf;//记录
rotateR(parent->_right);
rotateL(parent);
if (bf == 0)
{
//subRL自己就是新增节点
parent->_bf = subR->_bf = subRL->_bf = 0;
}
else if (bf == -1)
{
//新增在subRL的左边,直接修改为旋转后的平衡因子
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 1)
{
//新增在subRL的右边
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
双旋左边高
当新增节点在b处,旋转之后的平衡因子为parent 0,subL 1,subRL -1
当新增节点在c处,旋转之后的平衡因子为parent -1,subL 0,subRL 1
void rotateLR(Node* parent)
{
//这里关键的是平衡因子的修改
Node* subL = parent->_left;
Node* subLR = subL->_right;
//以subLR平衡因子做判断
int bf = subLR->_bf;//记录
rotateR(parent->_parent);
rotateL(parent->_left);
if (bf == 0)
{
//subLR自己就是新增节点
parent->_bf = subL->_bf = subLR->_bf = 0;
}
else if (bf == -1)
{
//新增在subLR的左边,直接修改为旋转后的平衡因子
parent->_bf =0;
subL->_bf =1;
subLR->_bf = 0;
else if (bf == 1)
{
//新增在subLR的右边,直接修改为旋转后的平衡因子
parent->_bf = -1;
subL->_bf =0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
记录60的平衡因子来确定parent与subRL的平衡因子。
最终实现的插入函数
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->_left = cur;
//这里除了链接cur,还要把我们的parent记录给我们的_parent
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//插入完之后,开始平衡二叉树
while (parent)
{
if (cur == parent->_left)
{
//如果插入的是父亲的左子树,平衡因子--
parent->_bf--;
}
else
{
//如果插入的是父亲的左子树,平衡因子++
parent->_bf++;
}
if (parent->_bf == 0)
{
//不需要向上更新平衡因子,完美情况
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
//父亲的平衡因子不为0,此时高度发生变化,需要向上更新祖先的平衡因子
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//平衡因子已经超过1,需要旋转调整处理
//右边高,左单旋
if (parent->_bf == 2 && cur->_bf == 1)
{
rotateL(parent);
}
//左边高,右单旋
else if (parent->_bf == -2 && cur->_bf == -1)
{
rotateR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
rotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
rotateLR(parent);
}
//旋转完成后,子树的高度降低,整个树的高度也降低
break;
}
else
{
//否则就直接出问题了,无法调整
assert(false);
}
}
return true;
}
遍历
还是中序遍历
void inorder()
{
_inorder(_root);
cout << endl;
}
void _inorder(Node* root)
{
if (root == nullptr)
{
return;
}
_inorder(root->_left);
cout << root->_kv.first<<" ";
_inorder(root->_right);
}
判断平衡
直接通过计算左右子树的高度差的绝对值来判断,不过需要再写一个计算子树高度的函数。
bool isbalance()
{
return _isbalance(_root);
}
int High(Node*root)
{
if (root == nullptr)
{
return 0;
}
int lefthigh = High(root->_left);
int righthigh = High(root->_right);
return lefthigh > righthigh ? lefthigh + 1 : righthigh + 1;
}
bool _isbalance(Node*root)
{
if (root == nullptr)
{
return true;
}
int treehigh1 = High(root->_left);
int treehigh2 = High(root->_right);
if (treehigh2 - treehigh1 != root->_bf)
{
cout << "平衡因子异常" << endl;
return false;
}
return abs(treehigh1 - treehigh2)<2&&_isbalance(root->_left) && _isbalance(root->_right);
}
AVL的最主要部分就是插入的时候,平衡二叉树。