当前位置: 首页 > news >正文

深入理解 AVL 树:自平衡二叉搜索树的原理与实现

在二叉搜索树(BST)的应用中,若数据插入顺序极端(如递增、递减),树会退化为链表,导致增删查改效率降至 O (n)。为解决这一问题,AVL 树应运而生 —— 它是最早的自平衡二叉搜索树,通过维持左右子树高度差不超过 1,将时间复杂度稳定在 O (log n)。本文将从原理到实践拆解 AVL 树的核心逻辑。

1. AVL树的核心概念

1.1 定义与平衡因子

AVL 树由前苏联科学家 G.M.Adelson - Velsky 和 E.M.Landis 于 1962 年提出,本质是满足以下条件的二叉搜索树:

1. 左右子树均为 AVL 树;

2. 左右子树高度差(平衡因子)的绝对值≤1。

平衡因子(BF)定义为「右子树高度 - 左子树高度」,合法取值仅为 - 1、0、1。引入平衡因子是为了直观判断树的平衡状态,如同 “风向标” 般辅助后续调整。

可能有人会疑惑:为何平衡标准是 “高度差≤1” 而非 “高度差 = 0”?实际上,绝对平衡在很多场景下无法实现 —— 例如含 2 个、4 个节点的 AVL 树,最优结构只能做到高度差为 1。强行追求绝对平衡会导致插入 / 删除时的调整成本过高,反而降低整体效率。

核心优势:AVL 树的节点分布接近完全二叉树,高度严格控制在 log₂n 级别(n 为节点数),因此增删查改的时间复杂度均为 O (log n),相比普通 BST 实现了质的提升。

2. AVL树的结构设计

AVL 树的节点需存储键值对、父子指针及平衡因子,同时走初始化列表进行初始化。

template<class K, class V>
struct AVLTreeNode 
{pair<K, V> _kv;          // 键值对AVLTreeNode* _parent;    // 父节点指针(便于平衡调整)AVLTreeNode* _left;      // 左子节点AVLTreeNode* _right;     // 右子节点int _bf;                 // 平衡因子// 节点构造函数AVLTreeNode(const pair<K, V>& kv): _kv(kv), _parent(nullptr), _left(nullptr), _right(nullptr), _bf(0) {}
};

类结构采用 C++ 模板实现以支持泛型,同时包含构造、拷贝、赋值、析构等完整的生命周期管理接口。

template<class K, class V>
class AVLTree 
{typedef AVLTreeNode<K, V> Node;
public:// 1. 默认构造函数AVLTree() {}// 2. 拷贝构造函数(深拷贝)AVLTree(const AVLTree& t) {_root = copy(t._root); // 递归拷贝整棵树}// 3. 赋值重载(现代写法,基于拷贝构造实现)AVLTree<K, V>& operator=(const AVLTree<K, V> tmp) {swap(_root, tmp._root); // 交换当前对象与临时对象的根节点return *this;}// 4. 析构函数~AVLTree() {Destroy(_root); // 递归销毁所有节点}// 核心功能接口bool Insert(const pair<K, V>& kv);Node* Find(const K& key);void InOrder();bool IsBalanceTree();int Height();int Size();private:// 拷贝辅助函数(递归深拷贝)Node* copy(Node* root);// 销毁辅助函数(递归后序遍历销毁)void Destroy(Node*& root);// 交换辅助函数(用于赋值重载)void swap(Node*& left, Node*& right);// 旋转操作(核心平衡调整)void RotateL(Node* parent);  // 左单旋void RotateR(Node* parent);  // 右单旋void RotateLR(Node* parent); // 左右双旋void RotateRL(Node* parent); // 右左双旋// 遍历与工具辅助函数void _InOrder(Node* root);int _Height(Node* root);int _Size(Node* root);bool _IsBalanceTree(Node* root);private:Node* _root = nullptr; // 根节点指针
};

3. AVL树的初始化和销毁

3.1 AVL树的初始化

(1)默认构造

