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

AVL树实现

一、AVL的概念

        AVL树是最先发明的自平衡二叉查找树,AVL是一颗空树,或者具有下列性质的二叉搜索树:其左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树是一颗高度平衡搜索二叉树,通过控制高度差去控制平横。

        AVL树实现这里我们引入一个平衡因子的概念,每个结点都有一个平衡因子,任何节点的平衡因子等于右子树的高度减去左子树的高度,也就是说,任何平衡因子等于0/-1/1,AVL树并不是必须要平衡因子,但有了平衡因子可以更方便我们去进行观察和控制树是否平衡。

        AVL树整体结点数量和分布和完全二叉树类似,高度可以控制在logN,那么增删查改的效率也可以控制在O(logN),相比二叉树有了本质的提升。

                

如上图:为一颗AVL树

如上图:当其插入13后,右子树不满足AVL树的概念,该进行调整。

二、AVL树的实现

        1.AVL树的结构

        

template<class T>
struct AVLTreeNode {using Node = AVLTreeNode<T>;
AVLTreeNode(const T& data=T()) :_parent(nullptr),_left(nullptr),_right(nullptr),_data(data),_bf(0){}Node* _parent;Node* _left;Node* _right;T _data;int _bf;
};

节点定义为结构体,有三叉指针及其数据存储和平衡因子构成

template<class T,class Compare=Less<T>>
class AVLTree {
public:using AVLNode = AVLTreeNode<T>;
//...
private:
//...Compare compare;AVLTreeNode<T>* _root;
};

AVL树储存根节点,这里我还尝试定义仿函数来控制比较。

        2.AVL树的插入

2.1AVL树插入一个值的大概过程

        (1)插入一个值,按照二叉搜索树规则进行插入。

        (2)新增结点以后,只会影响祖先节点的高度,也就是可能影响部分祖先结点的平衡因子,所以更新从新增节点到根节点路径上的平衡因子,实际中最好的情况下要更新到跟,有些情况更新到中间就停止了。

        (3)更新平衡因子过程中没有出现问题,则插入结束

        (4)更新平衡因子过程中出现不平衡,对不平衡子树旋转,旋转后本质调平衡的同时,还降低了子树的高度,不会再影响上一层,所以插入结束。

总结就是:更新平衡因子->满足->结束

                                        ->不满足->旋转

2.2平衡因子的更新

        更新原则:

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

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

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

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

        更新停止条件:

        更新后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也停止了

        

