目录
一、AVL树的基本概念
AVL树是一种特殊的二叉查找树,其每个节点的左右子树高度差(平衡因子)的绝对值不超过1。平衡因子的定义为:
BF(x)=Height(x.right)−Height(x.left)
其中,Height(x) 表示以 x 为根的子树的高度。AVL树通过旋转操作来调整树的平衡,确保在插入或删除节点后,树仍然保持平衡。
这种特性使得AVL树在插入和查找操作中都能保持较高的效率。本文将详细介绍AVL树的实现,包括插入操作、旋转调整以及平衡因子的维护。
二、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)
{ }
};
(一)节点结构解析
-
_left和_right分别指向节点的左子节点和右子节点。 -
_parent指向当前节点的父节点,用于在调整树结构时快速定位。 -
_kv是一个键值对,存储节点的键和值。 -
_bf是平衡因子,用于记录当前节点的左右子树高度差。
三、AVL树的查找操作
(一)思路解析
用cur代替根节点循环查找,如果key值比节点的值大,那就在右子树中查找;如果key值比节点的值小,那就在左子树中查找;循环往复,如果能找到返回当前指针,否则返回空。
Node* Find(const K& key)
{
Node* cur=_root;
while(cur)
{
if(key > cur->_kv.first)
{
cur=cur->_right;
}
else if(key < cur->_kv.first)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
四、AVL树的插入操作
插入操作是AVL树的核心功能之一。插入一个新节点后,需要调整树的平衡。以下是插入操作的代码实现:
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;
}
Node* parent = nullptr;
Node* cur = _root;
// 找到要插入的位置
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 (kv.first > parent->_kv.first)
{
parent->_right = cur; // 插入到父节点的右子树
cur->_parent = parent;
}
else
{
parent->_left = cur; // 插入到父节点的左子树
cur->_parent = parent;
}
// 更新平衡因子并调整树的平衡
while (parent)
{
if (cur == parent->_left)
{
--parent->_bf; // 左子树高度增加
}
else
{
++parent->_bf; // 右子树高度增加
}
if (parent->_bf == 0)
{
break; // 平衡因子为0,树仍然平衡
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent; // 继续向上更新平衡因子
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
// 平衡因子为2或-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;
}
private:
Node* _root = nullptr; // AVL树的根节点
};
(一)插入操作解析
-
插入新节点:
-
如果树为空,直接将新节点设置为根节点。
-
如果树不为空,从根节点开始,根据键值大小找到插入位置。
-
插入新节点后,更新其父节点的左右子节点指针。
-
-
更新平衡因子:
-
从插入节点的父节点开始,向上更新平衡因子。
-
如果父节点的平衡因子变为0,说明树仍然平衡,无需进一步调整。
在插入节点前,父亲bf==-1或者bf==1,一边高,一边低,新插入的节点就在低的那边. -
如果父节点的平衡因子变为1或-1,说明树的高度发生变化,需要继续向上更新。
在插入节点前,父亲bf==0,两边一样高,插入导致高度变化 -
如果父节点的平衡因子变为2或-2,说明树失去平衡,需要进行旋转调整。
在插入节点前,父亲bf==-1或者bf==1,一边高,一边低,新插入的节点在高的那边
-
-
旋转调整:
-
根据父节点和子节点的平衡因子,选择合适的旋转操作:
-
左单旋转:父节点的平衡因子为2,子节点的平衡因子为1。
-
右单旋转:父节点的平衡因子为-2,子节点的平衡因子为-1。
-
右左双旋转:父节点的平衡因子为2,子节点的平衡因子为-1。
-
左右双旋转:父节点的平衡因子为-2,子节点的平衡因子为1。
-
-
五、AVL树的旋转操作
旋转操作是AVL树调整平衡的关键步骤。以下是四种旋转操作的代码实现:
(一)左单旋转(RotateL)
思路解析:让父亲节点去做子节点的左,子节点的左变成父亲节点的右,再让子节点变成父亲节点,最后更新平衡因子。
void RotateL(Node* parent)
{
Node* subR = parent->_right; // 右子节点
Node* subRL = subR->_left; // 右子节点的左子节点
// 更新旋转完成后节点的孩子指向
parent->_right = subRL;
subR->_left = parent;
// 更新旋转完成后节点的父亲指向
Node* grandparent = parent->_parent;
parent->_parent = subR;
if (subRL)
{
subRL->_parent = parent;
}
// 更新根节点或子树的父节点指向
if (_root == parent)
{
_root = subR; // 如果在根节点左单旋转,更新根节点
subR->_parent = nullptr;
}
else
{
if (grandparent->_left == parent)
{
grandparent->_left = subR; // 父节点在左子树中
}
else
{
grandparent->_right = subR; // 父节点在右子树中
}
subR->_parent = grandparent;
}
// 更新平衡因子
parent->_bf = subR->_bf = 0;
}
(二)右单旋转(RotateR)
思路解析:让父亲节点去做子节点的右,子节点的右变成父亲节点的左,再让子节点变成父亲节点,最后更新平衡因子。
void RotateR(Node* parent)
{
Node* subL = parent->_left; // 左子节点
Node* subLR = subL->_right; // 左子节点的右子节点
// 更新旋转完成后节点的孩子指向
parent->_left = subLR;
subL->_right = parent;
// 更新旋转完成后节点的父亲指向
Node* grandparent = parent->_parent;
parent->_parent = subL;
if (subLR)
{
subLR->_parent = parent;
}
// 更新根节点或子树的父节点指向
if (_root == parent)
{
_root = subL; // 如果在根节点右单旋转,更新根节点
subL->_parent = nullptr;
}
else
{
if (grandparent->_left == parent)
{
grandparent->_left = subL; // 父节点在左子树中
}
else
{
grandparent->_right = subL; // 父节点在右子树中
}
subL->_parent = grandparent;
}
// 更新平衡因子
parent->_bf = subL->_bf = 0;
}
(三)右左双旋转(RotateRL)
思路解析:先对右子节点进行右单旋转,再对父节点进行左单旋转,最后根据旋转后的结果更新平衡因子(有三种情况需要进行讨论)
void RotateRL(Node* parent)
{
Node* subR = parent->_right; // 右子节点
Node* subRL = subR->_left; // 右子节点的左子节点
int bf = subRL->_bf; // 保存右左子节点的平衡因子
// 先对右子节点进行右单旋转
RotateR(parent->_right);
// 再对父节点进行左单旋转
RotateL(parent);
// 更新平衡因子
if (bf == 1) // 新增在右子树
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 0) // 新增在右左子节点
{
parent->_bf = subR->_bf = subRL->_bf = 0;
}
else if (bf == -1) // 新增在左子树
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else
{
assert(false); // 平衡因子异常
}
}
(四)左右双旋转(RotateLR)
思路解析:先对左子节点进行左单旋转,再对父节点进行右单旋转,最后根据旋转后的结果更新平衡因子(有三种情况需要进行讨论)
void RotateLR(Node* parent)
{
Node* subL = parent->_left; // 左子节点
Node* subLR = subL->_right; // 左子节点的右子节点
int bf = subLR->_bf; // 保存左右子节点的平衡因子
// 先对左子节点进行左单旋转
RotateL(parent->_left);
// 再对父节点进行右单旋转
RotateR(parent);
// 更新平衡因子
if (bf == 1) // 新增在右子树
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0) // 新增在左右子节点
{
parent->_bf = subL->_bf = subLR->_bf = 0;
}
else if (bf == -1) // 新增在左子树
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false); // 平衡因子异常
}
}
(五)旋转操作解析
-
左单旋转(RotateL):
-