我们先定义一个默认构造函数,因为我们在定义构造函数之后编译器就不会生成默认构造函数了。

AVLTree() {}

(2)拷贝构造

采用前序递归遍历,先拷贝当前节点,再递归拷贝左右子树,最后重建父子指针关系。同时平衡因子也需要同步拷贝,确保新树的平衡状态与原树一致。

// 拷贝构造函数(对外接口)
AVLTree(const AVLTree& t) 
{_root = copy(t._root); // 调用辅助函数递归拷贝
}// 拷贝辅助函数(递归深拷贝实现)
Node* copy(Node* root) 
{if (root == nullptr) return nullptr; // 空节点直接返回,递归终止条件// 1. 拷贝当前节点:创建新节点,复制原节点的键值对和平衡因子Node* newnode = new Node(root->_kv);newnode->_bf = root->_bf;// 2. 递归拷贝左右子树:深度遍历原树,逐一复制子节点newnode->_left = copy(root->_left);newnode->_right = copy(root->_right);// 3. 重建父子关系:确保新节点的子节点能找到父节点newnode->_parent = nullptr;if (newnode->_left) {newnode->_left->_parent = newnode;}if (newnode->_right) {newnode->_right->_parent = newnode;}return newnode; // 返回拷贝后的当前节点,供父节点连接
}

(3)赋值重载(现代写法)

这里的赋值重载与我们之前二叉搜索树的实现基本一致,使用现代写法,调用函数时用值传递拷贝构造出一个临时对象,然后交换当前对象的_root与临时对象的_root即可实现拷贝,函数结束时,临时对象会自动调用析构函数,销毁当前对象原来的旧数据。

3.2 AVL树的销毁

(1)析构函数

采用后序递归遍历,先销毁左右子树的所有节点,最后销毁当前节点,确保子节点资源先释放,避免访问已释放的内存。销毁后将root置为nullptr,避免后续误操作野指针。

// 析构函数对外接口
~AVLTree() 
{Destroy(_root); // 调用辅助函数递归销毁
}// 销毁辅助函数(递归后序遍历实现)
void Destroy(Node*& root) 
{if (root == nullptr)  // 空节点直接返回,递归终止条件{return;}// 1. 递归销毁左子树:先释放所有左子节点Destroy(root->_left);// 2. 递归销毁右子树:再释放所有右子节点Destroy(root->_right);// 3. 销毁当前节点:最后释放当前节点,避免野指针delete root;root = nullptr;
}

4. AVL树的插入(重点)

4.1 AVL树插入值的过程

过程如下:

1. 按二叉搜索树(BST)规则插入新节点;

2. 回溯更新平衡因子,若平衡因子在合法取值内(-1、0、1),则插入结束。若出现失衡(平衡因子 ±2),通过旋转调整平衡。

4.2 平衡因子的更新

 更新原则:

• 平衡因子 = 右子树高度 - 左子树高度

• 只有子树高度变化才会影响当前结点平衡因子

• 插入结点,会增加高度,所以新增结点在parent的右子树,parent的平衡因子++,新增结点在parent的左子树,parent平衡因子--

• parent所在子树的高度是否变化决定了是否会继续往上更新

更新停止条件:

• 更新后平衡因子为 0:子树高度不变,无需继续更新。

• 更新后平衡因子为 ±1:子树高度增加 1,需继续向上更新。

• 更新后平衡因子为 ±2:子树失衡,旋转调整后停止更新。

• 若更新至根节点,平衡因子为 ±1 时也停止。

详解:

• 更新后parent的平衡因子等于0,更新中parent的平衡因子变化为 - 1->0 或者 1->0,说明更新前parent子树一边高一边低,新增的结点插入在低的那边,插入后parent所在的子树高度不变,不会影响parent的父亲结点的平衡因子,更新结束。

