目录
前言:
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下;因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:
当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度;
AVL树的定义
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树;
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1);
AVL树结点的定义
cpp
//结点类
//k(key) v(value)
template<class k,class v>
struct AVLTreeNode
{
AVLTreeNode<k, v>* _left;//指向左孩子
AVLTreeNode<k, v>* _right;//指向右孩子
AVLTreeNode<k, v>* _parent;//指向父节点
int _bf = 0;//平衡因子
pair<k, v> _kv;//有效数据
//开辟结点时需要构造函数
AVLTreeNode(const pair<k,v>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
,_kv(kv)
{}
};
AVL树的插入
一棵树是AVL树的前提这棵树为搜索二叉树,因此AVL树的插入可以分为如下两步:
- 按照二叉搜索树的方式插入新节点
- 调整节点的平衡因子
cpp
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;
}
//parent记录父节点的位置,cur寻找插入位置
Node* parent = nullptr;
Node* cur = _root;
while (cur != nullptr)
{
//根据键值key比较,确定迭代左子树或是右子树
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;
//更新结点的平衡因子
//......
return true;
}
private:
Node* _root = nullptr;
};
**思考:**插入结点后会影响哪些结点的平衡因子呢?
新增节点的部分祖先结点,其中父节点的平衡因子必定发生改变;
平衡因子=右子树的高度 - 左子树的高度
若新增节点cur插入到parent左侧,那么parent的平衡因子-1;
若新增节点cur插入到parent右侧,那么parent的平衡因子+1;
是否继续更新其余的祖先结点,取决于以parent作为根节点的子树高度是否发生变化若子树的高度发生变化,则更新爷爷结点的平衡因子;
若子树的高度未发生变化,则不更新爷爷结点的平衡因子;
插入结点后,parent的平衡因子可能出现三种情况:0 正负1 正负2
若插入结点后parent的平衡因子为0,说明插入前parent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,高度没有发生变化,不再向上更新其余祖先结点;
若插入结点后parent的平衡因子为正负1,说明插入前parent的平衡因子一定为0,插入后被调整成正负1,此时满足AVL树的性质,高度发生变化,需要向上更新其余祖先结点;
插入结点后在cur的迭代过程中其parent的平衡因子为正负2,则parent的平衡因子违反平衡树的性质,需要对其进行旋转处理;
cpp
template <class k,class v>
class AVLTree
{
typedef AVLTreeNode<k, v> Node;
public:
//首先按照搜索二叉树的规则插入数据
//其次更新平衡因子bf
bool Insert(const pair<k,v>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
//parent记录父节点的位置,cur寻找插入位置
Node* parent = nullptr;
Node* cur = _root;
while (cur != nullptr)
{
//根据键值key比较,确定迭代左子树或是右子树
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;
//更新平衡因子_bf
while (parent)
{
//更新父节点的平衡因子
//若cur是parent的左孩子,则parent->_bf--;
if (cur == parent->_left)
{
parent->_bf--;
}
//若cur是parent的右孩子,则parent->_bf++;
else
{
parent->_bf++;
}
//更新祖先结点的平衡因子,根据以parent作为根结点的子树的高度是否发生变化进行更新
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)
{
//迭代过程中以parent作为根结点的子树违背平衡规则---旋转处理
}
//插入前该树不是AVL树
else
{
assert(false);
}
}
return true;
}
private:
Node* _root=nullptr;
};
AVL树的旋转
当结点的平衡因子出现正负2时,需要对子树进行旋转处理,旋转的目的首先要维持搜索二叉树的规则,其次要将当前子树由不平衡转化为平衡,最后要降低当前子树的高度;
旋转总共分为四种:左单旋 右单旋 先左单旋后右单旋 先右单旋后左单旋;
左单旋
插入结点后,在cur迭代过程中,出现parent结点的平衡因子为2并且cur的平衡因子为1时诱发左单旋;
cpp
//左单旋
void RotateLeft(Node* parent)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
Node* ppNode = parent->_parent;
parent->_right = SubRL;
parent->_parent = SubR;
SubR->_left = parent;
if (SubRL != nullptr)
{
SubRL->_parent = parent;
}
//当前树为整棵树,更新_root
if (parent == _root)
{
_root = SubR;
SubR->_parent = nullptr;
}
//当前树为某棵树的子树
else
{
if (ppNode->_left == parent)
{
ppNode->_left = SubR;
}
else
{
ppNode->_right = SubR;
}
SubR->_parent = ppNode;
}
//更改平衡因子
SubR->_bf = 0;
parent->_bf = 0;
}
右单旋
cpp
//右单旋
void RotateRight(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
Node* ppNode = parent->_parent;
parent->_left = SubLR;
parent->_parent = SubL;
if (SubLR != nullptr)
{
SubLR->_parent = parent;
}
SubL->_right = parent;
//当前树为整棵树
if (ppNode==nullptr)
{
_root = SubL;
SubL->_parent = nullptr;
}
//当前树为某棵树的子树
else
{
if (ppNode->_left == parent)
{
ppNode->_left = SubL;
}
else
{
ppNode->_right = SubL;
}
SubL->_parent = ppNode;
}
SubL->_bf = 0;
parent->_bf = 0;
}
插入结点后,在cur迭代过程中,出现parent结点的平衡因子为-2并且cur的平衡因子为-1时诱发右单旋;
左右双旋
当在子树b中插入节点,无论进行左单旋或者右单旋,都不能使得子树达到平衡;
- 左右双旋情形分析汇总如下
思考:如何调节h=0与h>0(两种情形)情形下SubL、SubLR、parent的平衡因子?
若SubLR->_bf = -1,则d子树插入节点;
若SubLR->_bf = 1,则e子树插入节点;
若SubLR->_bf = 0,则SubLR本身即为新增节点;
注:双旋中的单旋会更改SubLR的平衡因子,插入结点后应记录旋转前SubLR的平衡因子;
cpp
//先左单旋后右单旋
//根据SubLR的平衡因子调节三种情形下SubL parent SubLR的平衡因子
void RotateLR(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
int bf = SubLR->_bf;
//双旋中的单旋会更改平衡因子,应记录旋转之前SubLR中的平衡因子
RotateLeft(parent->_left);
RotateRight(parent);
//SubLR本身为新增结点
if (bf == 0)
{
parent->_bf = 0;
SubLR->_bf = 0;
SubL->_bf = 0;
}
//子树b被拆解的左子树d插入结点
else if (bf == -1)
{
parent->_bf = 1;
SubLR->_bf = 0;
SubL->_bf = 0;
}
//子树b被拆解的右子树e插入结点
else if (bf == 1)
{
parent->_bf = 0;
SubLR->_bf = 0;
SubL->_bf = -1;
}
else
{
assert(false);
}
}
右左双旋
cpp
//先右单旋后左单旋
void RotateRL(Node* parent)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
int bf = SubRL->_bf;
RotateRight(SubR);
RotateLeft(parent);
if (bf == 0)
{
parent->_bf = 0;
SubR->_bf = 0;
SubRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
SubR->_bf = 1;
SubRL->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
SubR->_bf = 0;
SubRL->_bf = 0;
}
else
{
assert(false);
}
}
Insert()函数总体代码
cpp
bool Insert(const pair<k,v>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
//parent记录父节点的位置,cur寻找插入位置
Node* parent = nullptr;
Node* cur = _root;
while (cur != nullptr)
{
//根据键值key比较,确定迭代左子树或是右子树
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;
//更新平衡因子_bf
while (parent)
{
//更新父节点的平衡因子
//若cur是parent的左孩子,则parent->_bf--;
if (cur == parent->_left)
{
parent->_bf--;
}
//若cur是parent的右孩子孩子,则parent->_bf++;
else
{
parent->_bf++;
}
//更新祖先结点的平衡因子,根据以parent作为根结点的子树的高度是否发生变化进行更新
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)
{
//迭代过程中以parent作为根结点的子树违背平衡规则---旋转处理
//左单旋场景
if (parent->_bf == 2 && cur->_bf == 1)
{
RotateLeft(parent);
}
//右单旋场景
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateRight(parent);
}
//先左旋后右旋场景
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
//先右旋后左旋场景
else
{
RotateRL(parent);
}
break;
}
//插入前该树不是AVL树
else
{
assert(false);
}
}
return true;
}
AVL树的验证
AVL树的验证:
- 首先检查其是否为搜索二叉树---利用二叉树的中序遍历得到有序序列验证;
- 求取每个结点子树的高度并计算左右子树高度差,验证高度差的绝对值小于2;
- 验证平衡因子是否计算正确;
cpp
//中序遍历
void InOrder()
{
_InOrder(_root);
}
//中序遍历子函数
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << (root->_kv).first << ":" << (root->_kv).second << endl;
_InOrder(root->_right);
}
中序遍历结果为有序只能证明为搜索二叉树,要证明二叉树是AVL树还需验证二叉树的平衡性,在此过程中可以检查每个结点的平衡因子是否计算正确;
验证方案一:
cpp
//求取当前树的高度
int Height(Node* root)
{
if (root == nullptr)
return 0;
int LeftHeight = Height(root->_left);
int RightHeight = Height(root->_right);
return LeftHeight > RightHeight ? LeftHeight + 1 : RightHeight + 1;
}
//前序遍历验证其是否为平衡树,验证根时求取右子树与左子树的高度并判断平衡因子是否计算正确
bool _IsBalance(Node* root)
{
if (root == nullptr)
return true;
//求取左子树,右子树的高度
int LeftHeight = Height(root->_left);
int RightHeight = Height(root->_right);
//判断当前树是否为平衡树
if (abs(RightHeight - LeftHeight) >= 2)
{
cout << (root->_kv).first << ":" << (root->_kv).second << "不平衡" << endl;
return false;
}
//判断当前根结点处的平衡因子是否正常
if ((RightHeight - LeftHeight)!=root->_bf)
{
cout << (root->_kv).first << ":" << (root->_kv).second << "平衡因子异常" << endl;
return false;
}
return _IsBalance(root->_left) && _IsBalance(root->_right);
}
采用前序遍历验证其是否为平衡树,验证根时求取右子树与左子树的高度,当验证下一层又要求下一层左子树与右子树的高度,并未保留上一次计算时的数据,存在大量冗余的计算,导致效率低下;
验证方案二:
采用后序遍历验证其是否为平衡树,验证步骤如下:
- 从叶子结点处开始计算每课子树的高度(每棵子树的高度 = 左右子树中高度的较大值 + 1);
- 先判断左子树是否是平衡二叉树;
- 再判断右子树是否是平衡二叉树;
- 若左右子树均为平衡二叉树,则返回当前子树的高度给上一层,继续判断上一层的子树是否是平衡二叉树,直到判断到根为止;(若判断过程中,某一棵子树不是平衡二叉树,则该树也就不是平衡二叉树)
cpp
bool IsBalance()
{
int height = 0;
return _IsBalance(_root, height);
}
bool _IsBalance(Node* root, int& height)
{
if (root == nullptr)
{
height = 0;
return true;
}
int leftHeight = 0, rightHeight = 0;
if (!_IsBalance(root->_left, leftHeight)
|| !_IsBalance(root->_right, rightHeight))
{
return false;
}
if (abs(rightHeight - leftHeight) >= 2)
{
cout << root->_kv.first << "不平衡" << endl;
return false;
}
if (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
return true;
}
欢迎大家批评指正,博主会持续输出优质内容,谢谢各位观众老爷观看,码字画图不易,希望大家给个一键三连支持~ 你的支持是我创作的不竭动力~