C++数据结构——AVL树

前言:本篇文章将紧随二叉搜索树的节奏,分享一个新的数据结构——AVL树。


目录

一.AVL树概念

二.AVL树插入规则

三.AVL树实现

1.基本框架

2.插入

3.旋转

1)左\右单旋

2)左右/右左双旋

4.遍历

5.求树高度

6.判断平衡

7.求树高度

总结


一.AVL树概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。

但是当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度

所以AVL树即:一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)


二.AVL树插入规则

由于AVL树的独特结构,我们给出以下的插入规则: 

1.按照搜索树规则插入。

2.更新插入节点的祖先节点的平衡因子:

        a.插入父亲的左边,父亲的平衡因子--

        b.插入父亲的右边,父亲的平衡因子++

        c.父亲的平衡因子 == 0,父亲所在的子树高度不变,不再往上更新,插入结束。

        d.父亲平衡因子 == 1 or -1,父亲所在的子树高度变了,往上更新,重复以上步骤。

        e.父亲平衡因子 == 2 or -2,父亲所在的子树已经不平衡了,需要旋转处理


三.AVL树实现

1.基本框架

template<class K,class V>
struct AVLTreeNode
{struct AVLTreeNode<K,V>* _left;struct AVLTreeNode<K,V>* _right;struct AVLTreeNode<K,V>* _parent;int _bf;pair<K, V> _kv;AVLTreeNode(const pair<K,V>& kv):_left(nullptr), _right(nullptr),_parent(nullptr), _kv(kv),_bf(0){}
};template<class K,class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
private:Node* _root;
};

基本框架与平衡二叉树类似,区别在于AVL树的节点为键值对

同时我们还需增加平衡因子_bf和父节点_parent,方便我们进行调整。


2.插入

	//插入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;}elsereturn false;}//插入cur = new Node(kv);if (kv.first < parent->_kv.first)parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;//更新平衡因子return true;}

基本的插入步骤与平衡二叉树一模一样,需要关注的就是插入的节点变为键值对

下面我们单独来看如何更新平衡因子

        while (parent){if (cur == 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){//出现问题,进行旋转break;}elseassert(false);}

按照我们上边的规则其实很好写出上述代码,要注意循环条件为parent如果没有父亲,也就是到达了根节点,那就无法再进行调整。 

下面我们来重点关注,如何进行旋转


3.旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

  1.  新节点插入较高左子树的左侧---左左:右单旋。
  2.  新节点插入较高右子树的右侧---右右:左单旋。
  3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋。
  4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋。

下面我们就一一来看这四种情况。


1)左\右单旋

先来看右单旋,可以抽象理解为,左子树过高,需要向右边旋转拉低。 

从上图能够看出,右单旋的步骤为:

  1. 让平衡因子为-2的节点成为它的左子节点的右子节点;
  2. 同时让该左子节点的右子节点成为平衡因子为-2的节点的左子节点。

同时我们需要关注的细节是:

  • 平衡因子为-2的节点是否为根节点。如果不是根节点则需要调整其父节点的指向。
  • 左子节点的右子节点是否为空。

通过这样的调整,就可以实现平衡,同时调整的两个关键节点的平衡因子均归0。 

下面来看代码:

	void RotateR(Node* parent){//定义左子节点Node* subL = parent->_left;//定义左子节点的右子节点Node* subLR = subL->_right;//调整parent->_left = subLR;//判空if (subLR)subLR->_parent = parent;//调整subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (parent == _root)//判断是否为根{_root = subL;_root->_parent = nullptr;}else//不是根节点,调整父节点指向{if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}//平衡因子归0parent->_bf = subL->_bf = 0;}

再来看左单旋: 

 左单旋则与右单旋完全相反,所以我们不做过多解释,直接给出代码:

	//左单旋void RotateL(Node* parent){//定义右子节点Node* subR = parent->_right;//定义右子节点的左子节点Node* subRL = subR->_left;//调整parent->_right = subRL;//判空if (subRL)subRL->_parent = parent;//调整subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root)//判断是否为根{_root = subR;_root->_parent = nullptr;}else//不是根节点,调整父节点指向{if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_right = subR;subR->_parent = ppNode;}//平衡因子归0parent->_bf = subR->_bf = 0;}

2)左右/右左双旋

如果说树并不是子树的一条斜边独高,而是折线型的一颗子树高,此时单靠单旋是解决不了问题的,因此需要通过双旋来解决

上图所示为先左后右的折线型,所以我们需要进行左右双旋,步骤为:

  1. 先从折线的折点位置,即上图的30位置,进行左单旋,使树变为左边一条斜边独高的树。
  2. 在从折线起点位置进行右单旋。
  3. 更新平衡因子。

其中更新平衡因子也分为不同的情况,以上图为例:

  • 如果新节点插入位置为60的左,那么旋转后60为0,30为0,90为1。
  • 如果新节点插入位置为60的右,那么旋转后60为0,30为-1,90为0。
  • 如果新节点就是60,那么三者的平衡因子均为0。

下面上代码:

	//左右双旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else{assert(false);}}

 注意更新平衡因子是通过初始时折线末点的平衡因子判断的,所以要提前记录。


再来看右左双旋

 与左右双旋相反,右左双旋是先右后左的折线,所以其操作步骤与之相反:

  1. 先从折线的折点位置,即上图的90位置,进行右单旋,使树变为右边一条斜边独高的树。
  2. 在从折线起点位置进行左单旋。
  3. 更新平衡因子。

其中更新平衡因子也同样分为不同的情况,以上图为例:

  • 如果新节点插入位置为60的左,那么旋转后60为0,30为0,90为1。
  • 如果新节点插入位置为60的右,那么旋转后60为0,30为-1,90为0。
  • 如果新节点就是60,那么三者的平衡因子均为0。

代码如下:

	//右左双旋void RotateLR(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else{assert(false);}}

根据父节点及其左右子节点的平衡因子,即可判断对应的旋转方式,下面补充插入步骤:

 

			else if (parent->_bf == 2 || parent->_bf == -2){//出现问题,进行旋转//左单旋if (parent->_bf == -2 && parent->_left->_bf == -1){RotateL(parent);}//右单旋else if (parent->_bf == 2 && parent->_right->_bf == 1){RotateR(parent);}//左右单旋else if (parent->_bf == -2 && parent->_left->_bf == 1){RotateLR(parent);}//右左单旋else{RotateRL(parent);}break;}

4.遍历

遍历操作与二叉搜索树类似,需要修改的是我们需要将键值对均打印出来:

	//遍历void InOrder(){inOrder(_root);cout << endl;}void inOrder(const Node* root){if (root == nullptr){return;}inOrder(root->_left);cout << root->_kv.first << ':' << root->_kv.second << " ";inOrder(root->_right);}

为了方便调用函数而无需传参,我们采用如上方式进行代码编写。 


5.求树高度

求树高度我们前边在讲解二叉树的时候已经分享过了,只需求出左右子树高度的最大值+1即可,通过递归计算:

	//求树高度int Height(const Node* root){if (root == nullptr)return 0;return max(Height(root->_left), Height(root->_right)) + 1;}

6.判断平衡

判断树是否平衡,即判断两棵子树的高度差是否大于等于2

	//判断平衡bool IsBalance(){return isBalance(_root);}bool isBalance(const Node* root){if (root == nullptr)return true;int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);if (abs(leftHeight - rightHeight) >= 2)return false;//检查平衡因子if (rightHeight - leftHeight != root->_bf)return false;return isBalance(root->_left) && isBalance(root->_right);}

同时还需要通过递归来判断各个子树是否平衡


7.求树高度

求树的大小,通过递归即求左子树的大小+右子树的大小+根节点:

	//求树大小int Size(){return size(_root);}int size(const Node* root){if (root == nullptr)return 0;return size(root->_left) + size(root->_right) + 1;}

总结

关于AVL树的基本内容就分享这么多,喜欢本篇文章的小伙伴记得一键三连,我们下期再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/685428.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

word转pdf的java实现(documents4j)

一、多余的话 java实现word转pdf可用的jar包不多&#xff0c;很多都是收费的。最近发现com.documents4j挺好用的&#xff0c;它支持在本机转换&#xff0c;也支持远程服务转换。但它依赖于微软的office。电脑需要安装office才能转换。鉴于没在linux中使用office&#xff0c;本…

PTP 对时协议 IEEE1588 网络对时 硬件基础

前言 在很多应用场景有精确对时的需求&#xff0c;例如车载网络&#xff0c;音视频流&#xff0c;工业网络。本文档将会阐述对时的硬件需求&#xff0c;网卡硬件以 Synopsys DesignWare Cores Ethernet MAC QoS 为例来展开说明。 协议 流行的协议为 IEEE1588 标准指定的对时…

Django 静态文件管理与部署指南

title: Django 静态文件管理与部署指南 date: 2024/5/10 17:38:36 updated: 2024/5/10 17:38:36 categories: 后端开发 tags: WebOptCDN加速DjangoCompressWebpackStaticDeployCICD-ToolsSecStatic 第一章&#xff1a;介绍 Django 静态文件的概念和重要性 在 Web 开发中&a…

爆爽,英语小白怒刷 50 课!像玩游戏一样学习英语~

重点!!!(先看这) 清楚自己学英语的目的, 先搞清楚目标&#xff0c;再行动自身现在最需要的东西&#xff1a;词汇量&#xff1f;口语&#xff1f;还是阅读能力&#xff1f;找对应的书籍,学习资料往兴趣靠拢&#xff1a;网上有大量的推荐美剧学习、小说学习&#xff0c;不要被他…

技术创作者在千帆AppBuilder中获得的极致体验

目录 前言 千帆AppBuilder简介 传统的技术文章写作方式 借助千帆AppBuilder提高写作质量和效率 千帆AppBuilder详细搭建步骤 1、注册百度智能云账号 2、登录百度智能云控制台 3、创建千帆AppBuilder应用 4、配置千帆AppBuilder应用 5、调试和发布千帆AppBuilder应用 …

你写的每条SQL都是全表扫描吗

你写的每条SQL都是全表扫描吗&#xff1f;如果是&#xff0c;那MySQL可太感谢你了&#xff0c;每一次SQL执行都是在给MySQL上压力、上对抗。MySQL有苦难言&#xff1a;你不知道索引吗&#xff1f;你写的SQL索引都失效了不知道吗&#xff1f;慢查询不懂啊&#xff1f;建那么多索…

Dependencies:查找项目中dll关联文件是否缺失。

前言 Dependencies工具作为一款优秀的DLL解析工具&#xff0c;能让你很直观地看到DLL的相关信息&#xff0c;如具备哪些功能函数、参数&#xff0c;又比如该DLL基于哪些DLL运行。判断该dll基于哪些dll运行&#xff0c;如果基于的dll丢失&#xff0c;那么就会提示。就能判断缺少…

Docker笔记(七)使用Docker部署Spring Boot项目

本文介绍如何使用Docker打包并部署Spring Boot多模块项目。 其中本文涉及的Docker的私库是用Nexus3搭建的。 使用Docker部署Spring Boot项目有三种方式 &#xff08;1&#xff09;使用 spring-boot-maven-plugin内置的build-image. &#xff08;2&#xff09;使用 Google 的 j…

手把手系列!使用 Zilliz Cloud 和 AWS Bedrock 搭建 RAG 应用

检索增强生成&#xff08;Retrieval Augemented Generation, RAG&#xff09;是一种 AI 框架&#xff0c;它通过结合信息检索和自然语言处理&#xff08;NLP&#xff09;能力从而增强文本生成。具体而言&#xff0c;RAG 系统中的语言模型通过一种检索机制查询和搜索知识库或外部…

rocketmq控制台部署

网络找rocketmq控制台有源码包&#xff0c;还需要编译太麻烦&#xff0c;找到了 rocketmq-dashboard-1.0.1-SNAPSHOT.jar已经编译后的jar&#xff0c;只有简单修改服务器端口和监控rocketmq集群地址和端口就可以。需要jar资源的可以咨询本人。 步骤1&#xff1a;根据需要配置ro…

LinkedList链表

LinkedList 的全面说明 LinkList底层实现了双向链表和双端队列特点可以添加任意元素&#xff08;元素可以重复&#xff09;&#xff0c;包括null线程不安全&#xff0c;没有实现同步 LinkedList 的底层操作机制 LinkedList底层维护了一个双向链表LinkList中维护了两个属性fi…

VBA_NZ系列工具NZ06:VBA创建PDF文件说明

我的教程一共九套及VBA汉英手册一部&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到数据库&#xff0c;到字典&#xff0c;到高级的网抓及类的应用。大家在学习的过程中可能会存在困惑&#xff0c;这么多知识点该如何组织…