• 更新后parent的平衡因子等于1 或 - 1,更新前更新中parent的平衡因子变化为0->1 或者 0-> - 1,说明更新前parent子树两边一样高,插入新增的结点后,parent所在的子树一边高一边低,parent所在的子树符合平衡要求,但是高度增加了1,会影响parent的父亲结点的平衡因子,所以要继续向上更新。

• 更新后parent的平衡因子等于2 或 - 2,更新前更新中parent的平衡因子变化为1->2 或者 - 1-> - 2,说明更新前parent子树一边高一边低,新增的插入结点在高的那边,parent所在的子树高的那边更高了,破坏了平衡,parent所在的子树不符合平衡要求,需要旋转处理,旋转的目标有两个:1、把parent子树旋转平衡。2、降低parent子树的高度,恢复到插入结点以前的高度。所以旋转后也不需要继续往上更新,插入结束。

• 不断更新,更新到根,根的平衡因子是1或 - 1也停止了。​

实例:

1. 更新到中间结点,3为根的子树高度不变,不会影响上一层,更新结束。

2. 更新到10结点,平衡因子为2,10所在的子树已经不平衡,需要旋转处理。

3. 更新到中间结点,3为根的子树高度不变,不会影响上一层,更新结束

4. 最坏更新到根停止

4.3 插入代码实现

下面的插入函数的代码实现,旋转的实现我们会在下面进行讲解:

bool Insert(const pair<K, V>& kv) 
{// 步骤1:空树处理:直接创建根节点if (_root == nullptr) {_root = new Node(kv);return true;}// 步骤2:按BST规则查找插入位置Node* cur = _root;Node* parent = nullptr;while (cur) {if (kv.first < cur->_kv.first) // 键值小于当前节点,走左子树{ parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first) // 键值大于当前节点,走右子树{ parent = cur;cur = cur->_right;}else // 键值重复,插入失败{ return false;}}// 步骤3:创建新节点并建立父子关系cur = new Node(kv);if (kv.first < parent->_kv.first) {parent->_left = cur; // 插入到父节点的左子树}else {parent->_right = cur; // 插入到父节点的右子树}cur->_parent = parent; // 新节点的父指针指向parent// 步骤4:回溯更新平衡因子并调整平衡while (parent) {// 4.1 更新当前父节点的平衡因子if (cur == parent->_left) {parent->_bf--; // 新节点在左子树,平衡因子-1}else {parent->_bf++; // 新节点在右子树,平衡因子+1}// 4.2 判断是否需要继续更新或旋转if (parent->_bf == 0) {// 插入后子树高度不变,无需继续向上更新,直接结束break;}else if (parent->_bf == 1 || parent->_bf == -1) {// 子树高度增加1,需继续向上更新父节点的平衡因子cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2) {// 子树失衡,触发旋转调整// 同号为单旋,异号为双旋if (parent->_bf == -2 && cur->_bf == -1) {RotateR(parent); // 左左失衡:右单旋}else if (parent->_bf == 2 && cur->_bf == 1) {RotateL(parent); // 右右失衡:左单旋}else if (parent->_bf == -2 && cur->_bf == 1) {RotateLR(parent); // 左右失衡:左右双旋}else if (parent->_bf == 2 && cur->_bf == -1) {RotateRL(parent); // 右左失衡:右左双旋}else{assert(false); //出现特殊错误,说明实现有误}break; // 旋转后平衡恢复,无需继续更新}else {assert(false); // 平衡因子异常(如±3),说明实现有误}}return true;
}

5. 旋转(重点)

5.1 旋转原则

1. 保持二叉搜索树的规则

2. 让旋转的树从不平衡变平衡,其次降低旋转树的高度旋转总共分为四种,左单旋 / 右单旋 / 左右双旋 / 右左双旋。

5.2 右单旋

右单旋的存在是为了处理“左左失衡”。我们看以下场景(a、b、c为高度为h的子树):

• 在a子树中插入一个新结点,导致a子树的高度从h变成h+1,不断向上更新平衡因子,导致10的平衡因子从-1变成-2,10为根的树左右高度差超过1,违反平衡规则。10为根的树左边太高了,需要往右边旋转,控制两棵树的平衡。

