文章目录
- 定义 && 性质
- 定义
- 性质
- 实现
- 思路
- 架构
- 节点
- AVL树
- 框架
- Insert(插入)
- 左单旋
- 右单旋
- 左右双旋
- 右左双旋
定义 && 性质
定义
二叉搜索树虽可以缩短查找的效率,但 如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
而AVL树可以较好的解决上述问题:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
性质
AVL树是一种 自平衡二叉搜索树,严格满足以下性质:
- 对于任何一个节点,其左子树和右子树的高度差(即平衡因子)不超过1。
- AVL树的每个节点存储的键值大于其左子树中所有节点的键值,小于其右子树中的所有节点的键值。这也是二叉搜索树的基本性质。
- 它的左右子树都是AVL树
实现
思路
- 核心思想是通过旋转操作保持树的平衡性
- 在 AVL 树中,每个节点都有一个平衡因子,并且平衡因子的值只能是 0,1 或 -1
- 当插入或删除节点导致某个节点的平衡因子绝对值大于 1 时,就需要通过旋转操作来调整整棵树的平衡性。
当右子树高的时候,平衡因子+1,当左子树高时,平衡因子-1
对上述的树,就需要通过旋转让其平衡
架构
- 包含两个结构体,一个用来进行节点的实现,一个用于实现
AVLTree
的各项功能
#pragma once
template<class K, class V>
struct AVLTreeNode //节点
{
// 成员变量 和 成员函数
};
template<class K, class V>
struct AVLTree
{
typedef AVLTreeNode<K, V> Node; //重命名
public:
// 公有成员函数
private:
// 私有成员函数
private:
// 成员变量
Node* _root = nullptr;
};
节点
- 将节点的实现在结构体中,节点包含 左右子树指针、父节点指针、键值对和平衡因子。
- AVL 树的节点需要记录其左右子树指针和父节点指针,以支持 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; //平衡因子
AVLTreeNode(const pair<K, V> & kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
,_bf(0)
{}
};
AVL树
框架
- 将待实现的功能放到这里
- 成员变量为_root根节点
template<class K, class V>
struct AVLTree
{
typedef AVLTreeNode<K, V> Node; //重命名
public:
// 插入
bool Insert(const pair<K, V>& kv)
{}
// 中序遍历/打印
void InOrder()
{}
//判断是否平衡
bool IsBalance()
{}
private:
// 判断是否平衡
bool _IsBalance(Node* root)
{}
// 求AVL树最大高度
int Height(Node* root)
{}
// 左单旋
void RotateL(Node* parent)
{}
// 右单旋
void RotateR(Node* parent)
{}
// 左右双旋
void RotateLR(Node* parent)
{}
// 右左双旋
void RotateRL(Node* parent)
{}
void _InOrder(Node* root)
{}
private:
// 根节点
Node* _root = nullptr;
};
Insert(插入)
插入过程主要分为两个步骤:
- 搜索树的插入过程。从根节点开始,与新插入的节点的键值比较大小,向左(小于当前节点的键值)或向右(大于当前节点的键值)递归地查找插入位置;如果要插入的节点的键值已经存在,则返回 false。
- 插入新的节点。将新节点插入到搜索树的合适位置,并更新其父节点指针。同时,从新节点开始向上检查,计算所经过节点的平衡因子并进行适当的旋转操作以调整树的平衡。
bool Insert(const pair<K, V>& kv)
{
// 根为空时第一次插入直接创建新节点
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
// 搜索树的插入逻辑
while (cur) {
// 如果插入的节点值大,则插入右边
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_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; // 记录插入节点的父节点
// 控制平衡
// 1、更新平衡因子
while (parent)
{
// cur在右边,则平衡因子++
if (cur == parent->_right)
++parent->_bf;
// cur在左边,则平衡因子--
else
--parent->_bf;
// 平衡因子 == 0,已经平衡
if (parent->_bf == 0) {
break;
}
// == 1,向上找
else if (abs(parent->_bf) == 1) {
parent = parent->_parent;
cur = cur->_parent;
}
// == 2,此时parent所在子树已经不平衡了,需要旋转处理
else if (abs(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)
{
// 左右双旋
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
//右左双旋
RotateRL(parent);
}
else
{
// 正常情况下不会出现该种情况,如果出现直接报错
assert(false);
}
}
else {
// 正常情况下不会出现该种情况,如果出现直接报错
assert(false);
}
}
return true;
}
左单旋
抽象图(思路):
- 根据图中思路写代码:记录右侧节点和右侧节点的左子树节点。
- 将 subR 的左子树更改为 parent,同时将 parent 的父节点指向 subR。
- 将 parent 的右子树更改为 subRL,并更新 subRL 的父节点为 parent。
- 如果 parent 是根节点,则将 subR 设为新的根节点,并将 subR 的父节点设为 nullptr;否则将 subR 的父节点指向 parent 的父节点,并更新 parent 的父节点的左子树或右子树为 subR。
- 最后,将 subR 和 parent 的平衡因子都设置为 0。
void RotateL(Node* parent)
{
// 此时右子树的高度比左子树高2,记录右侧节点
Node* subR = parent->_right;
Node* subRL = subR->_left;
// 目的需要将subR变为新的父节点(根节点)
// 将SubRL变为parent的子节点
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
// 创建此时父节点的头节点
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR; //此时subR到父节点的位置
// 如果本来父节点为根
if (_root == parent)
{
//将subR变为根
_root = subR;
subR->_parent = nullptr;
}
// 本来parent不为根节点
else
{
// 将subR的_parent指向ppNode
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
subR->_parent = ppNode;
}
// 此时平衡,更新平衡因子
subR->_bf = parent->_bf = 0;
}
右单旋
- 根据图片思路写代码:定义 subL 和 subLR 分别为 parent 节点的左孩子和 subL 节点的右孩子。
- 将 subLR 节点移动到 parent 节点的位置上,即 parent 的左孩子变成 subLR,subLR 的父节点变成 parent。
- 将 subL 节点移动到 subLR 节点的右边,subL 的右孩子变成 parent,parent 的父节点变成 subL。
- 如果原先 parent 节点是根节点,将 subL 节点设为新的根节点并将其父节点设置为空;如果不是根节点,则将 subL 节点移动到 parent 节点原来的父节点的位置上,并更新子节点的父节点。
- 更新 subL 和 parent 节点的平衡因子,均设为 0。
void RotateR(Node* parent)
{
// 获取左子树和左子树的右子树
Node* subL = parent->_left;
Node* subLR = subL->_right;
// 旋转操作:将左子树的右子树变为 parent 的左子树
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
// 将 parent 变为左子树的右子树
subL->_right = parent;
parent->_parent = subL;
// 如果 parent 原本是根节点,则设置新的根节点为 subL,否则需要调整 parent 的父节点
Node* ppNode = parent->_parent;
if (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
ppNode->_left = subL;
else
ppNode->_right = subL;
subL->_parent = ppNode;
}
// 更新平衡因子:旋转后,parent 和 subL 的平衡因子都变为 0
subL->_bf = parent->_bf = 0;
}
左右双旋
- 根据图片步骤写代码:定义 subL 和 subLR 分别为 parent 节点的左孩子和右孩子的左孩子。
- 对
subL
进行一次左旋操作,此时 subL 变成了 parent 节点的父节点,subLR 成为了 subL 的右孩子。 - 对
parent
节点进行一次右旋操作,此时 subLR 节点被转移到 parent 的位置上,并成为 parent 的父节点。同时,subLR 的右子树成为 parent 的左子树,subLR 的左子树成为 subL 的右子树。 - 根据新树结构和子树的高度差,调整各个节点的平衡因子。其中,subLR 节点的平衡因子设为 0。如果 subLR 原来的平衡因子为 1,则 parent 的平衡因子、subL 的平衡因子和 subLR 的平衡因子分别设为 0,-1 和 0;如果 subLR 原来的平衡因子为 -1,则三者分别设为 1,0 和 0;如果 subLR 原来的平衡因子为 0,则三者均设为 0。
void RotateLR(Node* parent)
{
// 定义subL,subLR
Node* subL = parent->_left;
Node* subLR = subL->_right;
// 保存subLR节点的平衡因子
int bf = subLR->_bf;
// 左旋操作,将subLR上移到subL的位置
RotateL(parent->_left);
// 右旋操作,将subLR上移到parent的位置
RotateR(parent);
// 根据新树结构和子树高度调整各个节点平衡因子
subLR->_bf = 0; // subLR的平衡因子设为0
if (bf == 1) // 如果subLR原来的平衡因子为1
{
parent->_bf = 1;
subL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subL->_bf = 1;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
}
// subLR的平衡因子取值范围应该是-1,0,1三个值,如果不在这个范围内则代表程序出现了错误
else
{
assert(false); // 断言,程序错误
}
}
右左双旋
- 根据图步骤来实现代码:定义 subR 和 subRL 分别为 parent 节点的右孩子和左孩子的右孩子。
- 对 subR 进行一次右旋操作,此时 subR 变成了 parent 节点的父节点,subRL 成为了 subR 的左孩子。
- 对 parent 节点进行一次左旋操作,此时 subRL 节点被转移到 parent 的位置上,并成为 parent 的父节点。同时,subRL 的左子树成为 parent 的右子树,subRL 的右子树成为 subR 的左子树。
- 根据新树结构和子树的高度差,调整各个节点的平衡因子。
void RotateRL(Node* parent)
{
// 定义subR,subRL
Node* subR = parent->_right;
Node* subRL = parent->_left;
// 保存subRL节点的平衡因子
int bf = subRL->_bf;
// 右旋操作,将subR上移到parent的位置
RotateR(parent->_right);
// 左旋操作,将subRL上移到parent的位置
RotateL(parent);
// 根据新树结构和子树高度调整各个节点平衡因子
subRL->_bf = 0; // subRL的平衡因子设为0
if (bf == 1) // 如果subRL原来的平衡因子为1
{
subR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
parent->_bf = 0;
}
else if (bf == 0)
{
subR->_bf = 0;
parent->_bf = 0;
}
// subRL的平衡因子取值范围应该是-1,0,1三个值,如果不在这个范围内则代表程序出现了错误
else
{
assert(false);
}
}