【C++】AVL树|插入|单旋|双旋
目录
一,AVL树的概念
二,AVL树节点的定义
三、AVL树的插入
四、AVL树的旋转
4.1 左单旋
4.2 右单旋
4.3 左右双旋
4.4右左双旋
五、AVL树的验证
前文对map/multimap/set/multiset进行了介绍,其容器有个共同特点,底层都是红黑树(二叉搜索树),二叉搜索树有个自身的缺陷:当往树中插入的元素有序或者接近有序时,二叉搜索树会退化成单只树,时间复杂度也会退化成O(N)。在这种情况下,map,set等关联式容器对二叉树进行了平衡处理,采用平衡树(AVL).
一,AVL树的概念
当向二叉搜索树中插入新结点后,保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度,这棵树叫 AVL树
一棵AVL树具有以下性质:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1 (-1/0/1)
- 如果一棵二叉搜索树是高度平衡的,它就是AVL树,如果它有n个结点,其高度可保持在O(logN),搜索时间复杂度O(logN)
二,AVL树节点的定义
AVL树节点的定义增加了一个指向父节点的指针,变成了三叉链结构,在原二叉树的基础上,加上平衡因子(调整平衡的作用)。
template<class K,class V>
struct AVLTreeNode //AVL树节点定义
{
//构造函数
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _bf(0)
{}
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf; //平衡因子
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
private:
Node* _root = nullptr;
};
三、AVL树的插入
AVL树只是在二叉搜索树的基础上加入了平衡因子,插入步骤也可以按照二叉搜索树来,插入其实只有两步骤:
- 按照二叉搜索树的方式插入新节点
- 调整节点的平衡因子
两个步骤进行详细解释:
(1)进行插入节点
因为AVL树本身就是一棵二叉搜索树,因此寻找结点的插入位置是非常简单的,按照二叉搜索树的插入规则:
待插入结点的key值比当前结点小就插入到该结点的左子树
待插入结点的key值比当前结点大就插入到该结点的右子树
待插入结点的key值与当前结点的 key 值相等就插入失败
(2)更新平衡因子
插入新节点后,更新平衡因子
- 新增结点在parent的右边,parent的平衡因子 ++
- 新增结点在parent的左边,parent的平衡因子 --
更新完一个结点的平衡因子后,都需要进行以下判断:
- 如果 parent 的平衡因子等于-1或者1,表明还需要继续往上更新平衡因子
解释:只有parent节点平衡因子为0的时候,才会因为因为插入新节点而改变了平衡因子++/--,所以当更新完平衡因子是-1/1的时候,说明新节点插入进来,打破了原有的平衡,改变了树的高度;
- 如果 parent 的平衡因子等于0,表明无需继续往上更新平衡因子了
解释:只有原节点parent平衡因子是1/-1的时候(原本就有高度差),新插入节点在树高度较矮的那一边,才会使平衡因子为0,该操作没有改变以parent为根节点的树高度,自然不需要往上更新;
- 如果parent的平衡因子等于 -2 或者 2,表明此时以 parent 结点为根结点的子树已经不平衡了,需要进行旋转处理
解释:插入前parent根节点树已经平衡因子为1,已经有高度差了,变成2/-2,说明新插入的节点在高度较高的子树上,此时parent节点的左右子树高度差大于1,需要旋转处理
当parent的平衡因子为 -2或2,需要旋转处理,旋转处理又分四种情况
- 当parent的平衡因子为-2,cur的平衡因子为-1时,进行右单旋
- 当parent的平衡因子为2,cur的平衡因子为1时,进行左单旋
- 当parent的平衡因子为-2,cur的平衡因子为1时,进行左右双旋
- 当parent的平衡因子为2,cur的平衡因子为-1时,进行右左双旋
bool insert(const pair<K, V>& kv)
{
//判断是否是空树,如若以此新建平衡二叉树
if (_root == nulptr)
{
_root = new Node(kv)
return true;
}
Node* parent = nullptr;
Node* cur = _root;
//寻找合适位置插入
while (cur)
{
if (cur->_kv.first > kv.first) //新节点比当前节点小,往左边
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first) //往右边
{
parent = cur;
cur = cur->_right;
}
else
{
return false; //插入失败,没找到合适位置
}
}
//找到合适位置,插入
cur = new Node(kv);
if (parent->_kv.first > kv.first) //插入左边
{
parent->_left = cur;
//更新平衡因子
parent->_bf--;
}
else
{
parent->_right = cur;
parent->_bf++;
}
cur->_parent = parent;
}
四、AVL树的旋转
4.1 左单旋
步骤如下:
- 让 subR 的左子树作为parent的右子树
- 让parent作为subR的左子树
- 让subR作为整个子树的根
- 更新平衡因子
// 左单转
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node subRL = subR->_left;
//进行连接
parent->_right = subRl;
if(subrL) //防止该节点不存在 空指针问题
subRL->_parent = parent;
subR->_left = parent;
//判断两种情况,parent是根节点,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;
}
4.2 右单旋
步骤如下:
- 让 subL 的右子树作为 parent 的左子树
- 让 parent 作为 subL 的右子树
- 让 subL 作为整个子树的根
- 更新平衡因子
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;
//判断parent节点是否为根节点
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;
}
4.3 左右双旋
步骤如下:
- 以 subL 为旋转点进行左单旋
- 以 parent 为旋转点进行右单旋
- 更新平衡因子
更新平衡因子
左右双旋之后,需要进行更新平衡因子,正确更新平衡因子的关键是:记录没有旋转之前 subLR 节点的平衡因子,该平衡因子用于判断以下三种情况:
- subLR 的平衡因子为1时,说明 subLR 的右子树是新增节点,左右双旋后 parent、subL、subLR 的平衡因子分别更新为0、-1、0
- subLR 的平衡因子为-1时,说明 subLR 的左子树是新增节点,左右双旋后 parent、subL、subLR 的平衡因子分别更新为1、0、0
- subLR 的平衡因子为0时,说明 subLR 自己就是新增节点,左右双旋后parent、subL、subLR的平衡因子分别更新为0、0、0
(1) subLR == -1
(2) subLR == 1
(3) subLR == 0
左右双旋的代码如下:
//左右双旋
void RotateLR(Node* parent)
{
//记录subL subLR节点,和subLR翻转前的平衡因子
//左右双旋
//更新平衡因子
Node* subL = parent->_left;
Node* subLR = subL->_right;
//记录翻转前的subLR的平衡因子
int bf = subLR->_bf;
RotateL(parent->_left);
RotateLR(parent);
//更新平衡因子
if (bf == 1) //新增节点在subLR右边
{
subL->_bf = -1;
subLR->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1) //新增节点在subLR左边
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 1;
}
else if (bf == 0) //新增节点就是subLR本身
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 0;
}
else
{
asser(false);
}
}
4.4右左双旋
步骤如下:
- 以 subR 为旋转点进行右单旋
- 以 parent 为旋转点进行左单旋
- 更新平衡因子
更新平衡因子
右左双旋之后,需要进行更新平衡因子,正确更新平衡因子的关键是:记录没有旋转之前 subLR 节点的平衡因子,该平衡因子用于判断以下三种情况:(与左右双旋一致,右左双旋就是在另一边)
- subLR 的平衡因子为1时,说明 subLR 的右子树是新增节点,左右双旋后 parent、subL、subLR 的平衡因子分别更新为 -1、0、0
- subLR 的平衡因子为-1时,说明 subLR 的左子树是新增节点,左右双旋后 parent、subL、subLR 的平衡因子分别更新为 0、1、0
- subLR 的平衡因子为0时,说明 subLR 自己就是新增节点,左右双旋后parent、subL、subLR的平衡因子分别更新为 0、0、0
(1) subLR == 1
(2) subLR == -1
(3) subLR == 0
代码如下:
void Rotate(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->right);
RotateL(parent);
//更新平衡因子
if (bf == -1)//subRL 的左子树新增
{
subR->_bf = 1;
parent->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 1)//subRL 的右子树新增
{
subR->_bf = 0;
parent->_bf = -1;
subRL->_bf = 0;
}
else if (bf == 0)//subLR 自己就是新增
{
subR->_bf = 0;
parent->_bf = 0;
subRL->_bf = 0;
}
else//不是上面三种情况,插入有问题
{
assert(false);
}
}
五、AVL树的验证
(1)验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
void InOrder()
{
InOrder(_root);
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
(2)验证其为平衡树
每个节点子树高度差的绝对值不超过1,进行验证节点的平衡因子是否计算正确,结果为 true 平衡因子正常
//判断平衡因子是否异常
bool IsBalance()
{
return IsBalance(_root);
}
int Height(Node* root)
{
if (root == nullptr)
return 0;
int lh = Height(root->_left);
int rh = Height(root->_right);
return lh > rh ? lh + 1 : rh + 1;
}
bool IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
if (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return abs(rightHeight - leftHeight) < 2
&& IsBalance(root->_left)
&& IsBalance(root->_right);
}