目录
AVL树介绍
二叉搜索树在一些场景下可能会退化成单边树,这样一来查找数据的效率就接近在顺序表中查找,就失去了它查找效率高的优点。
基于上述场景的存在,大佬们给出了相应的解决办法:当有新的节点插入后,如果左右子树的高度差超过1,这时就进行调整(调整的过程后续讨论)。经过调整的二叉搜索树始终保持着高度平衡的状态,我们将这样的二叉搜索树叫做AVL树。
AVL树的特点:它的左右子树都是AVL树,左右子树的高度差的绝对值不超过1,高度差我们将其叫做平衡因子(平衡因子用 -1/0/1表示)。
下述平衡因子的计算,都是采用右子树高度-左子树高度的计算方式:
当平衡因子超过1的时候,该树已经不在平衡,需要进行调整。
AVL树的模拟实现
节点部分
在二叉搜索树的基础上,AVL树的节点增加了记录平衡因子的变量,和一个指向父亲节点的父指针。
代码实现方面,对应上图的逻辑结构,有一个指向左孩子的指针、一个指向右孩子的指针和一个指向父节点的指针,数据域方面存储键值对,除此之外还增加了一个记录平衡因子的变量。
template<class K, class V>
struct AVLNode
{
AVLNode<K, V>* _left;
AVLNode<K, V>* _right;
AVLNode<K, V>* _parent;
pair<K, V> _kv;
int _bf;
AVLNode(const pair<K, V> kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
插入
为了代码的简洁:
typedef AVLNode<K, V> Node;
AVL树插入数据的过程相较于二叉搜索树更为复杂,因为它要保持树结构的平衡和平衡因子的更新。在插入过程中的前半部分和二叉搜索树的逻辑相同:根节点为空直接插入,根节点不为空将新节点的key值和root的key值进行比较,新节点key值大时向右继续寻找要插入的位置,反之向左寻找,如果找到相同的key返回false,不能插入。反之找到新节点要插入的位置。紧接着判断新节点的key值是大于父节点的key值,还是小于确定最终要插入的位置。
bool insert(pair<K, V> kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
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;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//调整树结构、平衡因子
//.......
return true
}
上述过程结束后,节点已经成功插入,接下来就是我们要重点处理的难题。节点的插入会改变树的结构,插入前parent的平衡因子有三种情况(1/0/-1),插入后parent的节点有五中情况(0、1、-1、2、-2)。
节点插入后平衡因子的不同状态
●根据parent的指向,判断节点的插入是在左还是右,更新平衡因子:
●新节点插入后parent的平衡因子变为0,说明parent在插入前的平衡因子是1或者-1,节点插入后parent的平衡因子被调整为0,插入成功。
●如果parent在插入新节点前的平衡因子为0,当新节点插入后,parent的平衡因子变成1或者-1。这种情况下以paren为根节点的子树高度发生了变化,在子树调整完平衡因子后,要继续向上更新平衡因子。
●如果parent在插入新节后的平衡因子为2或者-2,结构的平衡性被破坏,要进行调整,根据不同的情况,调整的方法分为以下几类:
右单旋
1.右单旋:新节点插入较高左子树的左侧,parent->bf = -2、cur->bf =-1。
代码实现:根据上图的旋转过程,用代码对其实现,就上述数据而言,40的右孩子变成了80的左孩子,80变成了40的右孩子,40变成了该树的根。需要注意的是,当前旋转的树可能只是一棵“参天大树的部分”,在parent父节点不为空的情况下,注意指向关系的处理,处理subLR也是如此,注意细节的把控!
void RotaR(Node* pparent)
{
Node* subL = pparent->_left;
Node* subLR = subL->_right;
pparent->_left = subLR;
if (subLR != nullptr)
{
subLR->_parent = pparent;
}
Node* pNode = pparent->_parent;
subL->_right = pparent;
pparent->_parent = subL;
if (pNode == nullptr)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (pNode->_left == pparent)
{
pNode->_left = subL;
}
else
{
pNode->_right = subL;
}
subL->_parent = pNode;
}
//调整平衡因子
pparent->_bf = subL->_bf = 0;
}
左单旋
2.左单旋:新节点插入较高右子树的右侧,parent->bf=2,cur->bf=1;
就上图数据而言,旋转过程中,B变成了40的右子树,40变成了80的左子树,80变成该树的根。和上述右单旋的处理一样,需要注意parent->_parent是否存在,subRL的连接细节等等。
void RotaL(Node* pparent)
{
Node* subR = pparent->_right;
Node* subRL = subR->_left;
pparent->_right = subRL;
if (subRL)
{
subRL->_parent = pparent;
}
Node* pNode = pparent->_parent;
pparent->_parent = subR;
subR->_left = pparent;
if (pNode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (pNode->_left == pparent)
{
pNode->_left = subR;
}
else if (pNode->_right == pparent)
{
pNode->_right = subR;
}
subR->_parent = pNode;
}
//调整平衡因子
pparent->_bf = subR->_bf = 0;
}
除了上述的两种情况外,还有更加恶劣的场景:
左右双旋
3.左右双旋:新节点插入较高左子树的右侧,parent->bf = -2,cur->bf=1先左单旋再右单旋。
如上图所示,当新节点插入的位置是较高左子树的右侧时,单次旋转不能维护树的平衡。首先第一步,将以parent->left为根节点的子树进行左旋操作,在将已parent的树进行一个右旋操作。这时树已经平衡,单旋的过程和上述的讲解是一样的,下述的代码实现对单旋进行了代码的复用,但是需要注意的是平衡因子的处理。
void RotaLR(Node* pparent)
{
//在旋转之前要记录一下subLR的平衡因子,它的值能够确定新增节点的位置
Node* subL = pparent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotaL(pparent->_left);
RotaR(pparent);
if (bf == -1)
{
//新增节点在左边
pparent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
//新增节点在右边
pparent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0)
{
//subLR就是新增节点
pparent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
右左双旋
4.右左双旋:新节点插入较高右子树的左侧,parent->bf = 2,cur->bf = -1先右单旋再左单旋
类似左右单旋,需要用右左单旋处理的场景是当新节点插入的位置是较高右子树的左侧时。首先将已parent->right为根节点的子树做一个右单旋的处理,接着将已parent为根节点的树进行左单旋。代码复用左单旋和右单旋的操作。一样需要注意平衡因子的处理。
void RotaRL(Node* pparent)
{
Node* subR = pparent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotaR(pparent->_right);
RotaL(pparent);
if (bf == 1)
{
//subRL右侧新增
pparent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
//subRL左侧新增
pparent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 0)
{
//本身就是新增
pparent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
行文至此,AVL树的插入操作就基本实现完成了,但是接下来的测试也是比较麻烦的事情,出现错误需要有更多的耐心去调试和纠错。为了更好的发现错误,这里实现一个检测左右树高度差的接口,和中序遍历接口。
正确度测试
●计算树的高度
int TreeHight(Node* root)
{
if (root == nullptr)
{
return 0;
}
int HightL = TreeHight(root->_left);
int HightR = TreeHight(root->_right);
return HightL > HightR ? HightL+1: HightR+1;
}
●检测树是否平衡
bool isBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int HightL = TreeHight(root->_left);
int HightR = TreeHight(root->_right);
if (HightR - HightL != root->_bf)
{
cout<<root->_kv.first<< "的平衡因子异常" << endl;
return false;
}
return abs(HightL - HightR)<2
&& isBalance(root->_left)
&& isBalance(root->_right);
}
●中序遍历
void _Inorder(Node* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.first << endl;
_Inorder(root->_right);
}
测试:对多组数据进行测试,包括左右旋、右左旋的场景
void AVLTest1()
{
AVL<int,int> avl;
//int arr[] = { 7,1,5,3,9,8,4,6};
//int arr[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
for (auto& e : arr)
{
avl.insert(make_pair(e,e));
}
avl.Inorder();
avl.isBalance();
}
暴力测试法:
void TestAVLTree2()
{
srand((size_t)time(nullptr));
const size_t N = 100000;
AVL<int, int> t;
for (size_t i = 0; i < N; ++i)
{
size_t x = rand();
t.insert(make_pair(x, x));
//cout << t.isBalance() << endl;
}
t.Inorder();
cout << t.isBalance() << endl;
}
整体代码
#pragma once
#include <assert.h>
template<class K, class V>
struct AVLNode
{
AVLNode<K, V>* _left;
AVLNode<K, V>* _right;
AVLNode<K, V>* _parent;
pair<K, V> _kv;
int _bf;
AVLNode(const pair<K, V> kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
template<class K,class V>
class AVL
{
typedef AVLNode<K, V> Node;
public:
bool insert(pair<K, V> kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
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;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//插入后平衡因子受到破坏,
while (parent)//parent为空时代表已经调整到了根
{
//更新平衡因子
if (parent->_right == cur)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
//说明插入节点前parent的平衡因子是0,
//插入后一边高一边低,向上继续调整平衡因子
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//这时树的形状已经不平衡需要调整
//左单旋
if (parent->_bf == 2 && cur->_bf == 1)
{
RotaL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
//右单旋
RotaR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
//左右旋
RotaLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
//右左旋
RotaRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
void Inorder()
{
_Inorder(_root);
}
int TreeHight()
{
return TreeHight(_root);
}
bool isBalance()
{
return isBalance(_root);
}
private:
bool isBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int HightL = TreeHight(root->_left);
int HightR = TreeHight(root->_right);
if (HightR - HightL != root->_bf)
{
cout<<root->_kv.first<< "的平衡因子异常" << endl;
return false;
}
return abs(HightL - HightR)<2
&& isBalance(root->_left)
&& isBalance(root->_right);
}
int TreeHight(Node* root)
{
if (root == nullptr)
{
return 0;
}
int HightL = TreeHight(root->_left);
int HightR = TreeHight(root->_right);
return HightL > HightR ? HightL+1: HightR+1;
}
void _Inorder(Node* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.first << endl;
_Inorder(root->_right);
}
void RotaL(Node* pparent)
{
Node* subR = pparent->_right;
Node* subRL = subR->_left;
pparent->_right = subRL;
if (subRL)
{
subRL->_parent = pparent;
}
Node* pNode = pparent->_parent;
pparent->_parent = subR;
subR->_left = pparent;
if (pNode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (pNode->_left == pparent)
{
pNode->_left = subR;
}
else if (pNode->_right == pparent)
{
pNode->_right = subR;
}
subR->_parent = pNode;
}
//调整平衡因子
pparent->_bf = subR->_bf = 0;
}
void RotaR(Node* pparent)
{
Node* subL = pparent->_left;
Node* subLR = subL->_right;
pparent->_left = subLR;
if (subLR != nullptr)
{
subLR->_parent = pparent;
}
Node* pNode = pparent->_parent;
subL->_right = pparent;
pparent->_parent = subL;
if (pNode == nullptr)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (pNode->_left == pparent)
{
pNode->_left = subL;
}
else
{
pNode->_right = subL;
}
subL->_parent = pNode;
}
//调整平衡因子
pparent->_bf = subL->_bf = 0;
}
void RotaLR(Node* pparent)
{
//在旋转之前要记录一下subLR的平衡因子,它的值能够确定新增节点的位置
Node* subL = pparent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotaL(pparent->_left);
RotaR(pparent);
if (bf == -1)
{
//新增节点在左边
pparent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
//新增节点在右边
pparent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0)
{
//subLR就是新增节点
pparent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
void RotaRL(Node* pparent)
{
Node* subR = pparent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotaR(pparent->_right);
RotaL(pparent);
if (bf == 1)
{
//subRL右侧新增
pparent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
//subRL左侧新增
pparent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 0)
{
//本身就是新增
pparent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
private:
Node* _root = nullptr;
};