1.概念
虽然二叉搜索树可以缩短查找的效率,但如果数据有序或者接近有序时二叉搜索树树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。AVL
树是具有一下性质的二叉搜索树:
1.它的左右子树都是AVL树
2.左右子树的高度差的绝对值不超过1
如果一个二叉搜索树是高度平衡的,它就是AVL树。如果它有n个节点,其高度可保持在log N,搜索时间复杂度为O(log N);
2.节点定义
template<class T>
struct AVLTreeNode
{
AVLTreeNode(T key)
:_bf(0), _key(key)
{}
AVLTreeNode* _left = nullptr;
AVLTreeNode* _right = nullptr;
AVLTreeNode* _parent = nullptr;
int _bf; //平衡因子
T _key;
};
3.AVL插入
AVL树的插入就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。
AVL树的插入过程可以分为两部分:
1.按照二叉搜索树的方式插入新节点
2.调整平衡因子
3.1 按照二叉搜索树的方式插入新的节点
bool Insert(T key)
{
Node* newnode = new Node(key);
if (_root == nullptr)
{
_root = newnode;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
//寻找插入位置
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else return false;
}
//插入新的节点
newnode->_parent = parent;
if (key < parent->_key) parent->_left = newnode;
else parent->_right = newnode;
}
3.2调整平衡因子
新节点插入后,AVL树的平衡性可能遭到破坏,因此就需要更新平衡因子,并检测是否破坏了AVL树的平衡性
当newnode插入后,parent的平衡因子一点需要调整,在插入之前parent的平衡因子分为三种情况:-1,0,1。
调整方式分为以下两种:
当新节点插入到parent左侧时,parent平衡因子-1;
当新节点插入到parent右侧时,parent平衡因子+1;
此时parent的平衡因子有以下三种情况:0,正负1,正负2
1.如果此时的平衡因子为0,说明插入新的节点后parent平衡了,满足AVL树的性质,无需继续向上调整。
2.如果此时平衡因子为正负1,说明插入新的节点后parent为根的树高度增加,需要继续向上调整。
3.如果此时平衡因子为正负2,说明parent违反了AVL树的性质,需要对其经行旋转处理。
cur = newnode;
while (parent)
{
//更新平衡因子
if (cur == parent->_left) parent->_bf--;
else parent->_bf++;
//检查平衡因子状态
if (parent->_bf == 0) break;
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
//不平衡,旋转
else if (parent->_bf == 2 || parent->_bf == -2)
{
if (parent->_bf == 2)
{
//...
}
else
{
//...
}
break;
}
else
{
cout << "error: _bf";
break;
}
}
4. AVL树的旋转
上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左 子树增加了一层,导致以60为根的二叉树不平衡。
要让60平衡,只能将60左子树的高度减少一层,右子树增加一层, 即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点 的平衡因子即可。
在旋转过程中,有以下几种情况需要考虑: 1. 30节点的右孩子可能存在,也可能不存在 2. 60可能是根节点,也可能是子树如果是根节点,旋转完成后,要更新根节点如果是子树,可能是某个节点的左子树,也可能是右子树
a.右单旋
void _RotateR(Node* pParent)
{
// pSubL: pParent的左孩子
// pSubLR: pParent左孩子的右孩子,注意:该
PNode* pSubL = pParent->_pLeft;
PNode* pSubLR = pSubL->_pRight;
// 旋转完成之后,30的右孩子作为双亲的左孩子
pParent->_pLeft = pSubLR;
// 如果30的左孩子的右孩子存在,更新亲双亲
if (pSubLR) pSubLR->_pParent = pParent;
// 60 作为 30的右孩子
// 因为60可能是棵子树,因此在更新其双亲前必须先保存60的双亲
PNode pPParent = pParent->_pParent;
// 更新60的双亲
pParent->_pParent = pSubL;
// 更新30的双亲
pSubL->_pParent = pPParent;
// 如果60是根节点,根新指向根节点的指针
if (NULL == pPParent)
{
_pRoot = pSubL;
pSubL->_pParent = NULL;
}
else
{
// 如果60是子树,可能是其双亲的左子树,也可能是右子树
if (pPParent->_pLeft == pParent)
pPParent->_pLeft = pSubL;
else
pPParent->_pRight = pSubL;
}
// 根据调整后的结构更新部分节点的平衡因子
pParent->_bf = pSubL->_bf = 0;
}
b.左单旋
具体细节与右单旋一致。
void _RotateL(Node* pParent)
{
Node* RSub = pParent->_right;
Node* RSubL = RSub->_left;
Node* pPParent = pParent->_parent;
if (RSubL) RSubL->_parent = pParent;
pParent->_right = RSubL;
RSub->_left = pParent;
pParent->_parent = RSub;
RSub->_parent = pPParent;
if (pParent == _root) _root = RSub;
else
{
if (pPParent->_left == pParent) pPParent->_left = RSub;
else pPParent->_right = RSub;
}
//更新平衡因子
RSub->_bf = pParent->_bf = 0;
}
c.先左旋在右旋
将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再 考虑平衡因子的更新。
// 旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进
//行调整
void _RotateLR(PNode pParent)
{
PNode pSubL = pParent->_pLeft;
PNode pSubLR = pSubL->_pRight;
// 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节
//点的平衡因子
int bf = pSubLR->_bf;
// 先对30进行左单旋
_RotateL(pParent->_pLeft);
// 再对90进行右单旋
_RotateR(pParent);
if (1 == bf)
pSubL->_bf = -1;
else if (-1 == bf)
pParent->_bf = 1;
}
d. 先右旋在左旋
void _RotateRL(Node* pParent)
{
Node* RSub = pParent->_right;
Node* RSubL = RSub->_left;
int bf = RSubL->_bf;
_RotateR(RSub);
_RotateL(pParent);
RSub->_bf = 0;
if (bf == 1)
{
RSub->_bf = 0;
pParent->_bf = -1;
}
else if (bf == -1)
{
pParent->_bf = 0;
RSub->_bf = 1;
}
else
{
RSub->_bf = pParent->_bf = 0;
}
}
总结:
加入以parent为根的子树不平衡,即以parent的平衡因子为2或-2,分别考虑以下情况
1.parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为RSub
当RSub的平衡因子为1时,执行左单旋,
当RSub的平衡因子为-1时,执行左右双旋。
2.parent的平衡因子为-2 ,说明parent的左子树高,设parent的左子树的根为LSub
当LSub的平衡因子为-1时,执行右单旋。
当LSub的平衡因子为1时,执行做单旋。
当旋转接完成后,parent为根的子树高度已经降低,以及平衡,无需向上更新。
5.AVL树的验证
AVL树实在二叉搜索树的基础上加了平衡性的限制,因此要验证AVL树可以分为两步
1.验证其为二叉搜索树
如果中序遍历结果为有序,则为二叉搜索树
2.验证其为平衡树
1.每个子树高度差的绝对值不超过1
2.节点平衡因子正确
void InOrder()
{
_InOrder(_root);
}
int GETHeight()
{
return _GETHeight(_root);
}
bool IsBalance()
{
return _isBalance(_root);
}
bool _isBalance(Node* root)
{
if (root->_bf >= 2 || root->_bf <= -1) return false;
int HeightLeft = _GETHeight(root->_left);
int HeightRight = _GETHeight(root->_right);
if (abs(HeightRight - HeightLeft) > 1) return false;
return _isBalance(root->_left) && _isBalance(root->_right);
}
int _GETHeight(Node* root)
{
if (root == nullptr) return 0;
return max(_GETHeight(root->_left), _GETHeight(root->_right)) + 1;
}
void _InOrder(Node* root)
{
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
测试代码
void test_AVL01()
{
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
AVLTree<int> t1;
for (auto e : a)
{
t1.Insert(e);
//cout << "Insert:" << e << "->" << t1.IsBalance() << endl;
}
cout << t1.GETHeight() << endl;
t1.InOrder();
//cout << t1.IsBalance() << endl;
}
6.AVL树模拟代码
#pragma once
template<class T>
struct AVLTreeNode
{
AVLTreeNode(T key)
:_bf(0), _key(key)
{}
AVLTreeNode* _left = nullptr;
AVLTreeNode* _right = nullptr;
AVLTreeNode* _parent = nullptr;
int _bf; //平衡因子
T _key;
};
template<class T>
class AVLTree
{
typedef AVLTreeNode<T> Node;
public:
bool Insert(T key)
{
Node* newnode = new Node(key);
if (_root == nullptr)
{
_root = newnode;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
//寻找插入位置
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else return false;
}
//插入新的节点
newnode->_parent = parent;
if (key < parent->_key) parent->_left = newnode;
else parent->_right = newnode;
//更新平衡因子
//插入后AVL树平衡,无需调整
//插入后AVL树高度增加,继续向上调整
cur = newnode;
while (parent)
{
if (cur == parent->_left) parent->_bf--;
else parent->_bf++;
//检查平衡因子状态
if (parent->_bf == 0) break;
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
//不平衡,旋转
else if (parent->_bf == 2 || parent->_bf == -2)
{
if (parent->_bf == 2)
{
//左单旋
if (parent->_right->_bf == 1)
{
_RotateL(parent);
}
else
{
_RotateRL(parent);
}
}
else
{
//右单旋
if (parent->_left->_bf == -1)
{
_RotateR(parent);
}
else
{
_RotateLR(parent);
}
}
break;
}
else
{
cout << "error: _bf";
break;
}
}
return true;
}
Node* Find(const T& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_key) cur = cur->_right;
else if (key < cur->_key) cur = cur->_left;
else return cur;
}
return nullptr;
}
size_t Size()
{
return _Size(_root);
}
void InOrder()
{
_InOrder(_root);
}
int GETHeight()
{
return _GETHeight(_root);
}
bool IsBalance()
{
return _isBalance(_root);
}
private:
bool _isBalance(Node* root)
{
if (root->_bf >= 2 || root->_bf <= -1) return false;
int HeightLeft = _GETHeight(root->_left);
int HeightRight = _GETHeight(root->_right);
if (abs(HeightRight - HeightLeft) > 1) return false;
return _isBalance(root->_left) && _isBalance(root->_right);
}
int _GETHeight(Node* root)
{
if (root == nullptr) return 0;
return max(_GETHeight(root->_left), _GETHeight(root->_right)) + 1;
}
void _InOrder(Node* root)
{
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
size_t _Size(Node* root)
{
if (root == nullptr) return 0;
size_t SizeLeft = _Size(root->_left);
size_t SizeRight = _Size(root->_right);
return SizeLeft + SizeRight + 1;
}
//右旋
void _RotateR(Node* pParent)
{
Node* LSub = pParent->_left;
Node* LSubR = LSub->_right;
Node* pPParent = pParent->_parent;
if(LSubR)LSubR->_parent = pParent;
pParent->_left = LSubR;
LSub->_right = pParent;
pParent->_parent = LSub;
LSub->_parent = pPParent;
if (pParent == _root) _root = LSub;
else
{
if (pPParent->_left == pParent) pPParent->_left = LSub;
else pPParent->_right = LSub;
}
//更新平衡因子
LSub->_bf = pParent->_bf = 0;
}
//左旋
void _RotateL(Node* pParent)
{
Node* RSub = pParent->_right;
Node* RSubL = RSub->_left;
Node* pPParent = pParent->_parent;
if (RSubL) RSubL->_parent = pParent;
pParent->_right = RSubL;
RSub->_left = pParent;
pParent->_parent = RSub;
RSub->_parent = pPParent;
if (pParent == _root) _root = RSub;
else
{
if (pPParent->_left == pParent) pPParent->_left = RSub;
else pPParent->_right = RSub;
}
//更新平衡因子
RSub->_bf = pParent->_bf = 0;
}
void _RotateRL(Node* pParent)
{
Node* RSub = pParent->_right;
Node* RSubL = RSub->_left;
int bf = RSubL->_bf;
_RotateR(RSub);
_RotateL(pParent);
RSub->_bf = 0;
if (bf == 1)
{
RSub->_bf = 0;
pParent->_bf = -1;
}
else if (bf == -1)
{
pParent->_bf = 0;
RSub->_bf = 1;
}
else
{
RSub->_bf = pParent->_bf = 0;
}
}
void _RotateLR(Node* pParent)
{
Node* LSub = pParent->_left;
Node* LSubR = LSub->_right;
int bf = LSubR->_bf;
_RotateL(LSub);
_RotateR(pParent);
LSub->_bf = 0;
if (bf == 0)
{
LSubR->_bf = LSubR->_bf = 0;
}
else if (bf == 1)
{
LSub->_bf = -1;
pParent->_bf = 0;
}
else if (bf == -1)
{
LSub->_bf = 0;
pParent->_bf = 1;
}
}
private:
Node* _root = nullptr;
};
void test_AVL01()
{
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
AVLTree<int> t1;
for (auto e : a)
{
int i = 1;
t1.Insert(e);
//cout << "Insert:" << e << "->" << t1.IsBalance() << endl;
}
cout << t1.GETHeight() << endl;
t1.InOrder();
//cout << t1.IsBalance() << endl;
}
void test_AVL02()
{
const int N = 10000000;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; i++)
{
v.push_back(rand() + i);
//cout << v.back() << endl;
}
size_t begin2 = clock();
AVLTree<int> t;
for (auto e : v)
{
t.Insert(e);
//cout << "Insert:" << e << "->" << t.IsBalance() << endl;
}
size_t end2 = clock();
cout << "Insert:" << end2 - begin2 << endl;
//cout << t.IsBalance() << endl;
cout << "Height:" << t.GETHeight() << endl;
cout << "Size:" << t.Size() << endl;
size_t begin1 = clock();
// 确定在的值
for (auto e : v)
{
t.Find(e);
}
// 随机值
/*for (size_t i = 0; i < N; i++)
{
t.Find((rand() + i));
}*/
size_t end1 = clock();
cout << "Find:" << end1 - begin1 << endl;
}