• 旋转核心步骤,因为5 < b子树的值 < 10,将b变成10的左子树,10变成5的右子树,5变成这棵树新的根,符合搜索树的规则,控制了平衡,同时这棵树的高度恢复到了插入之前的h+2,符合旋转原则。如果插入之前10是整棵树的一个局部子树,旋转后不会再影响上一层(因为旋转后高度与插入新节点前相等),插入就结束了。

左左失衡:5所在的子树为10的左子树,a所在的子树又为5的左子树,a处产生了失衡,此为“左左失衡”。左左失衡的本质就是 “父节点左子树过高,且左子树的左子树进一步过高”,平衡因子的变化(-1→-2)也完全符合这一过程,左左失衡的象征为:parent->_bf = -2 且 subL->_bf = -1。

右单旋代码实现:

	// 右单旋:针对 "左左" 失衡场景(parent->_bf = -2 且 subL->_bf = -1)void RotateR(Node* parent){// 步骤1:保存所有关键节点(必须先保存,再修改指针,避免地址丢失)Node* subL = parent->_left;       // parent的左孩子(旋转后将成为新的父节点)Node* subLR = subL->_right;       // subL的右孩子(旋转后会成为parent的左孩子)Node* pParent = parent->_parent;  // parent的原父节点(用于后续连接新的子树)// 步骤2:调整subL与parent的父子关系subL->_right = parent;  // subL的右孩子指向parent(subL成为parent的父节点)parent->_parent = subL; // parent的父节点更新为subL// 步骤3:处理subLR(若存在)parent->_left = subLR;  // parent的左孩子指向subLR(接收subL原来的右子树)if (subLR != nullptr)   // 若subLR存在,更新其父子关系{subLR->_parent = parent;}// 步骤4:将新的子树根(subL)与原父节点(pParent)连接subL->_parent = pParent;  // subL的父节点设为pParent(替代原parent的位置)if (pParent == nullptr){// 若parent原本是根节点,旋转后subL成为新的根节点_root = subL;}else{// 根据parent原本在pParent中的位置(左/右孩子),让subL替代其位置if (pParent->_left == parent){pParent->_left = subL;  // parent原本是左孩子,subL现在成为左孩子}else{pParent->_right = subL; // parent原本是右孩子,subL现在成为右孩子}}// 步骤5:更新平衡因子(左左场景旋转后,subL和parent的平衡因子均为0)subL->_bf = 0;parent->_bf = 0;}

5.3 左单旋

右单旋的逻辑与左单旋非常相似,只是情况反过来罢了,能理解右单旋便能轻松掌握左单旋。

左单旋的存在是为了处理“右右失衡”。我们看以下场景(a、b、c为高度为h的子树):

• 在a子树中插入一个新结点,导致a子树的高度从h变成h + 1,不断向上更新平衡因子,导致10的平衡因子从1变成2,10为根的树左右高度差超过1,违反平衡规则。10为根的树右边太高了,需要往左边旋转,控制两棵树的平衡。

• 旋转核心步骤,因为10 < b子树的值 < 15,将b变成10的右子树,10变成15的左子树,15变成这棵树新的根,符合搜索树的规则,控制了平衡,同时这棵树的高度恢复到了插入之前的h + 2,符合旋转原则。如果插入之前10是整棵树的一个局部子树,旋转后不会再影响上一层,插入结束了。

右右失衡:15所在的子树为10的右子树,a所在的子树又为15的右子树,a处产生了失衡,此为“右右失衡”。右右失衡的本质就是 “父节点右子树过高,且右子树的右子树进一步过高”,平衡因子的变化(1→2)也完全符合这一过程,左左失衡的象征为:parent->_bf = 2 且 subL->_bf = 1。

左单旋代码实现:

