目录
上一篇已经对关联式容器set/map/multiset/multimap进行了简答的介绍,大家可能发现它们有一个共同点:其底层都是按照二叉搜索树来实现的,但是学习二叉搜索树时,已经知道当树中插入的元素有序或接近有序时,二叉搜索树的会变得极不平衡,查找操作的时间复杂度可能达到 O(n),甚至退化成链表。因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。
一、AVL树的概念
AVL树是一种自平衡二叉搜索树,它得名于它的发明者 Adelson-Velsky 和 Landis。AVL树通过在每次插入或删除节点时进行旋转操作来保持树的平衡,以确保每个结点的左右子树高度之差的绝对值不超过1,降低树的高度,从而实现较高效率的查找、插入和删除操作。
AVL树的性质:
- 它的左右子树都是AVL树
- 每个节点的左子树和右子树的高度差(平衡因子)不超过1。
- 如果插入或删除操作导致树失去平衡,AVL树会通过旋转操作(包括单旋转和双旋转)来重新平衡。
- 若一个AVL树有n个节点,它的查找、插入和删除操作的时间复杂度都是 O(log_2 n),高度可保持在 O(log_2 n)。
二、AVL树节点的定义
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)
{}
};
三、AVL树的操作
3.1 AVL树的平衡因子
AVL 树的平衡因子是指每个节点的左子树高度减去右子树高度的值,即左子树高度 - 右子树高度。对于任意一个节点,其平衡因子可以是 -1、0 或 1。
AVL 树的平衡因子定义如下:
- 如果一个节点的平衡因子为 -1,表示该节点的左子树比右子树高度高 1;
- 如果一个节点的平衡因子为 0,表示该节点的左子树和右子树高度相等;
- 如果一个节点的平衡因子为 1,表示该节点的右子树比左子树高度高 1。
AVL 树通过保持每个节点的平衡因子为 -1、0 或 1 来确保树的平衡。当插入或删除节点后,需要通过旋转操作来调整各个节点的平衡因子,以确保整棵树仍保持平衡状态。
3.2 AVL树的插入
在 AVL 树中插入元素的过程如下:
- 按照二叉搜索树的规则,找到新元素应该插入的位置,将其作为叶子节点插入到树中
- 在插入新元素后,从插入点开始向上回溯,更新每个祖先节点的平衡因子,并检查它们是否失去了平衡。
- 如果发现某个节点失去了平衡,则需要对其进行旋转操作,以恢复整棵树的平衡。
步骤一:按照二叉搜索树的规则插入新元素
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv)
{
Node* pnode = new Node(kv);
if (_root == nullptr)
{
_root = pnode;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < pnode->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > pnode->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
if (parent->_kv.first < pnode->_kv.first)
{
parent->_right = pnode;
pnode->_parent = parent;
}
else
{
parent->_left = pnode;
pnode->_parent = parent;
}
//开始处理平衡因子
//...
private:
Node* _root = nullptr;
};
步骤二:更新每个祖先节点的平衡因子,检查它们是否失去了平衡。
在插入元素之前,parent的平衡因子分为三种情况:0,1,-1,插入后分为以下两种情况:
- 如果cur插入到parent的左侧,只需给parent的平衡因子-1即可
- 如果cur插入到parent的右侧,只需给parent的平衡因子+1即可
bool Insert(const pair<K, V>& kv)
{
//.....
//开始处理平衡因子
cur = pnode;
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)
{
//失去了平衡,需要进行旋转
//旋转操作。。。
}
else
{
assert(false);
}
}
return true;
}
插入后,parent节点的平衡因子发生变化,有这么三种情况:0,正负1, 正负2。
- 情况1:parent的平衡因子变为0(parent的平衡因子一定是由1或-1变成0)
9的平衡因子由-1变成0,但是以9为根节点的这棵树整体高度不变,所以不会再对9的祖先节点造成影响,所以不需要再向上更新平衡因子,也不需要进行旋转的操作。
同理,由1变为0也是一样
操作:parent的平衡因子变为0时,不需要再向上更新平衡因子。
- 情况2:parent的平衡因子变为 -1/1(parent的平衡因子一定是0由变成1或-1)
插入1后,1的父节点3的平衡因子变成-1,3这个节点的平衡因子是正常的,但是3的平衡因子从0变成-1时,它的左边变高了,3的父节点9的平衡因子变成-2,发生错误,我们就需要对9进行调整同理,如果变成1也是一样(例如插入18)。
所以插入新节点后,新节点的父节点(parent)的平衡因子如果变成了-1或1,我们就需要沿着这个父节点到根的路径依次更新父节点的祖先节点的平衡因子,如果某个祖先节点平衡因子更新后变成2或者-2(绝对值大于1),就立即进行旋转调整.
- 情况3:parent的平衡因子变为 -2/2,此时违反平衡树的性质,需要对其进行旋转处理。
步骤三:如果发现某个节点失去了平衡,则需要对其进行旋转操作。
3.3 AVL树的旋转
根据节点插入位置的不同,AVL树的旋转分为四种:
1. 新节点插入较高左子树的左侧---左左:右单旋
右单旋步骤如下:
- 设key为12的节点为parent,parent的左节点为cur(key为6),cur的右节点为curR
- 将curR链接在parent的左边,parent链接在cur的右边。
- 调整各节点的父节点
- 将parent和cur的平衡因子置0。
注:
- 使用右单旋的情况为:parent->_bf == -2 && cur->_bf == -1
- 图中的a,b,c 均为抽象节点,h 可取值0、1、2...,虽然不同大小的h对应的情况很多,在此也不一一赘叙、但经过总结,只要满足新节点插入较高左子树的左侧,就可以使用右旋。
下面是h=0 和h = 1的右单旋示例:(这里将cur命名为subL,curR命名为subLR) - 需要注意的是,此处的parent 可能是根节点,也可能是子树。如果是子树,可能是左子树也可能是右子树,这时就需要先备份一份parent的parent(下面命名为parentParent),将旋转后的根节点的_parent和parentParent指向parent的指针修改。
- cur节点的右孩子可能存在,也可能不存在。当h等于0时,cur就没有右孩子。
右单旋的实现如下:
//右单旋
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curR = cur->_right;
//因为parent可能是棵子树,因此在更新其双亲前必须先保存parent的双亲
Node* parentParent = parent->_parent;
parent->_left = curR;
parent->_parent = cur;
cur->_right = parent;
cur->_parent = parentParent;
//旋转完成之后,cur的右孩子作为parent的左孩子
//如果cur的右孩子存在,更新亲双亲
if (curR)
{
curR->_parent = parent;
}
// 如果parent是根节点,_root指向新的根节点
if (_root == parent)
{
_root = cur;
}
else //否则就要判断原来的parent是父节点的左子树还是右子树
{
if (parentParent->_left == parent)
{
parentParent->_left = cur;
}
else
{
parentParent->_right = cur;
}
}
// 根据调整后的结构更新部分节点的平衡因子
parent->_bf = cur->_bf = 0;
}
2. 新节点插入较高右子树的右侧---右右:左单旋
使用左单旋的情况为:parent->_bf == 2 && cur->_bf == 1
左单旋步骤如下:
- 将curL链接在parent的右边
- 将parent链接在cur的左边
- 调整各节点的父节点
- 将cur和parent的平衡因子调整为0
左单旋的抽象节点分析与右单旋类似,所以在此不再花费篇幅进行讲解
左单旋的实现如下:
//左单旋
void RotateL(Node* parent)
{
Node* parentParent = parent->_parent;
Node* cur = parent->_right;
Node* curL = cur->_left;
parent->_right = cur->_left;
parent->_parent = cur;
cur->_parent = parentParent;
cur->_left = parent;
if (curL)
{
curL->_parent = parent;
}
if (_root == parent)
{
_root = cur;
//cur->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = cur;
}
else
{
parentParent->_right = cur;
}
}
parent->_bf = cur->_bf = 0;
}
3.新节点插入较高左子树的右侧---左右:先左单旋再右单旋
使用左右双旋的情况为:parent->_bf == -2 && cur->_bf == 1
旋转步骤:
- 对cur进行左单旋(cur为parent的左节点)
- 对parent进行右单旋
- 调整平衡因子
对抽象节点进行分析:
- 当 h=0 时,curR就是新增(curR->_bf = 0),旋转后curR = 0,cur = 0,parent = 0
- 当 h=1 时,新增节点可能是curR的左节点,也可能是右节点。
若为左节点,curR->_bf = -1,cur->_bf = 1,parent->_bf = -2。
旋转后:curR->_bf = 0,cur->_bf= 0,parent->_bf = 1
若为右节点,curR->_bf = 1,cur->_bf = 1,parent->_bf = -2。
旋转后:curR->_bf = 0,cur->_bf = -1,parent->_bf = 0 - 当 h=2、3、4...,结果与h=1相同
综上,平衡因子调整规则为:
- 当curR->_bf = 0时:调整为curR->_bf = 0,cur->_bf = 0,parent->_bf = 0
- 当curR->_bf = -1时:调整为curR->_bf = 0,cur->_bf= 0,parent->_bf = 1
- 当curR->_bf = 1时:调整为curR->_bf = 0,cur->_bf = -1,parent->_bf = 0
注:
- 旋转之前,保存curR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子。
左右双旋实现如下:
//先左再右
void RotateLR(Node* parent)
{
//对parent的left左旋,再对parent右旋
Node* cur = parent->_left;
Node* curR = cur->_right;
int bf = curR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 0)//curR是新增的
{
parent->_bf = cur->_bf = curR->_bf = 0;
}
else if (bf == -1)//curR的left是新增的
{
parent->_bf = 1;
cur->_bf = 0;
curR->_bf = 0;
}
else if (bf == 1)//curR的right是新增的
{
parent->_bf = 0;
cur->_bf = -1;
curR->_bf = 0;
}
else
{
assert(false);
}
}
4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋
使用右左双旋的情况为:parent->_bf == 2 && cur->_bf == -1
旋转步骤:
- 对subR进行右单旋
- 对parent进行左单旋
- 调整平衡因子
右左双旋对抽象节点进行分析与左右双旋类似,大家可自行尝试分析。
平衡因子调整规则为:
- 当curR->_bf = 0时:调整为curR->_bf = 0,cur->_bf = 0,parent->_bf = 0
- 当curR->_bf = -1时:调整为curR->_bf = 0,cur->_bf= 1,parent->_bf = 0
- 当curR->_bf = 1时:调整为curR->_bf = 0,cur->_bf = 0,parent->_bf = -1
//先右再左
void RotateRL(Node* parent)
{
//对parent的right右旋,再对parent左旋
Node* cur = parent->_right;
Node* curL = cur->_left;
int bf = curL->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 0)//curL是新增的
{
parent->_bf = cur->_bf = curL->_bf = 0;
}
else if (bf == -1)//curL的left是新增的
{
parent->_bf = 0;
cur->_bf = 1;
curL->_bf = 0;
}
else if (bf == 1)//curL的right是新增的
{
parent->_bf = -1;
cur->_bf = 0;
curL->_bf = 0;
}
else
{
assert(false);
}
}
3.4 AVL树的验证
如果要验证一个二叉搜索树是否为AVL 树,则需要检查每个节点的平衡因子是否符合 AVL 树的定义,即平衡因子为 -1、0 或 1,并且整棵树中的每个节点都满足 AVL 树的平衡性质。
验证 AVL 树的一般步骤如下:
- 对树中的每个节点,计算其左子树的高度和右子树的高度;
- 计算每个节点的平衡因子(左子树高度 - 右子树高度);
- 检查每个节点的平衡因子是否为 -1、0 或 1,如果不是,则说明该节点不满足 AVL 树的平衡性质;
- 递归地对树中的每个节点进行上述检查,确保整棵树都符合 AVL 树的定义。
验证的实现:
//判断是否平衡
bool IsBalance()
{
return _IsBalance(_root);
}
//求树的高度
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 (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return abs(rightHeight - leftHeight) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
四、AVL树的完整代码
#pragma once
#include <assert.h>
#include<iostream>
using namespace std;
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)
{
Node* pnode = new Node(kv);
if (_root == nullptr)
{
_root = pnode;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < pnode->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > pnode->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
if (parent->_kv.first < pnode->_kv.first)
{
parent->_right = pnode;
pnode->_parent = parent;
}
else
{
parent->_left = pnode;
pnode->_parent = parent;
}
//开始处理平衡因子
cur = pnode;
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 && 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);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool IsBalance()
{
return _IsBalance(_root);
}
private:
//左旋
void RotateL(Node* parent)
{
Node* parentParent = parent->_parent;
Node* cur = parent->_right;
Node* curL = cur->_left;
parent->_right = cur->_left;
parent->_parent = cur;
cur->_parent = parentParent;
cur->_left = parent;
if (curL)
{
curL->_parent = parent;
}
if (_root == parent)
{
_root = cur;
//cur->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = cur;
}
else
{
parentParent->_right = cur;
}
}
parent->_bf = cur->_bf = 0;
}
//右旋
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curR = cur->_right;
Node* parentParent = parent->_parent;
parent->_left = curR;
parent->_parent = cur;
cur->_right = parent;
cur->_parent = parentParent;
if (curR)
{
curR->_parent = parent;
}
if (_root == parent)
{
_root = cur;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = cur;
}
else
{
parentParent->_right = cur;
}
}
parent->_bf = cur->_bf = 0;
}
//先右再左
void RotateRL(Node* parent)
{
//对parent的right右旋,再对parent左旋
Node* cur = parent->_right;
Node* curL = cur->_left;
int bf = curL->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 0)//curL是新增的
{
parent->_bf = cur->_bf = curL->_bf = 0;
}
else if (bf == -1)//curL的left是新增的
{
parent->_bf = 0;
cur->_bf = 1;
curL->_bf = 0;
}
else if (bf == 1)//curL的right是新增的
{
parent->_bf = -1;
cur->_bf = 0;
curL->_bf = 0;
}
else
{
assert(false);
}
}
//先左再右
void RotateLR(Node* parent)
{
//对parent的left左旋,再对parent右旋
Node* cur = parent->_left;
Node* curR = cur->_right;
int bf = curR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 0)//curR是新增的
{
parent->_bf = cur->_bf = curR->_bf = 0;
}
else if (bf == -1)//curR的left是新增的
{
parent->_bf = 1;
cur->_bf = 0;
curR->_bf = 0;
}
else if (bf == 1)//curR的right是新增的
{
parent->_bf = 0;
cur->_bf = -1;
curR->_bf = 0;
}
else
{
assert(false);
}
}
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;
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
if (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return abs(rightHeight - leftHeight) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
private:
Node* _root = nullptr;
};