-
适用于父节点的右子树过高,且右子节点的平衡因子为1的情况。
-
将父节点的右子节点提升为新的父节点,调整指针关系,更新平衡因子都为0。
-
-
右单旋转(RotateR):
-

-
适用于父节点的左子树过高,且左子节点的平衡因子为-1的情况。
-
将父节点的左子节点提升为新的父节点,调整指针关系,更新平衡因子都为0。
-
-
右左双旋转(RotateRL):
-

-
适用于父节点的右子树过高,且右子节点的平衡因子为-1的情况。
-
先对右子节点进行右单旋转,再对父节点进行左单旋转,调整指针关系,更新平衡因子。
-
平衡因子分为三种情况:
-
1、新增在右子树
-
2、新增在左子树
-
3、新增在右左子节点

-
-
-
左右双旋转(RotateLR):
-

-
适用于父节点的左子树过高,且左子节点的平衡因子为1的情况。
-
先对左子节点进行左单旋转,再对父节点进行右单旋转,调整指针关系,更新平衡因子。
-
平衡因子分为三种情况:
-
1、新增在右子树
-
2、新增在左子树
-
3、新增在左右子节点
-
-
六、AVL树的遍历操作
为了验证AVL树的结构和平衡性,我们可以通过中序遍历输出树的节点。以下是中序遍历的代码实现:
void InOrder()
{
_InOrder(_root);
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left); // 遍历左子树
cout << root->_kv.first << " "; // 输出当前节点的键
_InOrder(root->_right); // 遍历右子树
}
(一)中序遍历解析
中序遍历的顺序为:左子树 -> 当前节点 -> 右子树。通过中序遍历,可以输出AVL树的节点,验证树的结构是否正确。
七、AVL树的平衡性检查
为了确保AVL树在插入操作后仍然保持平衡,我们可以通过递归检查每个节点的平衡因子。以下是平衡性检查的代码实现:
bool IsBalance()
{
return _IsBalance(_root);
}
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);
}
int _Height(Node* root)
{
if (root == nullptr)
{
return 0;
}
int left = _Height(root->_left); // 左子树高度
int right = _Height(root->_right); // 右子树高度
return left > right ? (1 + left) : (1 + right);
}
(二)平衡性检查解析
-
_IsBalance函数递归检查每个节点的平衡因子是否符合AVL树的定义。 -
_Height函数计算以某个节点为根的子树的高度。
八、总结
AVL树是一种自平衡二叉查找树,通过维护每个节点的平衡因子(左右子树高度差绝对值≤1)来保证高效操作。本文详细介绍了AVL树的实现:1)定义包含平衡因子的节点结构;2)插入操作时通过四种旋转(左单旋、右单旋、右左双旋、左右双旋)调整平衡;3)提供查找、遍历和平衡性检查方法。通过维护平衡因子和旋转操作,AVL树确保插入、查找等操作的时间复杂度保持在O(logn)。文中给出了完整的C++实现代码,包括节点结构调整、平衡因子更新和旋转操作的具体实现。
3778

被折叠的 条评论
为什么被折叠?