插入结点及更新平衡因子的代码实现

	bool insert(const T& data) {AVLTreeNode<T>* Node = new AVLTreeNode<T>(data);if (_root == nullptr) {_root = Node;}AVLTreeNode<T>* parent = _root;while (parent!=nullptr) {//右边if (compare(parent->_data, data)) {if (parent->_right != nullptr) {parent = parent->_right;}else {parent->_right = Node;Node->_parent = parent;break;}}//去重else if (data == parent->_data) {return false;}//左边else {if (parent->_left != nullptr) {parent = parent->_left;}else {parent->_left = Node;Node->_parent = parent;break;}}}while (parent) {//如果更新到根节点,就结束//更新平衡因子if (parent->_left != nullptr) {if (parent->_left == Node)parent->_bf--;}if (parent->_right != nullptr) {if (parent->_right == Node)parent->_bf++;}//判断//等于0结束if (parent->_bf == 0) {break;return true;}//等于1/-1继续向上更新else if(parent->_bf==1||parent->_bf==-1){Node = parent;parent = parent->_parent;}///等于2/-2不平衡了,开始旋转else if (parent->_bf == 2 || parent->_bf == -2) {//此处为旋转代码break;}else {assert(false);}}return true;}

        3.旋转

3.1旋转的原理

        1.保持搜索树的规则

        2.让旋转的树从不平衡变平衡,其次降低树的高度

        旋转分为四种,左单旋/右单旋/左右双旋/右左双旋

3.2右单旋

        如图,展示的是以10为根的树,有a/b/c抽象为三棵高度为h的子树(h>=0)(为什么要抽象,是因为情况会有很多很多中,这里以简代繁),a/b/c均符合AVL树的要求,它代表了所有右单旋的情景,实际右单旋的形态有很多种。

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

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

        

        这种情况下,a/b/c都为空,与上面一样。

3.3 右单旋代码实现

        

void RotateR(AVLNode* parent) {AVLNode* SubL = parent->_left;AVLNode* NodeR = SubL->_right;//爷爷辈和左子树根节点连if (parent == _root) {//父节点为根节点,左子树的根节点要成为AVL树的根结点,因此将其parent置为nullptr_root = SubL;SubL->_parent = nullptr;}else {//不为根节点就要找爷爷结点,因为右旋后父结点会改变,爷爷结点要与父亲结点相连AVLNode* pparent = parent->_parent;//找父节点是爷爷结点的左结点还是右结点if (pparent->_right == parent) {pparent->_right= SubL;SubL->_parent = pparent;}else {pparent->_left = SubL;SubL->_parent = pparent;}}//左子树和parent左链接parent->_left = SubL->_right;if(NodeR!=nullptr)NodeR->_parent = parent;//parent和左子树的根节点连parent->_parent = SubL;SubL->_right = parent;//更新平衡因子SubL->_bf = 0;parent->_bf = 0;
}

3.4 左单旋

        如图展示的是10为根的树,有a/b/c抽象为三棵高度为h的子树(h>=0),a/b/c均符合AVL树的要求,10可以是整棵树的根也可以是局部的根。

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

        旋转核心步骤,因为10<b的子树<15,因此,让b作为10的子树,15作为a和10的parent,符合搜索树的规则,控制了平衡,同时这棵树的高度恢复到了插入之前的h+2,符合原则,如果插入之前10整颗树的一个局部子树,旋转后不会再影响上一层,插入结束。

 3.5 左单旋代码实现

//左单旋
void RotateL(AVLTreeNode<T>* parent) {AVLNode* SubR = parent->_right;AVLNode* NodeL = SubR->_left;//爷爷辈和左子树根节点连if (parent == _root) {//parent为根节点,此时右子树的根节点要成为AVL树的根结点,右子树的根结点的parent要为nullptr_root = SubR;SubR->_parent = nullptr;}else {//不为空,则找爷爷结点,让右子树的根节点的parent为爷爷结点AVLNode* pparent = parent->_parent;//找parent为爷爷结点的左孩子还是右孩子,因为要更新parent结点if (pparent->_left == parent) {pparent->_left = SubR;SubR->_parent = pparent;}else {pparent->_right = SubR;SubR->_parent = pparent;}}//左子树和parent左链接parent->_right = SubR->_left;if(NodeL!=nullptr)NodeL->_parent = parent;//parent和左子树的根节点连parent->_parent = SubR;SubR->_left = parent;//更新平衡因子SubR->_bf = 0;parent->_bf = 0;
}

3.6 左右双旋

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

图一

图二

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

        场景1:h>=1时,新增结点插入在e子树,e子树高度变为h并不断更新8  5  10平衡因子,导致旋转,其中10的平衡因子为-2,5的平衡因子为1,符合(左边高的右边高)进行左右双旋,旋转后的5平衡因子为0,8的平衡因子也为0,10的平衡因子为1.

        场景2:h>=1时,新增结点插入在f子树,f子树的高度变为h并不断更新8 5 10平和因子,导致旋转,其中10的平衡因子为-2,5的平衡因子为1,符合(左边高的右边高)进行左右双旋,旋转后5

的平横因子为-1,8的平衡因子为0,10的平衡因子为0.

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

3.7 左右双旋代码实现

//左右双旋
void RotateLR(AVLNode* pParent) {//左边高的右边高AVLNode* parent = pParent->_left;AVLNode* SubR = parent->_right;int bf = SubR->_bf;RotateL(parent);RotateR(pParent);if (bf == 0) {SubR->_bf = 0;parent->_bf = 0;pParent->_bf = 0;}else if (bf == 1) {parent->_bf = -1;SubR->_bf = 0;pParent->_bf = 0;}else if (bf == -1) {parent->_bf = 0;SubR->_bf = 0;pParent->_bf = 1;}
}

3.8 右左双旋

与左右双旋类似

3.9 右左双旋代码实现

//右左双旋
void RotateRL(AVLNode* pParent) {//右边高的左边高AVLNode* parent = pParent->_right;AVLNode* SubL = parent->_left;int bf = SubL->_bf;RotateR(parent);RotateL(pParent);if (bf == 0) {SubL->_bf = 0;parent->_bf = 0;pParent->_bf = 0;}else if (bf == 1) {parent->_bf = 0;SubL->_bf = 0;pParent->_bf = -1;}else if (bf == -1) {parent->_bf = 1;SubL->_bf = 0;pParent->_bf = 0;}
}

4.AVL树平衡检测

        我们实现的AVL树是否合格,我们通过检查左右子树高度差的程序进行反向验证,同时检查一下结点的平衡因子更新是否出了问题。

//判断树深int _Heighttree(AVLTreeNode<T>* root) {if (root == nullptr) {return 0;}return _Heighttree(root->_left) >_Heighttree(root->_right)? _Heighttree(root->_left) + 1: _Heighttree(root->_right)+1;}
//判断是不是AVL树bool	_IsAVLTree(AVLNode* root) {	if (root == nullptr) {return true;}bool left = _IsAVLTree(root->_left);bool right = _IsAVLTree(root->_right);if (_Heighttree(root->_right) - _Heighttree(root->_left) == root->_bf) {return true&& left && right;}else {return false;}}

测试代码

void AVLTreeTest() {const int N = 100000;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> t;for (auto e : v) {t.insert(e);}size_t end2 = clock();cout << "insert:" << end2 - begin2 << endl;cout << t.IsAVLTree() << endl;cout <<"height:"<< t.Heighttree() << endl;cout << "size:" << t.size() << endl;}
http://www.dtcms.com/a/582542.html

相关文章:

  • Vue 组件插槽的深层传递
  • HENGSHI SENSE 6.1 发布,从 ChatBI 到 Agentic Analytics
  • 网站 哪些服务器wordpress新编辑器分类
  • 网站进度条源代码juqery-ui泰安网站建设公司
  • 11月7日星期五今日早报简报微语报早读
  • 网站维护一般都是维护什么公司注册网站需要提供什么文件
  • 网站开发现在是热门专业吗福建网站建建设
  • wordpress 首页无法访问seo信息编辑招聘
  • Nginx配置DNS缓存
  • 生信工作流框架搭建 | 01-nextflow、snakemake、wdl 对比测试
  • Windows 下 ROS/ROS2 开发环境最优解:WSL 比直接安装、虚拟机、双系统更优雅!
  • (Linux (6):从包管理到工具探索,构建系统操作基础认知)
  • 网站建设哪家专业为什么电脑打不开网页
  • wordpress 4.7.9seo推广有哪些
  • 读取证书问题so调用问题
  • Jottings-Lishaozhuo 2025.11.7
  • 聊聊怎么更好去设计数据库表
  • STM32H743-ARM例程40-U_DISK_IAP
  • 网站开发 实时更新做网站做的好的公司有哪些
  • 专门做酒店设计的网站源码屋整站源码
  • Memory MCP(记忆服务器)
  • Java基础 | 基本类型与引用类型使用规范
  • 香港1核2G云服务器当网站服务器够用不?
  • 用Python来学微积分36-牛顿 - 莱布尼茨公式的深度解析
  • 网站建设规章制度软件app开发需要多少钱
  • 网站建设外出考察信息家在深圳房网论坛首页
  • 测试开发工程师面经准备(sxf)
  • 2026商业航天展,中国商业航天规模化发展的加速器
  • 网站建设与推广范文防下载 的视频网站 怎么做
  • TrueNas Scale配置immich集成GPU硬件加速,用于智能模型,人脸识别,OCR文本识别加速