void RotateL(Node* parent) 
{Node* subR = parent->_right;   // 父节点的右子节点Node* subRL = subR->_left;     // 右子节点的左子树Node* pParent = parent->_parent; // 父节点的原父节点// 1. 调整subR与parent的关系subR->_left = parent;parent->_parent = subR;// 2. 处理subRL(若存在)parent->_right = subRL;if (subRL) {subRL->_parent = parent;}// 3. 连接新父节点与原祖父节点subR->_parent = pParent;if (pParent == nullptr) {_root = subR; // 原父节点是根节点,更新根}else {if (pParent->_left == parent) {pParent->_left = subR;}else {pParent->_right = subR;}}// 4. 重置平衡因子subR->_bf = 0;parent->_bf = 0;
}

5.4 左右双旋

左右双旋的存在是为了处理“左右失衡”。我们看以下场景:

通过图1和图2可以看到,左边高时,如果插入位置不是在a子树,而是插入在b子树,b子树高度从h变成h+1,引发旋转,右单旋无法解决问题,右单旋后,我们的树依旧不平衡。右单旋解决的是纯粹的左边高(左左失衡),但是插入在b子树中,10为根的子树不再是单纯的左边高,对于10是左边高,但是对于5是右边高(左右失衡),需要用两次旋转才能解决,以5为旋转点进行一个左单旋,以10为旋转点进行一个右单旋,这棵树这棵树就平衡了。

图1

图2

图1和图2分别为左右双旋中h == 0和h == 1的具体场景分析,下面我们将a / b / c子树抽象为高度h的AVL子树进行分析,另外我们需要把b子树的细节进一步展开为8和子树高度为h - 1的e和f子树,因为我们要对b的父亲5为旋转点进行左单旋,左单旋需要动b树中的左子树。b子树中新增结点的位置不同,平衡因子更新的细节也不同,通过观察8的平衡因子不同,这里我们要分三个场景讨论。

• 场景1:h >= 1时,新增结点插入在e子树,e子树高度从h - 1变为h并不断更新8->5->10平衡因子,引发旋转,其中8的平衡因子为 - 1,旋转后8和5平衡因子为0,10平衡因子为1。

• 场景2:h >= 1时,新增结点插入在f子树,f子树高度从h - 1变为h并不断更新8->5->10平衡因子,引发旋转,其中8的平衡因子为1,旋转后8和10平衡因子为0,5平衡因子为 - 1。

• 场景3:h == 0时,a / b / c都是空树,b自己就是一个新增结点,不断更新5->10平衡因子,引发旋转,其中8的平衡因子为0,旋转后8、10和5平衡因子均为0。

左右双旋代码实现:

void RotateLR(Node* parent) 
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf; // 记录subLR的平衡因子,用于后续调整// 第一步:对左子节点进行左单旋RotateL(subL);// 第二步:对原父节点进行右单旋RotateR(parent);// 根据subLR的原始平衡因子调整平衡因子if (bf == -1) {parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1) {parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == 0) {// 插入节点为subLR本身(叶子节点),所有平衡因子归0parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else //出现异常错误则报错{assert(false);}
}

5.5 右左双旋

跟左右双旋类似,右左双旋的存在是为了处理“右左失衡”。下面我们将a / b / c子树抽象为高度h的AVL子树进行分析,另外我们需要把b子树的细节进一步展开为12和左子树高度为h - 1的e和f子树,因为我们要对b的父亲15为旋转点进行右单旋,右单旋需要动b树中的右子树。b子树中新增结点的位置不同,平衡因子更新的细节也不同,通过观察12的平衡因子不同,这里我们要分三个场景讨论。

• 场景1:h >= 1时,新增结点插入在e子树,e子树高度从h - 1变为h并不断更新12->15->10平衡因子,引发旋转,其中12的平衡因子为 - 1,旋转后10和12平衡因子为0,15平衡因子为1。

• 场景2:h >= 1时,新增结点插入在f子树,f子树高度从h - 1变为h并不断更新12->15->10平衡因子,引发旋转,其中12的平衡因子为1,旋转后15和12平衡因子为0,10平衡因子为 - 1。

