前面对map和set做了简单的介绍,这几个的个共同特点就是其底层都是用二叉搜索树来写的,但是二叉搜索树有自身的缺陷,如果树中插入的元素有序或接近有序,二叉搜索树就会退化成单支树,时间复杂度变成O(N),所以map和set等关联式容器的底层结构是对二叉树做了平衡处理,采用AVL树实现
AVL树的概念
当向二叉搜索树插入新的节点后,如果能保证每个节点的左右子树高度差的绝对值不超过一,即是AVL树,如果超过一,就要进行调整,使其变成AVL树
AVL树节点的定义
template<calss T>
struct AVLTreeNode
{
AVLTreeNode(const T& data)
: _Left(nullptr)
, _Right(nullptr)
, _Parent(nullptr)
, _data(data)
, _bf(0)
{}
AVLTreeNode<T>* _Left; // 该节点的左孩子
AVLTreeNode<T>* _Right; // 该节点的右孩子
AVLTreeNode<T>* _Parent; // 该节点的双亲
T _data;
int _bf; // 该节点的平衡因子
}
AVL树的插入
AVL树引入了平衡因子的概念,就是右子树高度-左子树高度
插入:
1.按照搜索树规则插入
2.更新祖先节点的平衡因子
a.插入父亲节点的右边,平衡因子++
b.插入父亲节点的左边,平衡因子--
c.父亲平衡因子==0,父亲所在子树高度不变,停止更新,插入结束
d.父亲平衡因子==1or-1,父亲所在子树高度变了,继续往上更新
e.父亲平衡因子==2or-2,父亲所在子树不平衡了,需要旋转处理
AVL树的旋转
当AVL树不平衡时,我们会进行旋转,一共有四种旋转方式:右单旋,左单旋,左右旋,右左旋
右单旋
右单旋对应的parent的平衡因子是-2,cur对应的平衡因子是-1
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
parent->_bf = subL->_bf = 0;
}
左单旋
左单旋对应的parent的平衡因子是2,cur对应的平衡因子是1
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
parent->_bf = subR->_bf = 0;
}
左右旋
左右旋的第一种情况:新增节点在60的左子树,先进行左旋,然后右旋,最后更新平衡因子
左右旋的第二种情况:新增节点在60的右子树,先进行左旋,然后右旋,最后更新平衡因子
左右旋的第三种情况:60作为新增节点,先进行左旋,然后右旋,最后更新平衡因子
左右旋的条件是parent的平衡因子是-2,cur的平衡因子是1
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
//左旋
RotateL(parent->_left);
//右旋
Rotate(parent);
//更新平衡因子
if (bf == -1)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
subLR->_bf = 0;
subL->_bf = -1;
parent->_bf = 0;
}
else
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 0;
}
}
右左旋和左右选差不多,也有三种情况,这里不再画图
右左旋的条件是parent的平衡因子是2,cur的平衡因子是-1
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(subR);
RotateL(parent);
if (bf == 1)
{
subRL->_bf = 0;
subR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subRL->_bf = 0;
parent->_bf = 0;
subR->_bf = 1;
}
else
{
subRL->_bf = 0;
parent->_bf = 0;
subR->_bf = 0;
}
}
判断AVL树是否平衡
int _Height(Node* root)
{
if (root == nullptr)
{
return 0;
}
return max(_Height(root->_left), _Height(root->_right)) + 1;
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int LeftHeight = _Height(root->_left);
int RightHeight = _Height(root->_right);
//不平衡
if (abs(LeftHeight - RightHeight) >= 2)
{
cout << root->_kv.first << "\n";
return false;
}
//检查平衡因子
if (RightHeight - LeftHeight != root->_bf)
{
cout << root->_kv.first << "\n";
return false;
}
return _IsBalance(root->_left) && _IsBalance(root->_right);
}
AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即O(logN)但是如果要对AVL树做一些结构修改的操
作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数
据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。