文章目录
- 平衡二叉搜索树(AVL树)
- 1.AVL树的概念和介绍
- 2.AVL树的简单实现
- 2.1AVL树的插入
- 2.2AVL树的旋转
- 2.2.1左旋
- 2.2.2右旋
- 2.2.3右左双旋
- 2.2.4左右双旋
- 全部源码
平衡二叉搜索树(AVL树)
为什么要引入平衡二叉搜索树?
在之前我们学习了二叉搜索树,二叉搜索树的结构类似于一个倒置的树,而左子树的值小于根节点的值,右节点的值大于根节点的值,这种结构使得二叉搜索树在处理有序数据时非常高效。但是如果在传入的数据为有序或接近有序,二叉搜索树会退化为单支树,类似链表、此时二叉搜索树在查找、插入、删除的优异性能都消失了。
同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N
1.AVL树的概念和介绍
对此我们引入了平衡二叉搜索树,也叫AVL树。
AVL树是由两位俄罗斯的数学家G. M. Adelson-Velsky和E. M. Landis在1962年的论文《An algorithm for the organization of information》中发明的。这是一种自平衡二叉查找树,任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
(1)它的左右子树都是AVL树
(2)左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)
2.AVL树的简单实现
和实现二叉搜索树的节点类似,只需要考虑多平衡因子和父子节点的关系即可。
以下为AVL树节点的定义:
template<class K, class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf; //平衡因子
AVLTreeNode(const pair<K, V>& kv)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
定义AVL树类:
template<class K,class V>
class AVLTree
{
//便于书写Node节点
typedef AVLTreeNode<K, V> Node;
public:
//AVL树增删查改函数的实现
private:
Node* _root = nullptr;
};
2.1AVL树的插入
AVL树的插入操作包括插入节点和平衡调整。具体实现步骤如下:
(1)插入节点:首先,按照普通二叉搜索树的插入方法进行插入。
(2)平衡调整:插入节点后,从插入节点开始沿着通向根节点的路径向上检查所有节点,观察它们是否仍然保持平衡。如果某个节点的平衡因子绝对值大于1,就需要进行旋转操作以重新平衡这个树。旋转操作包括单旋转和双旋转。
插入节点实现:
//AVL树插入一个节点
bool AVLInsert(const pair<K, V>& kv)
{
//创建cur指向根节点
Node* cur = _root;
Node* parent = nullptr;
//如果AVL树为空,直接返回创建的新节点
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
//如果AVL树不为空,寻找可以插入的节点
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->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
//AVL树要保持平衡,控制平衡因子为-1、0、1
//while()
return true;
}
平衡调整实现:
新节点插入之前:
平衡因子=右子树的高度-左子树的高度,cur插入后,parent的平衡因子一定需要调整,在插入之前,parent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:
(1)如果cur插入到parent的左侧,左子树高+1,只需给parent的平衡因子-1即可。
(2)如果cur插入到parent的右侧,右子树高+1,只需给parent的平衡因子+1即可。
while (parent)
{
if (cur == parent->_left)//cur插入在parent左边
{
parent->_bf--;
}
else if (cur == parent->_right)//cur插入在parent右边
{
parent->_bf++;
}
}
新节点插入之后:
当cur插入以后,parent的平衡因子可能有三种情况:0,+1 \ -1, +2 \ -2
(3)如果parent的平衡因子为0,说明插入之前parent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功且无需旋转。
(4)如果pParent的平衡因子为+1 \ -1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新,判断是否旋转。
(5)如果pParent的平衡因子为+2 \ -2,则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)
{
//子树不平衡了,需要旋转
}
else//如果有其他情况直接报错
{
assert(false);
}
2.2AVL树的旋转
AVL的旋转分为4种情况:
(1)左单旋转(Left Single Rotation):当新节点cur插入在较高右子树的右侧时进行左单旋转。具体步骤为将curleft变为parent的右子树,将parent节点变为cur的左子树,然后更新相关节点的指向。如果parent是根节点,那么cur将成为新的根节点。
(2)右单旋转(Right Single Rotation):当新节点cur插入在较低左子树的左侧时进行右单旋转。具体步骤为将curright变为parent的左子树,将parent节点变为cur的右子树,然后更新相关节点的指向。
(3)右左双旋转(Right Left Double Rotation):先进行右单旋转,再进行左单旋转。当新节点cur插入在的左子树的右侧时,先进行右单旋转,再进行左单旋转。
(4)左右双旋转(Left Right Double Rotation):先进行左单旋转,再进行右单旋转。当新节点cur插入在的右子树的左侧时,先进行左单旋转,再进行右单旋转。
else if (parent->_bf == 2 || parent->_bf == -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)//右左双旋
{
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋
{
RotateLR(parent);
}
break;
}
2.2.1左旋
(1)左单旋转(Left Single Rotation):当新节点cur插入在较高右子树的右侧时进行左单旋转。具体步骤为将curleft变为parent的右子树,将parent节点变为cur的左子树,然后更新相关节点的指向。如果parent是根节点,那么cur将成为新的根节点。
//左旋
void RotateL(Node* parent)
{
//创建cur节点和父节点
Node* cur = parent->_right;
Node* curleft = cur->_left;
//将右子树的左节点连接在parent的右节点上
parent->_right = curleft;//关键步骤1
if (curleft)//如果右子树的左节点不为空,连接一下父节点
{
curleft->_parent = parent;
}
//将父节点断开连接到原来右节点的左子树上,降低二叉树高度
cur->_left = parent;//关键步骤2
//仍需要处理特殊情况
//如果原父节点不为_root,保存父节点的父节点
Node* ppnode = parent->_parent;
//两个节点连接
parent->_parent = cur;
//如果父节点为_root,直接更新
if (parent == _root)
{
_root = cur;
cur->_parent = nullptr;
}
else//如果父节点不为_root,需要重新连接
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
}
else//判断是父父节点的左节点还是右节点
{
ppnode->_right = cur;
}
//反转将cur节点连接父节点
cur->_parent = ppnode;
}
//更新平衡因子
parent->_bf = cur->_bf = 0;
}
2.2.2右旋
(2)右单旋转(Right Single Rotation):当新节点cur插入在较低左子树的左侧时进行右单旋转。具体步骤为将curright变为parent的左子树,将parent节点变为cur的右子树,然后更新相关节点的指向。
//右旋
void RotateR(Node* parent)
{
//取子节点和子节点中的最大节点,作为父节点的左子树
Node* cur = parent->_left;
Node* curright = cur->_right;
//将父节点和左子树中的最大节点连接,降低层高
parent->_left = curright;//重要步骤1
if (curright)
{
curright->_parent = parent;
}
//将子节点作为根,并将原来父节点连接在子节点的右节点
cur->_right = parent;//重要步骤2
//上面的代码基本可以完成右旋操作,但是还要考虑parent是否为_root
Node* ppnode = parent->_parent;
parent->_parent = cur;
if (ppnode == nullptr)//parent为_root
{
_root = cur;
cur->_parent = nullptr;
}
else//parent不为_root
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
}
else//判断cur节点在原来子树的右边还是左边,并且连接
{
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
//右旋完成,更新平衡因子
parent->_bf = cur->_bf = 0;
}
2.2.3右左双旋
双旋源码放在全部源码中。
(3)右左双旋转(Right Left Double Rotation):先进行右单旋转,再进行左单旋转。当新节点cur插入在的左子树的右侧时,先进行右单旋转,再进行左单旋转。
2.2.4左右双旋
(4)左右双旋转(Left Right Double Rotation):先进行左单旋转,再进行右单旋转。当新节点cur插入在的右子树的左侧时,先进行左单旋转,再进行右单旋转。
全部源码
#pragma once
#include<assert.h>
template<class K, class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf; //平衡因子
AVLTreeNode(const pair<K, V>& kv)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
template<class K,class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
//AVL树插入一个节点
bool AVLInsert(const pair<K, V>& kv)
{
//创建cur指向根节点
Node* cur = _root;
Node* parent = nullptr;
//如果AVL树为空,直接返回创建的新节点
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
//如果AVL树不为空,寻找可以插入的节点
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->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
//AVL树要保持平衡,控制平衡因子为-1、0、1
while (parent)
{
if (cur == parent->_left)
{
parent->_bf--;
}
else if (cur == parent->_right)
{
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 && cur->_bf == 1)//左旋
{
RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)//右旋
{
RotateR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)//右左双旋
{
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋
{
RotateLR(parent);
}
break;
}
else
{
assert(false);
}
}
return true;
}
//左旋
void RotateL(Node* parent)
{
//创建cur节点和父节点
Node* cur = parent->_right;
Node* curleft = cur->_left;
//将右子树的左节点连接在parent的右节点上
parent->_right = curleft;//关键步骤1
if (curleft)//如果右子树的左节点不为空,连接一下父节点
{
curleft->_parent = parent;
}
//将父节点断开连接到原来右节点的左子树上,降低二叉树高度
cur->_left = parent;//关键步骤2
//仍需要处理特殊情况
//如果原父节点不为_root,保存父节点的父节点
Node* ppnode = parent->_parent;
//两个节点连接
parent->_parent = cur;
//如果父节点为_root,直接更新
if (parent == _root)
{
_root = cur;
cur->_parent = nullptr;
}
else//如果父节点不为_root,需要重新连接
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
}
else//判断是父父节点的左节点还是右节点
{
ppnode->_right = cur;
}
//反转将cur节点连接父节点
cur->_parent = ppnode;
}
//更新平衡因子
parent->_bf = cur->_bf = 0;
}
//右旋
void RotateR(Node* parent)
{
//取子节点和子节点中的最大节点,作为父节点的左子树
Node* cur = parent->_left;
Node* curright = cur->_right;
//将父节点和左子树中的最大节点连接,降低层高
parent->_left = curright;//重要步骤1
if (curright)
{
curright->_parent = parent;
}
//将子节点作为根,并将原来父节点连接在子节点的右节点
cur->_right = parent;//重要步骤2
//上面的代码基本可以完成右旋操作,但是还要考虑parent是否为_root
Node* ppnode = parent->_parent;
parent->_parent = cur;
if (ppnode == nullptr)//parent为_root
{
_root = cur;
cur->_parent = nullptr;
}
else//parent不为_root
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
}
else//判断cur节点在原来子树的右边还是左边,并且连接
{
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
//右旋完成,更新平衡因子
parent->_bf = cur->_bf = 0;
}
//右左双旋
void RotateRL(Node* parent)
{
//找到双旋节点
Node* cur = parent->_right;
Node* curleft = cur->_left;
int bf = curleft->_bf;//记录平衡因子
RotateR(parent->_right);//先右旋cur,让节点保持在一条直线上
RotateL(parent);//左旋parent
//不同情况更新不同的平衡因子
if (bf == 0)//新增的节点就是所需要右左旋的节点
{
cur->_bf = 0;
curleft->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)//新增节点的父节点平衡因子为1,新增在了左边
{
cur->_bf = 0;
curleft->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)//新增节点的父节点平衡因子为-1,新增在了右边
{
cur->_bf = 1;
curleft->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
//左右双旋
void RotateLR(Node* parent)
{
//找到双旋节点
Node* cur = parent->_left;
Node* curright = cur->_right;
int bf = curright->_bf;
RotateL(parent->_left);//先右旋
RotateR(parent);//再左旋
//更新平衡因子,新增节点更新位置不同,节点的平衡因子也不同
if (bf == 0)
{
cur->_bf = 0;
curright->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)
{
cur->_bf = -1;
curright->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1)
{
cur->_bf = 0;
curright->_bf = 0;
parent->_bf = 1;
}
}
//求AVL树高
int AVLHeight()
{
return _AVLHeight(_root);
}
int _AVLHeight(Node* root)
{
if (root == nullptr)
{
return 0;
}
int leftHeight = _AVLHeight(root->_left);
int rightHeight = _AVLHeight(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
//判断AVL树是否平衡
bool AVLIsBalance()
{
return _AVLIsBalance(_root);
}
bool _AVLIsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHeight = _AVLHeight(root->_left);
int rightHeight = _AVLHeight(root->_right);
if (rightHeight - leftHeight != root->_bf)
{
cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
return false;
}
return abs(rightHeight = leftHeight) < 2 && _AVLIsBalance(root->_left)&& _AVLIsBalance(root->_right);
}
private:
Node* _root = nullptr;
};