• 场景3:h == 0时,a / b / c都是空树,b自己就是一个新增结点,不断更新15->10平衡因子,引发旋转,其中12的平衡因子为0,旋转后10、12和15平衡因子均为0。

右左双旋代码实现:

void RotateRL(Node* parent) 
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf; // 记录subRL的平衡因子// 第一步:对右子节点进行右单旋RotateR(subR);// 第二步:对原父节点进行左单旋RotateL(parent);// 根据subRL的原始平衡因子调整平衡因子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 if (bf == 0) {parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else //出现异常错误则报错{assert(false);}
}

6. 辅助接口实现

6.1 查找

与普通 BST 查找逻辑一致,因 AVL 树高度平衡,效率为 O (log n):

Node* Find(const K& key) 
{Node* cur = _root;while (cur) {if (cur->_kv.first < key) {cur = cur->_right;}else if (cur->_kv.first > key) {cur = cur->_left;}else {return cur; // 找到返回节点指针}}return nullptr; // 未找到
}

6.2 中序遍历

中序遍历可输出有序序列,用于验证 AVL 树的 BST 特性:

void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << " ";_InOrder(root->_right);
}

6.3 高度与大小计算

高度与大小的计算与普通二叉树逻辑一致,这里我们不再过多赘述:

int _Height(Node* root) 
{if (root == nullptr){return 0;}int leftH = _Height(root->_left);int rightH = _Height(root->_right);return max(leftH, rightH) + 1;
}int _Size(Node* root) 
{if (root == nullptr){return 0;}return _Size(root->_left) + _Size(root->_right) + 1;
}

6.4 平衡检测(重点)

平衡检测采用后序递归遍历的方式,对每个节点执行以下操作:

• 递归检测左子树:先确认左子树是 AVL 树;

• 递归检测右子树:再确认右子树是 AVL 树;

• 检测当前节点:计算当前节点的实际高度差,与记录的平衡因子比对,同时检查高度差是否合法。

通过该函数我们能判断一棵树是否为AVL树:

bool _IsBalanceTree(Node* root) 
{if (root == nullptr)  // 空树是AVL树(递归终止条件){return true;}// 步骤1:计算当前节点的“实际平衡因子”(右子树高度 - 左子树高度)int leftH = _Height(root->_left);  // 递归计算左子树高度int rightH = _Height(root->_right); // 递归计算右子树高度int realBf = rightH - leftH;       // 实际平衡因子// 步骤2:验证“平衡因子合法性”和“记录一致性”if (abs(realBf) >= 2) {// 高度差≥2,违反AVL树平衡规则cout << "节点" << root->_kv.first << "高度差异常(实际平衡因子:" << realBf << ")" << endl;return false;}if (root->_bf != realBf) {// 记录的平衡因子与实际计算的高度差不一致,说明平衡因子维护逻辑出错cout << "节点" << root->_kv.first << "平衡因子记录异常(记录:" << root->_bf << ",实际:" << realBf << ")" << endl;return false;}// 步骤3:递归检测左右子树,只有左右子树都平衡,当前树才平衡return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

7. 源码

AVLTree.h

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _parent;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;int _bf;AVLTreeNode(const pair<K, V> kv):_kv(kv), _parent(nullptr), _left(nullptr), _right(nullptr), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;public://默认构造AVLTree(){}//拷贝构造AVLTree(const AVLTree& t){_root = copy(t._root);}Node* copy(Node* root){// 如果传入的根节点为空,直接返回空指针,表示拷贝结束if (root == nullptr)return nullptr;// 为新树创建一个与原始根节点值相同的新节点Node* newnode = new Node(root->_kv);// 递归地拷贝原始根节点的左子树,并将结果赋给新节点的左指针newnode->_left = copy(root->_left);// 递归地拷贝原始根节点的右子树,并将结果赋给新节点的右指针newnode->_right = copy(root->_right);// 新节点的父节点默认为空newnode->_parent = nullptr;// 如果新节点的左子节点存在,设置其父节点为新节点if (newnode->_left){newnode->_left->_parent = newnode;}// 如果新节点的右子节点存在,设置其父节点为新节点if (newnode->_right){newnode->_right->_parent = newnode;}// 返回新树的根节点指针return newnode;}//赋值重载(现代写法)AVLTree<K, V>& operator=(const AVLTree <K, V> tmp){swap(_root, tmp._root);return *this;}void swap(Node*& left, Node*& right){Node* tmp = left;left = right;right = tmp;}//析构函数~AVLTree(){Destroy(_root);}void Destroy(Node*& root){if (root == nullptr){return;}//递归销毁左子树Destroy(root->_left);//递归销毁右子树Destroy(root->_right);//销毁根节点delete root;root = nullptr;}bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}//判断插入到左边还是右边if (kv.first < parent->_kv.first){cur = new Node(kv);parent->_left = cur;}else{cur = new Node(kv);parent->_right = cur;}cur->_parent = parent;//控制平衡//更新平衡因子while (parent){if (cur == parent->_left)parent->_bf--;elseparent->_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){RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else{assert(false);}break;}else{assert(false);}}return true;}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* pParent = parent->_parent;//处理subR与parent的父子关系subR->_left = parent;parent->_parent = subR;//处理subRL与parent的关系parent->_right = subRL;if (subRL){subRL->_parent = parent;}//处理subR与pParent的关系subR->_parent = pParent;if (pParent == nullptr){_root = subR;}else{if (pParent->_left == parent){pParent->_left = subR;}else{pParent->_right = subR;}}//设置平衡因子subR->_bf = 0;parent->_bf = 0;}// 右单旋:针对 "左左" 失衡场景(parent->_bf = -2 且 subL->_bf = -1)void RotateR(Node* parent){// 步骤1:保存所有关键节点(必须先保存,再修改指针,避免地址丢失)Node* subL = parent->_left;       // parent的左孩子(旋转后将成为新的父节点)Node* subLR = subL->_right;       // subL的右孩子(旋转后会成为parent的左孩子)Node* pParent = parent->_parent;  // parent的原父节点(用于后续连接新的子树)// 步骤2:调整subL与parent的父子关系subL->_right = parent;  // subL的右孩子指向parent(subL成为parent的父节点)parent->_parent = subL; // parent的父节点更新为subL// 步骤3:处理subLR(若存在)parent->_left = subLR;  // parent的左孩子指向subLR(接收subL原来的右子树)if (subLR != nullptr)   // 若subLR存在,更新其父子关系{subLR->_parent = parent;}// 步骤4:将新的子树根(subL)与原父节点(pParent)连接subL->_parent = pParent;  // subL的父节点设为pParent(替代原parent的位置)if (pParent == nullptr){// 若parent原本是根节点,旋转后subL成为新的根节点_root = subL;}else{// 根据parent原本在pParent中的位置(左/右孩子),让subL替代其位置if (pParent->_left == parent){pParent->_left = subL;  // parent原本是左孩子,subL现在成为左孩子}else{pParent->_right = subL; // parent原本是右孩子,subL现在成为右孩子}}// 步骤5:更新平衡因子(左左场景旋转后,subL和parent的平衡因子均为0)subL->_bf = 0;parent->_bf = 0;}//左右双旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);if (bf == -1){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if (bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}//右左双旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);if (bf == -1){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 1;}else if (bf == 1){subRL->_bf = 0;parent->_bf = -1;subR->_bf = 0;}else if (bf == 0){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 0;}else{assert(false);}}void InOrder(){_InOrder(_root);cout << endl;}int Height(){return _Height(_root);}int Size(){return _Size(_root);}bool IsBalanceTree(){return _IsBalanceTree(_root);}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}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;}int _Size(Node* root){if (root == nullptr)return 0;return _Size(root->_left) + _Size(root->_right) + 1;}bool _IsBalanceTree(Node* root){if (root == nullptr)  // 空树是AVL树(递归终止条件){return true;}// 步骤1:计算当前节点的“实际平衡因子”(右子树高度 - 左子树高度)int leftH = _Height(root->_left);  // 递归计算左子树高度int rightH = _Height(root->_right); // 递归计算右子树高度int realBf = rightH - leftH;       // 实际平衡因子// 步骤2:验证“平衡因子合法性”和“记录一致性”if (abs(realBf) >= 2){// 高度差≥2,违反AVL树平衡规则cout << "节点" << root->_kv.first << "高度差异常(实际平衡因子:" << realBf << ")" << endl;return false;}if (root->_bf != realBf){// 记录的平衡因子与实际计算的高度差不一致,说明平衡因子维护逻辑出错cout << "节点" << root->_kv.first << "平衡因子记录异常(记录:" << root->_bf << ",实际:" << realBf << ")" << endl;return false;}// 步骤3:递归检测左右子树,只有左右子树都平衡,当前树才平衡return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);}private:Node* _root = nullptr;
};

test.cpp

#include<vector>
#include"AVLTree.h"void TestAVLTree1()
{AVLTree<int, int> t;// 常规的测试用例int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };// 特殊的带有双旋场景的测试用例//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){t.Insert({ e, e });}t.InOrder();cout << t.IsBalanceTree() << endl;
}// 插入一堆随机值,测试平衡,顺便测试一下高度和性能等
void TestAVLTree2()
{const int N = 1000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}size_t begin2 = clock();AVLTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));}size_t end2 = clock();cout << "Insert:" << end2 - begin2 << endl;cout << t.IsBalanceTree() << endl;cout << "Height:" << t.Height() << endl;cout << "Size:" << t.Size() << endl;size_t begin1 = clock();// 确定在的值for (auto e : v){t.Find(e);}// 随机值//for (size_t i = 0; i < N; i++)//{//	t.Find((rand() + i));//}size_t end1 = clock();cout << "Find:" << end1 - begin1 << endl;
}int main()
{TestAVLTree1();//TestAVLTree2();return 0;
}

