AVL树
- 1.1 AVL树的概念
- 1.2 AVL树节点的定义
- 1.3 AVL树的旋转
- 1.3.1 右旋(右单旋)
- 1.3.2 左旋(左单旋)
- 1.3.3 左右双旋(先左单旋再右单旋)
- 1.3.4 右左双旋(先右单旋再左单旋)
- 1.4 AVL树的插入
- 1.5 AVL树的遍历和树的高度
1.1 AVL树的概念
1.AVL树是一种自平衡的二叉搜索树,它的特点是任何一个节点的左右子树的高度差不超过1。AVL树的平衡性保证了它的查找、插入和删除操作的时间复杂度都是O(logn)。AVL树的平衡调整是通过旋转操作来实现的,有四种基本的旋转方式:左旋、右旋、左右旋和右左旋。AVL树的命名来自于它的发明者Adelson-Velsky和Landis。
2.一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树;
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)。
因为AVL树的特点是任何一个节点的左右子树的高度差不超过1,所以当插入一个节点,需要判断平衡因子是否满足,不满足则需要调整,这就导致插入时效率低下,实际中用的并不多,但是AVL树的旋转 是需要了解的。
1.2 AVL树节点的定义
节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0或-1的节点被认为是平衡的。带有平衡因子-2或2的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。
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)
, _kv(kv)
, _bf(0)
{}
};
二叉搜索树的头文件AVLTree.h:
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv); //插入
void InOrder(); //遍历
int Height(); //树的高度
private:
void RotateR(Node* parent); //右旋
void RotateL(Node* parent); //左旋
void RotateLR(Node* parent); //左右双旋
void RotateRL(Node* parent); //右左双旋
void _InOrder(Node* root); //遍历
int _Height(Node* root); //树的高度
private:
Node* _root = nullptr;
};
1.3 AVL树的旋转
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,
使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:左旋、右旋、左右旋和右左旋。
至于哪四种,只需要画图看看就很会清楚:
如果按顺序插入,可以看到每个节点的左右子树的高度差不超过1,所以不需要旋转。
1.3.1 右旋(右单旋)
而如果如下图,那么就需要旋转,其中,parent为平衡因子大于1的结点,subL为parent的左节点,subLR为parent的右节点;下图的树,可能为某棵树的子树,subLR可能为空,也可能不为空。
如下图,subLR不为空,parent为平衡因子大于1的结点:
如何旋转?让parent变为subL的右子树,subL的右子树即subLR变为parent的左子树,这样可以降低高度并且旋转后仍为AVL树,如下图:
代码于解析如下:
void RotateR(Node* parent) //右单旋
{
//找到旋转需要的结点
Node* subL = parent->_left;
Node* subLR = subL->_right;
//parent可能为某一棵树的子树,所以parent->_parent有两种情况,所以先更改别的指针
parent->_left = subLR;
//subLR可能为空也可能不为空,所以这里需要判断一下,防止指针非法访问
if (subLR)
subLR->_parent = parent;
//接下来处理parent->_parent的节点,先找到parent->_parent节点的位置,然后可以修改parent->_parent的指针
Node* ppnode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
//判断parent->_parent的两种情况
if (parent == _root) //判断条件也可以为 ppnode == nullptr
{
//如果parent为根节点,则需要变根节点
_root = subL;
_root->_parent = nullptr;
}
else
{
//否则,判断parent为左子树还是右子树
if (ppnode->_left == parent)
{
ppnode->_left = subL;
}
else
{
ppnode->_right = subL;
}
//修改指针
subL->_parent = ppnode;
}
//更新平衡因子,高度降低,且parent、subL左右子树高度差为0
parent->_bf = subL->_bf = 0;
}
1.3.2 左旋(左单旋)
左单旋和右单旋思路基本相同。
代码如下:
void RotateL(Node* parent) //左单旋
{
//找到旋转需要的结点
Node* subR = parent->_right;
Node* subRL = subR->_left;
//parent可能为某一棵树的子树,所以parent->_parent有两种情况,所以先更改别的指针
parent->_right = subRL;
//subRL可能为空也可能不为空,所以这里需要判断一下,防止指针非法访问
if (subRL)
subRL->_parent = parent;
//接下来处理parent->_parent的节点,先找到parent->_parent节点的位置,然后可以修改parent->_parent的指针
Node* ppnode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
//判断parent->_parent的两种情况
if (ppnode == nullptr)
{
//如果parent为根节点,则需要变根节点
_root = subR;
_root->_parent = nullptr;
}
else
{
//否则,判断parent为左子树还是右子树
if (ppnode->_left == parent)
{
ppnode->_left = subR;
}
else
{
ppnode->_right = subR;
}
subR->_parent = ppnode;
}
//更新平衡因子,高度降低,且parent、subR左右子树高度差为0
parent->_bf = subR->_bf = 0;
}
1.3.3 左右双旋(先左单旋再右单旋)
如果按如下图插入,该如何旋转?
如果右单旋上图会是什么样子?
可以看到右旋后的树高度未降低,所以右旋后的不是AVL树,那么左旋?
可以看到树的高度虽然降低,但是旋转后的树违反了AVL树的规定,即左节点大于右结点,所以左旋后的不是AVL树。那么该如何旋转?所以这里必须旋转两次,即先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。如下图所示:
void RotateLR(Node* parent) //左右双旋
{
//找到旋转需要更改的结点
Node* subL = parent->_left;
Node* subLR = subL->_right;
//旋转之前,保存subLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
int bf = subLR->_bf;
//先左旋再右旋
RotateL(parent->_left);
RotateR(parent);
//对subLR结点的平衡因子进行讨论
//旋转之前,subLR的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进行调整
if (bf == 1)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 1;
subLR->_bf = 0;
subL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = 0;
}
else
{
//否则直接报错
assert(false);
}
}
1.3.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)
{
subR->_bf = 0;
parent->_bf = -1;
subRL->_bf = 0;
}
else if (bf == -1)
{
subR->_bf = 1;
parent->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 0)
{
subR->_bf = 0;
parent->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
1.4 AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么
AVL树的插入过程可以分为两步:
- 按照二叉搜索树的方式插入新节点
- 调整节点的平衡因子
1.先按照二叉搜索树的规则将节点插入到AVL树中
2.新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否
破坏了AVL树的平衡性
插入后,pParent的平衡因子一定需要调整,在插入之前,pParent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:
- 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可
- 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可
此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2
1.如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整
成0,此时满足AVL树的性质,插入成功
2.如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更
新成正负1,此 时以pParent为根的树的高度增加,需要继续向上更新
3.如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进
行旋转处理
代码如下:
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
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.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
// 更新平衡因子
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 1 || parent->_bf == -1)
{
// 继续更新
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
// 需要旋转处理 -- 1、让这颗子树平衡 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)
{
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
1.5 AVL树的遍历和树的高度
在类中调用AVL树的遍历和树的高度函数时,在类中套一层,因为类外不能访问私有成员,代码如下:
void InOrder()
{
_InOrder(_root);
cout << endl;
}
int Height()
{
return _Height(_root);
}
int _Height(Node* root)
{
if (root == NULL)
return 0;
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
return leftH > rightH ? leftH + 1 : rightH + 1;
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
完整代码如下:
#pragma once
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)
, _kv(kv)
, _bf(0)
{}
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
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.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
// 更新平衡因子
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 1 || parent->_bf == -1)
{
// 继续更新
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
// 需要旋转处理 -- 1、让这颗子树平衡 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)
{
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
int Height()
{
return _Height(_root);
}
private:
int _Height(Node* root)
{
if (root == NULL)
return 0;
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
return leftH > rightH ? leftH + 1 : rightH + 1;
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
void RotateR(Node* parent) //右单旋
{
//找到旋转需要的结点
Node* subL = parent->_left;
Node* subLR = subL->_right;
//parent可能为某一棵树的子树,所以parent->_parent有两种情况,所以先更改别的指针
parent->_left = subLR;
//subLR可能为空也可能不为空,所以这里需要判断一下,防止指针非法访问
if (subLR)
subLR->_parent = parent;
//接下来处理parent->_parent的节点,先找到parent->_parent节点的位置,然后可以修改parent->_parent的指针
Node* ppnode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
//判断parent->_parent的两种情况
if (parent == _root) //判断条件也可以为 ppnode == nullptr
{
//如果parent为根节点,则需要变根节点
_root = subL;
_root->_parent = nullptr;
}
else
{
//否则,判断parent为左子树还是右子树
if (ppnode->_left == parent)
{
ppnode->_left = subL;
}
else
{
ppnode->_right = subL;
}
//修改指针
subL->_parent = ppnode;
}
//更新平衡因子,高度降低,且parent、subL左右子树高度差为0
parent->_bf = subL->_bf = 0;
}
void RotateL(Node* parent) //左单旋
{
//找到旋转需要的结点
Node* subR = parent->_right;
Node* subRL = subR->_left;
//parent可能为某一棵树的子树,所以parent->_parent有两种情况,所以先更改别的指针
parent->_right = subRL;
//subRL可能为空也可能不为空,所以这里需要判断一下,防止指针非法访问
if (subRL)
subRL->_parent = parent;
//接下来处理parent->_parent的节点,先找到parent->_parent节点的位置,然后可以修改parent->_parent的指针
Node* ppnode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
//判断parent->_parent的两种情况
if (ppnode == nullptr)
{
//如果parent为根节点,则需要变根节点
_root = subR;
_root->_parent = nullptr;
}
else
{
//否则,判断parent为左子树还是右子树
if (ppnode->_left == parent)
{
ppnode->_left = subR;
}
else
{
ppnode->_right = subR;
}
subR->_parent = ppnode;
}
//更新平衡因子,高度降低,且parent、subR左右子树高度差为0
parent->_bf = subR->_bf = 0;
}
void RotateLR(Node* parent) //左右双旋
{
//找到旋转需要更改的结点
Node* subL = parent->_left;
Node* subLR = subL->_right;
//旋转之前,保存subLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
int bf = subLR->_bf;
//先左旋再右旋
RotateL(parent->_left);
RotateR(parent);
//对subLR结点的平衡因子进行讨论
//旋转之前,subLR的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进行调整
if (bf == 1)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 1;
subLR->_bf = 0;
subL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = 0;
}
else
{
//否则直接报错
assert(false);
}
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 1)
{
subR->_bf = 0;
parent->_bf = -1;
subRL->_bf = 0;
}
else if (bf == -1)
{
subR->_bf = 1;
parent->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 0)
{
subR->_bf = 0;
parent->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
private:
Node* _root = nullptr;
};
测试代码如下:
void Test_AVLTree()
{
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
AVLTree<int, int> t1;
for (auto e : a)
{
t1.Insert(make_pair(e, e));
}
t1.InOrder();
cout << "Height: " << t1.Height() << endl;
}
int main()
{
Test_AVLTree();
return 0;
}
运行结果如下: