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

二叉平衡搜索树:AVL树

目录

一、AVL树的概念

1.平衡因子

二、AVL树的核心操作

2.1、AVL树节点定义

2.2、AVL树的插入

2.2.1、AVL树的旋转分析

2.2.2、双旋平衡因子分析

2.3、AVL的删除

三、验证AVL树的平衡

四、完整代码


引言

什么是AVL树?

AVL树也是一颗二叉搜索树,只不过AVL树在二叉搜索树的基础上做了平衡处理。通过平衡因子保证树的平衡。平衡树的高度。

为什么需要AVL树?

二叉搜索树(BST)的查找、插入和删除操作在理想情况下时间复杂度为 O(log⁡n),但若插入数据是有序的(例如依次插入1,2,3,…,n),BST会退化为链表,操作效率降至 O(n)。因为二叉搜索树的效率跟其高度有关,所以为了避免退化成链表,只需对树的高度进行控制就行,而AVL树通过动态调整树的结构,确保树的所有操作的时间复杂度稳定在O(logn)。


一、AVL树的概念

1.平衡因子

AVL树的每个节点都有一个平衡因子,定义为:

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

AVL树要求所有节点的平衡因子必须为-1,0,1。如果某个节点的平衡因子的绝对值大于1,则说明该树失衡,需要通过旋转操作调整。

结构如下:


二、AVL树的核心操作

2.1、AVL树节点定义

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):_parent(nullptr),_left(nullptr),_right(nullptr),_kv(kv),_bf(0){}
};
  • 父节点指针:在 AVL 树的插入和删除操作中,需要通过父节点来调整树的结构。例如,在旋转操作中,需要知道当前节点的父节点,以便正确地调整父子关系。


2.2、AVL树的插入

思路:

  1. 按照二叉搜索树的方式插入新节点。
  2. 更新平衡因子,判断树是否平衡,需不需要调整。

场景分析:
1、新增节点在左,parent平衡因子减减

2、新增节点在右,parent平衡因子加加

3、更新后parent平衡因子==0,说明parent所在子树的高度不变,不会影响祖先,不用再沿着到root的路径往上更新

4、更新后parent平衡因子==1或-1,说明parent所在子树的高度变化,会影响祖先,需要继续沿着到root的路径往上更新

5、更新后parent平衡因子==2或-2,说明parent所在子树的高度变化且不平衡,对parent所在子树进行旋转,让树平衡

bool insert(const pair<K, V>& kv)
{//插入新节点操作和二插搜索树一样,这里先不写//......//AVL平衡,更新平衡因子while (parent){if (parent->_right == cur){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;
}

2.2.1、AVL树的旋转分析

旋转的四种情况:
1、新节点插入较高右子树的右侧--左单旋

核心操作:
parent->right = cur->left

cur->left = parent

旋转需要注意的问题:

1、保证旋转完还是搜索树

2、变成平衡树且降低该树高度

左旋代码:

void RotateL(Node* parent) 
{Node* cur = parent->_right; Node* curleft = cur->_left;// 将原父节点的右子节点指向当前节点的左子节点parent->_right = curleft;if (curleft) {curleft->_parent = parent; // 更新左子节点的父节点为原父节点}Node* ppnode = parent->_parent; // 获取原父节点的父节点cur->_left = parent; // 将原父节点设置为当前节点的左子节点parent->_parent = cur; // 更新原父节点的父节点为当前节点// 如果原父节点是根节点,则更新根节点为当前节点if (parent == _root){_root = cur;cur->_parent = nullptr;}else{// 如果原父节点不是根节点,更新其父节点的相应子节点为当前节点if (ppnode->_left == parent){ppnode->_left = cur;cur->_parent = ppnode;}else{ppnode->_right = cur;cur->_parent = ppnode;}}// 更新平衡因子,由于是左旋,原父节点和当前节点的平衡因子都设置为0parent->_bf = cur->_bf = 0;
}

2、新节点插入较高左子树的左侧--右单旋

核心操作:
parent->left = cur->right

cur->right = parent

原理跟上面类似

右单旋代码:

void RotateR(Node* parent) 
{Node* cur = parent->_left; Node* curright = cur->_right; // 将原父节点的左子节点指向当前节点的右子节点parent->_left = curright;if (curright){curright->_parent = parent; // 更新右子节点的父节点为原父节点}Node* ppnode = parent->_parent;cur->_right = parent; // 将原父节点设置为当前节点的右子节点parent->_parent = cur; // 更新原父节点的父节点为当前节点// 如果原父节点是根节点,则更新根节点为当前节点if (parent == _root){_root = cur; cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;cur->_parent = ppnode; }else{ppnode->_right = cur; cur->_parent = ppnode;}}// 更新平衡因子,由于是右旋,原父节点和当前节点的平衡因子都设置为0parent->_bf = cur->_bf = 0;
}

3、新节点插入较高左子树的右侧---左右双旋:先左单旋再右单旋

下面图中的h表示树的高度

在b或c任意位置插入新节点都会导致双旋:

先对30进行左单旋,然后再对90进行右单旋

4、新节点插入较高右子树的左侧---右左双旋:先右单旋再左单旋

2.2.2、双旋平衡因子分析

以此图(左右双旋)为例:

从左右双旋的结果上来看:

60的左边给了30的右边,

60的右边给了90的左边,

60成了这棵树的根

可以看出,最后平衡因子的更新结果跟60这个节点的平衡因子有关。

  1. 当60的平衡因子==0时,说明60就是新增,最后旋转的结果:30和90的平衡因子==0。
  2. 当60的平衡因子==-1时,说明在60的左边新增了节点,那么最后旋转的结果肯定会让30右边减少一个节点,30节点的平衡因子就会减一个 (30->bf==0);90节点的左边节点高度就会比右边高度少一个,那么(90->bf==1)。
  3. 当60的平衡因子==1时,说明新增节点在60的右边,那么最后30节点的右边就少两个节点(30->bf==-1);90节点的左边少两个节点,则(90->bf==0)。
void RotateLR(Node* parent) // 左右双旋
{Node* cur = parent->_left; Node* curright = cur->_right; int bf = curright->_bf; // 保存当前节点右子节点的平衡因子RotateL(parent->_left);RotateR(parent);if (bf == 0){parent->_bf = 0; cur->_bf = 0; curright->_bf = 0; }else if (bf == 1){// 如果当前节点的右子节点的平衡因子为1,说明在右子树新增了节点parent->_bf = 0; // 父节点的平衡因子为0cur->_bf = -1; // 当前节点的平衡因子为-1(左子树比右子树高)curright->_bf = 0; // 当前节点的右子节点的平衡因子为0}else if (bf == -1){// 如果当前节点的右子节点的平衡因子为-1,说明在左子树新增了节点parent->_bf = 1; // 父节点的平衡因子为1(右子树比左子树高)cur->_bf = 0; // 当前节点的平衡因子为0curright->_bf = 0; // 当前节点的右子节点的平衡因子为0}else{assert(false); // 如果平衡因子不在[-1, 1]范围内,抛出断言错误}
}

右左双旋的平衡因子跟上面类似。

void RotateRL(Node* parent) // 右左双旋
{Node* cur = parent->_right; Node* curleft = cur->_left; int bf = curleft->_bf; // 保存当前节点左子节点的平衡因子RotateR(parent->_right);RotateL(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0; curleft->_bf = 0; }else if (bf == 1){// 如果当前节点的左子节点的平衡因子为1,说明在右子树新增了节点parent->_bf = -1; // 父节点的平衡因子为-1(左子树比右子树高)cur->_bf = 0; // 当前节点的平衡因子为0curleft->_bf = 0; // 当前节点的左子节点的平衡因子为0}else if (bf == -1){// 如果当前节点的左子节点的平衡因子为-1,说明在左子树新增了节点parent->_bf = 0; // 父节点的平衡因子为0cur->_bf = 1; // 当前节点的平衡因子为1(右子树比左子树高)curleft->_bf = 0; // 当前节点的左子节点的平衡因子为0}else{assert(false); // 如果平衡因子不在[-1, 1]范围内,抛出断言错误}
}

2.3、AVL的删除

AVL节点的删除可以看看这篇

【高阶数据结构】平衡二叉树(AVL)的删除和调整_平衡二叉树删除节点后怎么调整-CSDN博客


三、验证AVL树的平衡