结语

好好学习,天天向上!有任何问题请指正,谢谢观看!

http://www.dtcms.com/a/582857.html

相关文章:

  • py day33 异常处理
  • 网站开发 相册网站备案 地域
  • 基于asp网站开发 论文装潢设计网站
  • 算法763. 划分字母区间
  • JVM组件协同工作机制详解
  • 使用 FastAPI+FastCRUD 快速开发博客后端 API 接口
  • 网站底部版权信息网页游戏开服表大全
  • 系统运维Day02_数据同步服务
  • 与设计行业相关的网站四川省住房与城乡建设厅网站
  • 深圳市设计网站缪斯设计网站
  • 现在还有做系统的网站吗wordpress摄影主题 lens
  • OLEDB连接对象介绍(一)
  • 【申论】申论基础知识
  • 商务网站建设调研host wordpress
  • 一款AB实验分析智能体是如何诞生的
  • 你的MES系统,是在“记录过去”还是在“指挥未来”?
  • FPGA教程系列-Vivado中串行FIR设计(非FIR核)
  • I2C接口(2):IIC多主设备仲裁机制详解--从原理到Verilog实现
  • 技术网站推广范例怎么建立自己公司的网站
  • 网站的设计公司网咖活动营销方案
  • 北京市朝阳区网站开发公司中国建设监理网站
  • 多语言网站是怎么做的交互设计网站有哪些
  • iis部署网站浏览报404建设网站公司塞尼铁克
  • 使用 PyTorch来构建线性回归的实现
  • 营销型网站设计公司企业网站模板下载服务哪家好
  • 对接物联网使用netty通信与MQTT之间的区别
  • 重塑城市公共安全管理的“智慧之眼”
  • 临海建设局官方网站plc编程入门基础知识
  • 有教做衣服的网站吗免费签名logo设计
  • 2.2.STM32-新建工程