map和set的底层是二叉搜索树,如果一棵树插入的元素接近有序,那么树会退化为单支树,在查找的时间复杂度会为O(N) ,因此对普通二叉树进行了平衡处理,即采用平衡树来实现。
AVL树的性质1.它的左右子树都是AVL树2.任何一颗左右子树的高度差绝对值不超过1
平衡因子:控制节点的高度
template
struct AVLTreeNode
{
AVLTreeNode(const T& data = T())
: _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(data)
, _bf(0)
{}
AVLTreeNode* _pLeft;
AVLTreeNode* _pRight;
AVLTreeNode* _pParent;
T _data;
int _bf; // 节点的平衡因子
};
树的插入分为这几步:
1.树为空,链接根节点,返回2.树不为空,找到插入位置,插入位置必为叶子节点
3.对孩子和父亲进行双向链接
4.修改平衡因子
-- 左插入 bf--
-- 右插入 bf++
5.如果插入节点影响祖先节点的平衡因子,向上调整平衡因子
--如果平衡因子为0 不做调整
--平衡因子为1 ,继续往上调整祖先
--平很因子为2,旋转调整子树
新增节点导致节点的平衡因子为1 继续往上调整父亲的平衡因子
新增节点导致节点的平衡因子为2 子树作旋转调整
6.对不符合规则的平衡因子树旋转规则
1).parent的平衡因子为2 ,cur的平衡因子为1,进行左单旋。
2).parent的平衡因子为-2,cur的平衡因子为-1,进行右单旋。
3).parent的平衡因子为2,cur的平衡因子为-1,进行右左双旋。
4)parent的平衡因子为-2,cur的平衡因子为-,进行左右双旋。
// 在AVL树中插入值为data的节点
bool Insert(const T& data)
{
if (_pRoot == nullptr)
{
_pRoot = new Node(data);
return true;
}
Node* cur = _pRoot;
Node* parent = nullptr;
while (cur)
{
if (cur->_data < data)
{
parent = cur;
cur = cur->_pRight;
}
else if (cur->_data > data)
{
parent = cur;
cur = cur->_pLeft;
}
else
{
return false;
}
}
//找到插入点
cur = new Node(data);
if (parent->_data < data)
parent->_pRight = cur;
else
parent->_pLeft = cur;
cur->_pParent = parent;
//调整平衡因子
while (parent)
{
//左减右加
if (cur == parent->_pLeft) parent->_bf--;
else parent->_bf++;
if (parent->_bf == 0) //加入前bf -1 \ 1
break;
else if (parent->_bf == 1 || parent->_bf == -1) //bf==0 ,继续向上调整
{
cur = cur->_pParent;
parent = parent->_pParent;
}
else if (parent->_bf == -2 || parent->_bf == 2)
{
//右边高,左单旋
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);
}
else
assert(false);
}
}
插入使子树parent的平衡因子为2 ,cur的平衡因子为1 ,呈现一条直线
例如
下面展示抽象图
小长条表示节点高度h,h可以是0 、1的任意一种,旋转思路是把subR推上去作根,subRL是subR的左子树
1).让subRL作parent的右子树,如果subRL不为空,subRL的父亲链接parent
2).将parent链接subR的左 ,parent的parent链接subR (双向链接)
3).如果subR不为根,提前保存parent的parent ,链接subR的parent
4).调整平衡因子,全为0
代码
// 左单旋
void RotateL(Node* pParent)
{
Node* subR = pParent->_pRight;
Node* subRL = subR->_pLeft;
Node* parentparent = pParent->_pParent;
//链接subrl
pParent->_pRight = subRL;
if (subRL)
subRL->_pParent = pParent;
subR->_pLeft = pParent;
pParent->_pParent = subR;
if (pParent == _pRoot)
{
_pRoot = subR;
_pRoot->_pParent = nullptr;
}
else
{
//判断parentparent的孩子
if (parentparent->_pLeft == pParent)
parentparent->_pLeft = subR;
else
parentparent->_pRight = subR;
subR->_pParent = parentparent;
}
//更新平衡因子
pParent->_bf = subR->_bf = 0;
}
右单旋的思路与做单旋基本一致,针对parent为-2,cur为-1,左边高的一条直线
抽象图:
关于右单旋,需要完成四步:
1).parent的左链接subLR 如果subLR不为空,它的parent链接parent
2).parent作为subL的右子树
3).如果parent不为根,subL链接parent
4).更新平衡因子 平衡因子全为0
代码
// 右单旋
void RotateR(Node* pParent)
{
Node* subL = pParent->_pLeft;
Node* subLR = subL->_pRight;
Node* parentparent = pParent->_pParent;
pParent->_pLeft = subLR;
if (subLR)
subLR->_pParent = pParent;
subL->_pRight = pParent;
pParent->_pParent = subL;
if (pParent == _pRoot)
{
_pRoot = subL;
subL->_pParent = nullptr;
}
else
{
if (parentparent->_pLeft == pParent)
parentparent->_pLeft = subL;
else
parentparent->_pRight = subL;
subL->_pParent = parentparent;
}
//更新平衡因子
subL->_bf = pParent->_bf = 0;
}
parent为-2 subR为1 的类似折线型 要进行左右双旋
下面详细画图展示:
调整的步骤如下
1.对subL节点进行左单旋
2.再对parent节点进行右单旋
3.更新平衡因子,由于新增节点可以在左子树,也可以在右子树,所以进行详细讨论
-------1.subLR的平衡因子为-1时,
subLR和subL 的平衡因子变为0,parent的平衡因子为1
------2.subLR的平衡因子为1时
subLR和parent的平衡因子为0 ,subL的平衡因子为-1
-----3.subLR的平衡因子为0
subLR的平衡因子为0时,进行双旋后,subL、subLR、parent的平衡因子都为0
// 左右双旋
void RotateLR(Node* pParent)
{
Node* subL = pParent->_pLeft;
Node* subLR = subL->_pRight;
int bf = subLR->_bf;
RotateL(subL);
RotateR(pParent);
//调整平衡因子
if (bf == 0)
{
subL->_bf = subLR->_bf = pParent->_bf = 0;
}
else if (bf == -1)
{
subL->_bf = subLR->_bf = 0;
pParent->_bf = 0;
}
else if (bf == 1)
{
pParent->_bf = subLR->_bf = 0;
subL->_bf = -1;
}
else
assert(false);
}
针对parent 为2 subR -1 的折线形
调整步骤
1.先对subR节点进行右单旋,这样就形成直线形状
2.对parent节点进行左单旋
3.调整平衡因子
调整平衡因子需要分三类:
分别是subRL为-1 1 0
图解标注三类平衡因子的调整:
1.
subRL为-1时,parent和subRL都调整0,subR调整为0
2.
subRL为1时, subLR和subR 调整为0 parent调整为-1
3.
subRL的平衡因子为0时,右左单旋后,subRL subR 和parent的平衡因子都调整为0
代码
// 右左双旋
void RotateRL(Node* pParent)
{
Node* subR = pParent->_pRight;
Node* subRL = subR->_pLeft;
int bf = subRL->_bf;
RotateR(subR);
RotateL(pParent);
//更新平衡因子
if (bf == 0)
subR->_bf = subRL->_bf = pParent->_bf = 0;
else if (bf == -1)
{
pParent->_bf = subRL->_bf = 0;
subR->_bf = 1;
}
else if (bf == 1)
{
pParent->_bf = -1;
subRL->_bf = subR->_bf = 0;
}
else
assert(false);
}
AVL树是一个绝对平衡的二叉树,子树高度差不超过1,极大降低查找的时间复杂度,变成稳定的O(log2_N)
但是由于要维护绝对平衡,在删除时候,要进行多次的旋转调整,甚至从叶子调整到根节点。
如果要高效查找一个数据,那么可以放在AVL树中
如果一个结构要经常发生改变,AVL树就不太适用。
AVL树是一种绝对平衡的二叉树,其三叉链结果是我们首次接触,单旋转,双旋思路非常巧妙。
双旋的平衡因子调整也分多种情况,在复杂的结构中,画图描述是解决的关键。
本文关于AVL的插入进行深入的探讨,作者水平有限,如有疏忽,请指出!
gitee:
链接直达:AVL树的模拟实现