AVLTree(搜索平衡二叉树)
性质一:每一个节点的左右子树都是AVLTree
性质二:每个节点左右子树高度只差不超过1
优点:提高查找效率,减少树的高度进而可以降低递归的深度。解决了有序插入导致二叉树退化成单支树的问题
缺点:为了保持平衡需要大量的旋转,插入,删除效率低。性价比不如红黑树
此样例使用 balance factor(_bf)来控制平衡
AVLTree和其节点的定义
//AVLTree节点
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode(pair<K, V> kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
, _kv(kv)
{}
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf; //balance factor 平衡因子
pair<K, V> _kv;
};
template<class K, class V>
class AVLTree
{
private:
Node* _root;
};
insert函数的实现
bool insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
}
else {
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(kv);
if (kv.first < parent->_kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else if (kv.first > parent->_kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
//1.更新平衡因子 -- 新增结点到祖先结点的路径上的结点
//2.若出现异常平衡因子,则需要旋转平衡处理
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
//这里存在四种情况
//1.平衡因子大于1 或 小于-1 需要进行旋转平衡
//2.父亲的平衡因子等于0 这说明了这个一定是补上了子树矮的那一部分,子树的最大高度不变,无需继续向上调整
//3.父亲平衡因子为1 或者 -1 说明子树有一边变高了,这棵树最大高度改变,需继续向上调整
//4.父亲不存在了,一路更新到根节点
if (parent->_bf == 2 || parent->_bf == -2)
{
if (parent->_bf == -2 && cur->_bf == -1)
{
Rrotate(cur);
break;
}
else if (parent->_bf == 2 && cur->_bf == 1)
{
Lrotate(cur);
break;
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
LRrotate(parent);
break;
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RLrotate(parent);
break;
}
else
{
assert(false);
}
}
else if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
else
{
//插入更新平衡因子前,就有问题了
assert(false);
}
}
return true;
}
}
一:插入节点
1.root为空,新建节点并初始化赋值给root
2.root非空,循环向下查找,若插入节点key大于cur节点的key,去cur右子树查找,反之去左子树找。若等于返回false。直到找到空位置新建节点进行插入
二:判断平衡
新结点的插入会导致平衡因子的变化,我们需要通过平衡因子进行判断怎么翻转节点
这里存在四种情况
1.平衡因子大于1 或 小于-1 需要进行旋转平衡
2.父亲的平衡因子等于0 这说明了这个一定是补上了子树矮的那一部分,子树的最大高度不变,无需继续向上调整
3.父亲平衡因子为1 或者 -1 说明子树有一边变高了,这棵树最大高度改变,需继续向上调整
4.一路更新到根节点,直到父亲不存在
情况一:右单旋
条件:parent->_bf = -2 && cur->_bf = -1
//右旋
void Rrotate(Node* cur)
{
Node* parent = cur->_parent;
Node* parpar = parent->_parent;
Node* subL = cur;
Node* subLR = subL->_right;
parent->_bf = 0;
subL->_bf = 0;
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
subL->_right = parent;
parent->_parent = subL;
subL->_parent = parpar;
if (parpar == nullptr)
{
_root = subL;
}
else
{
if (parpar->_left == parent)
{
parpar->_left = subL;
}
else
{
parpar->_right = subL;
}
}
}
情况二:左单旋
条件:parent->_bf = 2 && cur->_bf = 1
以上两种情况为最简单的两种,经过旋转后,根节点和sub节点的平衡因子都为零,说明这一块子树已经平衡已经无需继续向上调整,所以可以直接break跳出循环
//左旋
void Lrotate(Node* cur)
{
Node* subR = cur;
Node* parent = cur->_parent;
Node* parpar = parent->_parent;
Node* subRL = subR->_left;
subR->_bf = parent->_bf = 0;
if (subRL)
{
subRL->_parent = parent;
}
parent->_right = subRL;
parent->_parent = subR;
subR->_left = parent;
if (parpar == nullptr)
{
_root = subR;
}
else
{
if (parpar->_left == parent)
{
parpar->_left = subR;
subR->_parent = parpar;
}
else
{
parpar->_right = subR;
subR->_parent = parpar;
}
}
}
情况三:先左旋,后右旋
有三种情况
1:parent->_bf = -2 && subL->_bf=1 && subLR->_bf=-1
2:parent->_bf = -2 && subL->_bf=1 && subLR->_bf=1
3.parent->_bf=-2 && subL->_bf = 1 && subLR->_bf==0
以上三种情况都是先左旋后右旋,最终根节点的平衡因子都是零,所以无需向上调整。但是我们发现情况一,情况二,情况三旋转后左右两结点的平衡因子组合有所不同,在完成双旋后,我们需要对平衡因子进行调整
情况四:先右旋,后左旋。和情况三类似
//先右旋,后左旋
void RLrotate(Node* parent)
{
Node* subRL = parent->_right->_left;
Node* subR = parent->_right;
//记录subLR的_bf 便于旋转后调整平衡因子
int bf = subRL->_bf;
Rrotate(subRL);
Lrotate(subRL);
if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = parent->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
subRL->_bf = subR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = subRL->_bf = subR->_bf = 0;
}
}
//先左旋,后右旋
void LRrotate(Node* parent)
{
Node* subLR = parent->_left->_right;
Node* subL = parent->_left;
int bf = subLR->_bf;
Lrotate(subLR);
Rrotate(subLR);
if (bf == 1)
{
subL->_bf = -1;
parent->_bf = subLR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = subLR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = subL->_bf = subLR->_bf = 0;
}
}
旋转条件判断
//1.更新平衡因子 -- 新增结点到祖先结点的路径上的结点
//2.若出现异常平衡因子,则需要旋转平衡处理
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
//这里存在四种情况
//1.平衡因子大于1 或 小于-1 需要进行旋转平衡
//2.父亲的平衡因子等于0 这说明了这个一定是补上了子树矮的那一部分,子树的最大高度不变,无需继续向上调整
//3.父亲平衡因子为1 或者 -1 说明子树有一边变高了,这棵树最大高度改变,需继续向上调整
//4.父亲不存在了,一路更新到根节点
if (parent->_bf == 2 || parent->_bf == -2)
{
if (parent->_bf == -2 && cur->_bf == -1)
{
Rrotate(cur);
break;
}
else if (parent->_bf == 2 && cur->_bf == 1)
{
Lrotate(cur);
break;
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
LRrotate(parent);
break;
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RLrotate(parent);
break;
}
else
{
assert(false);
}
}
else if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
else
{
//插入更新平衡因子前,就有问题了
assert(false);
}
}
return true;
}
那么如何判断我们的树是否是AVL树呢?我们可以设计小实验来检测我们的树。
只要将每个结点的右子树高度减去左子树高度就可以得到这个结点的左右子树的高度差了,与_bf进行比对就可以判断是否平衡。
//递归取得树的深度
int tree_deep(Node* root)
{
if (root == nullptr)
{
return 0;
}
int deep = tree_deep(root->_left) + 1;
if (deep < tree_deep(root->_right) + 1)
{
deep = tree_deep(root->_right) + 1;
}
return deep;
}
//根据深度来得到真实的高度差
int Bfnum(Node* root)
{
return tree_deep(root->_right) - tree_deep(root->_left);
}
//中序遍历每个结点,并打印真实高度差和平衡因子
void _inOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_inOrder(root->_left);
cout << root->_kv.first << ":" << root->_bf << ":" << Bfnum(root) << endl;
_inOrder(root->_right);
}
void inOrder()
{
_inOrder(_root);
}
VL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证
查询时高效的时间复杂度log2(N) 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:
插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,
但一个结构经常修改,就不太适合。
查找性能高,修改结构性能低。结构不会被修改时可以使用,若频繁增删则不太适合