目录
上一篇已经对关联式容器set/map/multiset/multimap进行了简答的介绍,大家可能发现它们有一个共同点:其底层都是按照二叉搜索树来实现的,但是学习二叉搜索树时,已经知道当树中插入的元素有序或接近有序时,二叉搜索树的会变得极不平衡,查找操作的时间复杂度可能达到 O(n),甚至退化成链表。因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。
一、AVL树的概念
AVL树是一种自平衡二叉搜索树,它得名于它的发明者 Adelson-Velsky 和 Landis。AVL树通过在每次插入或删除节点时进行旋转操作来保持树的平衡,以确保每个结点的左右子树高度之差的绝对值不超过1,降低树的高度,从而实现较高效率的查找、插入和删除操作。
AVL树的性质:
- 它的左右子树都是AVL树
- 每个节点的左子树和右子树的高度差(平衡因子)不超过1。
- 如果插入或删除操作导致树失去平衡,AVL树会通过旋转操作(包括单旋转和双旋转)来重新平衡。
- 若一个AVL树有n个节点,它的查找、插入和删除操作的时间复杂度都是 O(log_2 n),高度可保持在 O(log_2 n)。
二、AVL树节点的定义
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
pair<K, V> _kv;
int _bf;//平衡因子balance factor
AVLTreeNode(const pair<K,V> kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
,_bf(0)
{}
};
三、AVL树的操作
3.1 AVL树的平衡因子
AVL 树的平衡因子是指每个节点的左子树高度减去右子树高度的值,即左子树高度 - 右子树高度。对于任意一个节点,其平衡因子可以是 -1、0 或 1。
AVL 树的平衡因子定义如下:
- 如果一个节点的平衡因子为 -1,表示该节点的右子树比左子树高度高 1;
- 如果一个节点的平衡因子为 0,表示该节点的左子树和右子树高度相等;
- 如果一个节点的平衡因子为 1,表示该节点的左子树比右子树高度高 1。
AVL 树通过保持每个节点的平衡因子为 -1、0 或 1 来确保树的平衡。当插入或删除节点后,需要通过旋转操作来调整各个节点的平衡因子,以确保整棵树仍保持平衡状态。
3.2 AVL树的插入
在 AVL 树中插入元素的过程如下:
- 按照二叉搜索树的规则,找到新元素应该插入的位置,将其作为叶子节点插入到树中
- 在插入新元素后,从插入点开始向上回溯,更新每个祖先节点的平衡因子,并检查它们是否失去了平衡。
- 如果发现某个节点失去了平衡,则需要对其进行旋转操作,以恢复整棵树的平衡。
步骤一:按照二叉搜索树的规则插入新元素
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv)
{
Node* pnode = new Node(kv);
if (_root == nullptr)
{
_root = pnode;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < pnode->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > pnode->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
if (parent->_kv.first < pnode->_kv.first)
{
parent->_right = pnode;
pnode->_parent = parent;
}
else
{
parent->_left = pnode;
pnode->_parent = parent;
}
//开始处理平衡因子
//...
private:
Node* _root = nullptr;
};
步骤二:更新每个祖先节点的平衡因子,检查它们是否失去了平衡。
在插入元素之前,parent的平衡因子分为三种情况:0,1,-1,插入后分为以下两种情况:
- 如果cur插入到parent的左侧,只需给parent的平衡因子-1即可
- 如果cur插入到parent的右侧,只需给parent的平衡因子+1即可
bool Insert(const pair<K, V>& kv)
{
//.....
//开始处理平衡因子
cur = pnode;
while (parent)
{
if (cur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//失去了平衡,需要进行旋转
//旋转操作。。。
}
else
{
assert(false);
}
}
return true;
}
插入后,parent节点的平衡因子发生变化,有这么三种情况:0,正负1, 正负2。
- 情况1:parent的平衡因子变为0(parent的平衡因子一定是由1或-1变成0