上文对map/multimap/set/multiset进行了简单的介绍,这几个容器有个共同点是:
其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。
一、AVL树
1.1 AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
AVL树也叫作高度平衡二叉树(红黑树是控制颜色)
一棵AVL树或是空树或是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子:右子树 - 左子树的高度)的绝对值不超过1(-1 / 0 / 1)
AVL树不一定需要平衡因子,使用平衡因子是一种控制实现的方式
如果一棵二叉搜索树是高度平衡的,他就是AVL树。如果他有N个节点,其高度可以保持在O(logN),搜索时间复杂度为O(logN)
1.2 AVL树基本框架
// AVLTree节点的定义
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)
,_bf(0)
,_kv(kv)
};
// AVLTree的定义
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
private:
AVLTree()
:_root(nullptr)
{}
private:
Node* _root;
};
1.3 AVL树的插入
在AVL树中,我们采用平衡因子来实现,如果插入在父节点的右边,parent的平衡因子++,如果在左边则–
那么cur的增加会影响parent,那会影响祖先吗?——会,只可能影响祖先
cur新增节点只会影响cur祖先节点的平衡因子
1. parent -> left == cur , parent -> bf--
2. parent -> right == cur , parent -> bf++
3. 更新后,如果parent -> bf == 0 则更新结束,说明更新前parent->bf是1或是-1,现在变为了0则表明原先是不平衡的状态,现在是平衡的,parent所在的子树高度不变
4. 更新后,如果parent -> bf == 1 / -1 则继续往上更新,说明更新前parent->bf是0,现在变为了1 / -1,则说明我有一边的子树变高了,parent所在的子树高度也就变了
5. 更新后,如果parent -> bf == 2 / -2,则说明parent子树已经不平衡了,需要进行旋转处理
演示三种不同的情况:
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
AVLTree()
:_root(nullptr)
{}
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else // 相等
{
return false;
}
}
cur = new Node(kv);
if (parent->_kv < kv.first)
{
parent->_right = cur;
cur->_parent = parent; // 这里是三叉链
}
else // 这里不可能相等,因为相等上面就走false了
{
parent->_left = cur;
cur->_parent = parent;
}
// 前面是插入,下面控制平衡
/* 一个节点的平衡因子受到子树的影响,新插入节点的平衡因子一定为0(子树为空树)
如果新节点插入在父亲的右边,则平衡因子++
如果新节点插入在父亲的左边,则平衡因子--
控制平衡有两步
1. 更新平衡因子—更新新增节点到根节点的祖先路径
2. 如果出现异常的平衡因子,就需要旋转平衡处理
*/
}
private:
Node* _root;
};
1.4 AVL树的旋转
旋转的意义:
- 保持搜索树规则
- 控制平衡
通俗来说也就是使得这棵树平衡了,这棵树的高度降了1
while (parent)
{
if (cur == parent->_left) // 若插入在左侧,则parent的平衡因子--
{
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)
{
// 旋转
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 if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
// 出现问题
assert(false);
}
}
1.4.1 右单旋
下图中a、b、c是高度为h的平衡搜索树
用抽象图可以代表各种右旋的情况,旋转的操作:
- b变为60的左边
- 60变成30的右边
上图实际表示出来就是下面这样子
因此就可以实现右旋代码,如下图所示可以用代码实现其连接关系
void RotateL(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR) // subLR可能会是空
subLR->_parent = parent; // 因为是三叉链,还需要去更新parent
Node* parentParent = parent->_parent; // 先存储祖宗
subL->_right = parent;
parent->_parent = subL;
// 没有祖先
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else // 还有祖先,将祖先的左右指针指向刚刚更新后子树的新的root
{
if (parentParent->_left == parent)
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parent;
}
parent->_bf = subL->_bf = 0;
}
1.4.2 左单旋
右侧高了,因此我们使用左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL) // subRL可能会是空
subRL->_parent = parent;
Node* parentParent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
// 如果有祖先
if (parentParent->_left == parent)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
subR->_bf = parent->_bf = 0;
}
1.4.3 左右双旋
新节点插入较高左子树的右侧:左右 -> 先左单旋在进行右单旋
1.4.4 右左双旋
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
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);
}
}
双旋旋转完后要记得去更新平衡因子