AVL树的概念
二叉搜索树虽可以缩短查找的效率,但
如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当
于在顺序表中搜索元素,效率低下
。因此,两位俄罗斯的数学家
G.M.Adelson-Velskii
和
E.M.Landis
在
1962
年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之
差的绝对值不超过
1(
需要对树中的结点进行调整
)
,即可降低树的高度,从而减少平均搜索长度。
一棵
AVL
树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是
AVL
树。如果它有
n个结点,其高度可保持在 O(log2n)
,搜索时
间复杂度O(log2n)
AVL树节点的定义
template <class T>
struct AVLNode
{
AVLNode(T data = T())
: _data(data), _left(nullptr), _right(nullptr), _height(1) {}
AVLNode *_left;
AVLNode *_right;
T _data; //数据域
int _height; //用来记录以当前节点为根节点的树的高度
};
AVL树的定义
template <class T>
class AVLTree
{
public:
AVLTree() { _root = nullptr; }
private:
struct AVLNode
{
AVLNode(T data = T())
: _data(data), _left(nullptr), _right(nullptr), _height(1) {}
AVLNode *_left;
AVLNode *_right;
T _data; //数据域
int _height; //用来记录以当前节点为根节点的树的高度
};
AVLNode *_root;
//当前节点的高度
int height(AVLNode *node) const
{
return node == nullptr ? 0 : node->_height;
}
//返回左右子树最高的高度
int maxHeight(AVLNode *node1, AVLNode *node2)
{
int height1 = height(node1);
int height2 = height(node2);
return height1 > height2 ? height1 : height2;
}
};
AVL树的旋转
如果在一棵原本是平衡的
AVL
树中插入一个新节点,可能造成不平衡,此时就要进行检查。在插入新节点位置向上回溯的根节点位置进行检查各个节点的左右子树的高度差。进行平衡调整
二叉树的失衡主要的基本情况如下:
![](https://img-blog.csdnimg.cn/20210522154950539.png)
AVL树为了维护节点平衡,总共包含四种旋转操作,分别是:
1.节点失衡,是由于左孩子的左子树太高造成的,进行右旋转操作
![](https://img-blog.csdnimg.cn/20210522155706916.png)
右旋转操作:左孩子的右子树放到失衡节点左子树上,失衡节点放到左孩子的右子树上,左孩子成为根节点
![](https://img-blog.csdnimg.cn/201906040453464.gif)
代码:
// /右旋转
AVLNode *_rightRotate(AVLNode *node)
{
AVLNode *child = node->_left;
node->_left = child->_right;
child->_right = node;
//及时更新高度
node->_height = maxHeight(node->_left, node->_right) + 1;
child->_height = maxHeight(child->_left, child->_right) + 1;
return child;
}
2.节点失衡,是由于右孩子的右子树太高造成的,进行左旋转操作
![](https://img-blog.csdnimg.cn/20210522155718717.png)
左旋转操作:右孩子的左子树放到失衡节点右子树上,失衡节点放到右孩子的左子树上,右孩子成为根节点
![](https://img-blog.csdnimg.cn/20190604045508949.gif)
代码:
//\左旋转
AVLNode *_leftRotate(AVLNode *node)
{
AVLNode *child = node->_right;
node->_right = child->_left;
child->_left = node;
//及时更新高度
node->_height = maxHeight(node->_left, node->_right) + 1;
child->_height = maxHeight(child->_left, child->_right) + 1;
return child;
}
3.节点失衡,是由于左孩子的右子树太高造成的,进行左-右旋转(也叫左平衡操作)操作
![](https://img-blog.csdnimg.cn/20210522155736385.png)
左平衡操作:先对失衡节点的左子树进行左旋操作,在对失衡节点进行右旋操作
代码:
// 左右旋转 <
AVLNode *_leftBlance(AVLNode *node)
{
node->_left = _leftRotate(node->_left);
return _rightRotate(node);
}
4.节点失衡,是由于右孩子的左子树太高造成的,进行右-左旋转(也叫右平衡操作)操作
![](https://img-blog.csdnimg.cn/20210522155744877.png)
右平衡操作:先对失衡节点的左子树进行右旋操作,在对失衡节点进行左旋操作
代码:
//右左旋转 >
AVLNode *_rightBlance(AVLNode *node)
{
node->_right = _rightRotate(node->_right);
return _leftRotate(node);
}
AVL树的插入
AVL
树就是在二叉搜索树的基础上引入了平衡因子,因此
AVL
树也可以看成是二叉搜索树。那么
AVL
树的插入 过程可以分为两步:
1.
按照二叉搜索树的方式插入新节点
2.
调整节点的高度
思路:
- 先按照BST树的插入操作进行节点插入
- 插入后判断左右子树高度是否失衡
- 若失衡判断需要单旋转还是双旋转
- 更新节点高度 返回
代码:
void remove(const T &val)
{
_root = _remove(_root, val);
}
AVLNode *_insert(AVLNode *node, const T &val)
{
if (node == nullptr)
{
return new AVLNode(val);
}
//向左子树插入
if (node->_data > val)
{
node->_left = _insert(node->_left, val);
//进行旋转调整 插入左子树 必然左边高度大于右边高度
if (height(node->_left) - height(node->_right) > 1)
{
//判断是 /还是 <
if (node->_left->_data > val)
{
// /
node = _rightRotate(node);
}
else
{
// <
node = _leftBlance(node);
}
}
}
//向右子树插入
else if (node->_data < val)
{
node->_right = _insert(node->_right, val);
if (height(node->_right) - height(node->_left) > 1)
{
if (node->_right->_data > val)
{
// >进行右平衡
node = _rightBlance(node);
}
else
{
// \ 左旋转
node = _leftRotate(node);
}
}
}
//更新高度
node->_height = maxHeight(node->_left, node->_right) + 1;
return node;
}
AVL树的删除
因为
AVL
树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新节点高度,只不过与插入不同的是,删除节点后的高度更新,最差情况下一直要调整到根节点的位置。
思路:与删除BST树一样需要讨论几个情况;
- 当待删除节点左右子树都不为空时
将该节点的前驱或者后继赋值给该节点 转而去删除前驱或者后继。
这里注意判断该节点的左右子树高度,若左子树高删除前驱,若右子树高删除后继。原因,删除比较高的节点时不会影响整个AVL树的平衡,因此也就不需要调整
- 当待删除节点只有一个子树节点
待删除节点在左子树中:判断右子树高度是否过高导致不平衡,若是,判断是右子树的左孩子高还是右子树的右孩子高,从而去进行相应的调整
待删除节点在右子树中:与上述相反
代码:
void remove(const T &val)
{
_root = _remove(_root, val);
}
AVLNode *_remove(AVLNode *node, const T &val)
{
if (node == nullptr)
return nullptr;
if (node->_data > val)
{
//在左子树中找删除
node->_left = _remove(node->_left, val);
if (height(node->_right) - height(node->_left) > 1)
{
// \ 左旋转
if (height(node->_right->_right) > height(node->_right->_left))
{
node = _leftRotate(node);
}
else
{
// >
node = _rightBlance(node);
}
}
}
else if (node->_data < val)
{
//在右子树中找删除
node->_right = _remove(node->_right, val);
if (height(node->_left) - height(node->_right) > 1)
{
// / 右旋转
if (height(node->_left->_left) > height(node->_left->_right))
{
node = _rightRotate(node);
}
else
{
// <
node = _leftBlance(node);
}
}
}
else
{
//找到了 对于左右子树都存在 删除他的前去或者后继
if (node->_left != nullptr && node->_right != nullptr)
{
// 比较左子树和右子树的高度,谁高删除谁,不用旋转
if (height(node->_left) > height(node->_right))
{
//删除前驱
AVLNode *p = node->_left;
while (p->_right != nullptr)
p = p->_right;
node->_data = p->_data;
node->_left = _remove(node->_left, p->_data);
}
else
{
//删除后继
AVLNode *p = node->_right;
while (p->_left != nullptr)
p = p->_left;
node->_data = p->_data;
node->_right = _remove(node->_right, p->_data);
}
}
else if (node->_left != nullptr)
{
AVLNode *child = node->_left;
delete node;
return child;
}
else if (node->_right != nullptr)
{
AVLNode *child = node->_right;
delete node;
return child;
}
else
{
return nullptr;
}
}
//更新高度
node->_height = maxHeight(node->_left, node->_right) + 1;
return node;
}