// AVL树的验证(平衡因子方向:右子树高度 - 左子树高度)
bool IsAVLTree() 
{return _IsValidBST(_root) && _IsAVLTree(_root);
}// 验证BST性质
bool _IsValidBST(Node* root, Node* min = nullptr, Node* max = nullptr) 
{if (root == nullptr) return true;if ((min && root->key <= min->key) || (max && root->key >= max->key)) {return false;}return _IsValidBST(root->left, min, root) && _IsValidBST(root->right, root, max);
}// 递归验证平衡因子和高度
bool _IsAVLTree(Node* pRoot) 
{if (pRoot == nullptr) {return true;}// 计算平衡因子(右减左)int rightH = _Height(pRoot->_right);int leftH = _Height(pRoot->_left);int bf = rightH - leftH;// 检查平衡因子是否合法if (bf != pRoot->_bf || bf < -1 || bf > 1) {return false;}// 递归检查子树return _IsAVLTree(pRoot->_left) && _IsAVLTree(pRoot->_right);
}// 高度计算(空节点高度为0)
int _Height(Node* pRoot) 
{if (pRoot == nullptr) {return 0;}return 1 + std::max(_Height(pRoot->_left), _Height(pRoot->_right));
}

四、完整代码

#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):_parent(nullptr),_left(nullptr),_right(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){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->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//AVL平衡,更新平衡因子while (parent){if (parent->_right == cur){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);}break;}else{assert(false);}}return true;}void RotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}Node* ppnode = parent->_parent;cur->_left = parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;cur->_parent = ppnode;}else{ppnode->_right = cur;cur->_parent = ppnode;}}parent->_bf = cur->_bf = 0;}void RotateR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}Node* ppnode = parent->_parent;cur->_right = parent;parent->_parent = cur;if (_root == parent){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;cur->_parent = ppnode;}else{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);RotateL(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curleft->_bf = 0;}else if (bf == 1){parent->_bf = -1;cur->_bf = 0;curleft->_bf = 0;}else if (bf == -1){parent->_bf = 0;cur->_bf = 1;curleft->_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){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else{assert(false);}}void Print(){_Print(_root);}void _Print(Node* root){if (root == nullptr){return;}_Print(root->_left);cout << root->_kv.first << " ";_Print(root->_right);}// AVL树的验证bool IsAVLTree(){return _IsAVLTree(_root);}// 根据AVL树的概念验证pRoot是否为有效的AVL树bool _IsAVLTree(Node* pRoot){if (pRoot == nullptr){return true;}int h = _Height(pRoot->_right) - _Height(pRoot->_left);if (h != pRoot->_bf || h > 1 || h < -1){return false;}return _IsAVLTree(pRoot->_left) && _IsAVLTree(pRoot->_right);}size_t _Height(Node* pRoot){if (pRoot == nullptr){return 0;}int leftH = _Height(pRoot->_left);int rightH = _Height(pRoot->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}private:Node* _root = nullptr;
};

相关文章:

  • 【前端vue生成二维码和条形码——MQ】
  • TMS320F28P550SJ9学习笔记17:Lin通信SCI模式完整的收发配置
  • 【实测案例】分布式光纤嵌入U型复材无损强度检测
  • Windows系统安装RustDesk Server的详细步骤和客户端设置
  • 车载诊断架构 --- 车载诊断概念的深度解读
  • Thin-Agent服务(TAS)概述
  • 无头开发模式
  • Vue接口平台学习九——接口用例页面1
  • 15-算法打卡-哈希表-有效的字母异位词-leetcode(242)-第十五天
  • 通信安全员历年考试重难点有哪些?
  • 从0开始掌握动态规划
  • 跟康师傅学Java-面向对象(基础)
  • 秒杀系统解决两个核心问题的思路方法总结:1.库存超卖问题;2.用户重复抢购问题。
  • linux 内核 container_of 宏的原理
  • 批量上传OpenStack镜像
  • python中参数前**的含义
  • 数据结构-前缀树
  • 【Vue 2中的emits声明与Vue 3的defineModel宏函数详解】
  • 蓝牙网关的功能与应用场景
  • Doris的向量化执行如何支撑分布式架构和复杂查询
  • 黄宾虹诞辰160周年|一次宾翁精品的大集结
  • 上海74岁老人宜春旅游时救起落水儿童,“小孩在挣扎容不得多想”
  • 广西干旱程度有所缓解,未来一周旱情偏重地区降水量仍不足
  • 幸福航空取消“五一”前航班,财务人员透露“没钱飞了”
  • 财政部下达农业生产防灾救灾资金3.76亿元,支持黄淮海等地抗旱保春播
  • 哈马斯同意释放剩余所有以色列方面被扣押人员,以换取停火五年