详解—[C++ 数据结构]—AVL树

news2025/1/21 3:03:45

目录

一.AVL树的概念

二、AVL树节点的定义

三、AVL树的插入

3.1插入方法

四、AVL树的旋转

1. 新节点插入较高左子树的左侧---左左:右单旋

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

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

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

5.AVL树的插入代码

五、AVL树的验证

六、AVL树的性能


一.AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

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

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

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

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 ,搜索时
间复杂度O( )。

 

二、AVL树节点的定义

AVL树每个节点都会记录他的左右孩子和父节点

并且每个节点记录一下自己的平衡因子

用模板T存储他的数据

template<class T>
struct AVLTreeNode
{
    AVLTreeNode(const T& data)
        : _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
        , _data(data), _bf(0)
    {}
    AVLTreeNode<T>* _pLeft; // 该节点的左孩子
    AVLTreeNode<T>* _pRight; // 该节点的右孩子
    AVLTreeNode<T>* _pParent; // 该节点的双亲
    T _data;
    int _bf; // 该节点的平衡因子
};

三、AVL树的插入

对于AVL树的插入,因为它是要结合AVL树的旋转的,所以在本文中,AVL树的插入和AVL树的旋转合起来才是完整的插入过程,所以这里我们主要讲一下插入的大体的一个过程,具体插入的细节及代码实现后面实现

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子


3.1插入方法

1. 先按照二叉搜索树的规则将节点插入到AVL树中

2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了
AVL树的平衡性

新节点插入后,他的父节点的平衡因子一定需要调整,在插入之前,父节点
的平衡因子分为三种情况:-1,0, 1,分以下两种情况:

1. 如果新节点插入到父节点的左侧,只需给父节点的平衡因子-1即可
2. 如果新节点插入到父节点的右侧,只需给父节点的平衡因子+1即可

此时:父节点的平衡因子可能有三种情况:0,正负1, 正负2
1. 如果父节点的平衡因子为0,说明插入之前父节点的平衡因子为正负1,插入后被调整成0,此
时满足AVL树的性质,插入成功


2. 如果父节点的平衡因子为正负1,说明插入前父节点的平衡因子一定为0,插入后被更新成正负1,此时以父节点为根的树的高度增加,需要继续向上更新


3. 如果父节点的平衡因子为正负2,则父节点的平衡因子违反平衡树的性质,需要对其进行旋转
处理

四、AVL树的旋转

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


1. 新节点插入较高左子树的左侧---左左:右单旋

最终,根据我们图上所画的这种右单选的情况,我们可以按照上图写出右旋转的代码:

 void RotateR(Node* parent)
    {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        // 更新节点之间的连接关系
        parent->_left = subLR;
        if (subLR)
        {
            subLR->_parent = parent;
        }
        subL->_right = parent;
        Node* pparent = parent->_parent;
        parent->_parent = subL;
        if (!pparent)
        {
            _root = subL;
            subL->_parent = nullptr;
        }
        else
        {
            if (pparent->_left == parent)
            {
                pparent->_left = subL;
            }
            else if (pparent->_right == parent)
            {
                pparent->_right = subL;
            }
            subL->_parent = pparent;
        }
        // 更新平衡因子
        parent->_bf = subL->_bf = 0;
    }

根据上图判断:

我们定义了父亲的左subL父亲左的右subLR

首先根据上图第一步,把父亲左的右节点给父亲的左,如果subLR不为空,更新一下subLR的父节点

然后把parent链接在subL的右边,(记录一下父亲的父亲(pparent),一会需要subL更新父节点)  更改父亲的父节点为subL

下面就开始更新subL的父节点了

第一步判断旋转前的person是不是根节点,如果是根节点的父亲(pparent)为空,然后把根节点更新为subL,把subL的父节点置为空

如果不是根节点,判断以前pparent的左边还是右边是父亲(parent),让pparent指向父亲改为指向subL,再把subL的父节点更新为pparent

最后,更新平衡因子,把parent和subL的平衡因子置为0

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

最终,根据我们图上所画的这种右单选的情况,我们可以按照上图写出左旋转的代码:

 // 左单旋
    void RotateL(Node* parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        // 更新节点之间的连接关系
        parent->_right = subRL;
        if (subRL)// subRL不为空才需要更新它的父亲
        {
            subRL->_parent = parent;
        }
        subR->_left = parent;
        Node* pparent = parent->_parent;
        parent->_parent = subR;
        if (!pparent)// parent为根时的处理
        {
            _root = subR;
            subR->_parent = nullptr;
        }
        else
        {
            if (pparent->_left == parent)
            {
                pparent->_left = subR;
            }
            else
            {
                pparent->_right = subR;
            }
            subR->_parent = pparent;
        }
        // 更新平衡因子
        parent->_bf = subR->_bf = 0;
    }

对于左单旋解释,左单旋跟右单旋 两者非常类似,所以这里不再花费篇幅去讲解.

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

将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。

// 左右双旋
    void RotateLR(Node* parent)
    {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        int flag = subLR->_bf;// 记录subLR的平衡因子,最后要依据它来更新其他节点的平衡因子
        // 依次旋转
        RotateL(subL);
        RotateR(parent);
        // 根据subLR平衡因子的值更新不同插入情况下的平衡因子
        if (flag == 1)// 说明是在subLR的右子树插入的,那么subLR的左子树变为subL的右子树,subL平衡因子变为-1,subLR和parent的为0
        {
            subL->_bf == -1;
        }
        else if (flag == -1)// 说明是在subLR的左子树插入的,subLR的右子树最后会被分给parent作为左子树,parent的平衡因子变为-1,subL和subLR的平衡因子变为0
        {
            parent->_bf == 1;
        }
    }

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

 void RotateRL(Node* parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        int flag = subRL->_bf;
        // 依次旋转
        RotateR(subR);
        RotateL(parent);
        // 更新平衡因子
        if (flag == 1)
        {
            parent->_bf == -1;
        }
        else if (flag == -1)
        {
            subR->_bf == 1;
        }
    }

5.AVL树的插入代码

bool Insert(const pair<k, v>& kv)
    {
        // 空树的话,就让插入的那个节点作为根
        if (!_root)
        {
            _root = new Node(kv);
            return true;
        }
        // 不是空树,就按照搜索树的性质找到插入的位置和它的父亲
        Node* cur = _root;
        Node* parent = nullptr;
        while (cur)
        {
            parent = cur;
            if (cur->_kv.first == kv.first)
            {
                return false;
            }
            else if (cur->_kv.first > kv.first)
            {
                cur = cur->_left;
            }
            else
            {
                cur = cur->_right;
            }
        }
        // 创建要插入的节点
        Node* newNode = new Node(kv);
        // 更新关系,插入节点
        newNode->_parent = parent;
        if (parent->_kv.first < newNode->_kv.first)
        {
            parent->_right = newNode;
        }
        else
        {
            parent->_left = newNode;
        }

        cur = newNode;
        parent = cur->_parent;
        while (parent)
        {
            // 向上更新平衡因子
            if (cur == parent->_left)
            {
                --(parent->_bf);
            }
            else
            {
                ++(parent->_bf);
            }
            // 检查是否需要调整
            // 0的话就平衡了
            // -1或1的话还要向上更新
            // -2或2的话需要旋转处理
            if (parent->_bf == 0)// 平衡因子为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)
                {
                    if (cur->_bf == 1)// 右子树的右子树也高 -->  左单旋
                    {
                        RotateL(parent);
                    }
                    else if (cur->_bf == -1)// 右子树的左子树也高  -->  右左双旋
                    {
                        RotateRL(parent);
                    }
                }
                else if (parent->_bf == -2)// 左子树高
                {
                    if (cur->_bf == -1)// 左子树的左子树也高  -->  右单旋
                    {
                        RotateR(parent);
                    }
                    else if (cur->_bf == 1)// 左子树的右子树也高  -->  左右双旋
                    {
                        RotateLR(parent);
                    }
                }
                break;
            }
        }
        return true;
    }

五、AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
1. 验证其为二叉搜索树
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
2. 验证其为平衡树
       每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
       节点的平衡因子是否计算正确

  int Height(Node* root)
    {
    	if (root == nullptr)
    		return 0;
    
    	int RightHeight = Height(root->_left);
    	int LeftHeight = Height(root->_right);
    
    	return RightHeight > LeftHeight ? RightHeight + 1 : LeftHeight + 1;
    }
    
    bool _IsBalance(Node* root)
    {
    	if (root == nullptr)
    		return true;//空的话应该返回true,因为不影响平衡
    
    	int LeftHeight = Height(root->_left);//迭代计算高度
    	int RightHeight = Height(root->_right);
    
    	if (RightHeight - LeftHeight != root->_bf)//仅仅判断高度是不够的,有可能平衡因子还是错了,所以要对每个平衡因子做检查
    	{
    		cout << "平衡因子现在是:" << root->_bf << endl;
    		cout << "平衡因子应该是:" << (RightHeight - LeftHeight) << endl;
    		return false;//平衡因子错了直接返回
    	}
    
    	return RightHeight - LeftHeight < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);
    }

六、AVL树的性能


       AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++-多态常见试题的总结

关于C多态的介绍&#xff1a;C-多态-CSDN博客 1. A.只有类的成员方法才可以被virtual修饰&#xff0c;其他的函数并不可以 B.正确 C.virtual关键字只在声明时加上&#xff0c;在类外实现时不能加 D.static和virtual是不能同时使用的 2. A.多态分为编译时多态和运行时多态&…

Linux详解——安装JDK

目录 一、下载jdk 二、tar包安装 三、rpm包安装 一、下载jdk 1.下载jdk https://www.oracle.com/technetwork/java/javase/downloads/index.html 2.通过CRT|WinSCP工具将jdk上传到linux系统中 二、tar包安装 # 1.将JDK解压缩到指定目录 tar -zxvf jdk-8u171-linux…

ubuntu系统进入休眠后cuda初始化报错

layout: post # 使用的布局&#xff08;不需要改&#xff09; title: torch.cuda.is_available()报错 # 标题 subtitle: ubuntu系统进入休眠后cuda初始化报错 #副标题 date: 2023-11-29 # 时间 author: BY ThreeStones1029 # 作者 header-img: img/about_bg.jpg #这篇文章标题背…

大杀四方,华为组建智能车大联盟 | 百能云芯

最近&#xff0c;华为和一系列汽车公司合资的新公司迎来新的进展。除了与长安汽车的合作外&#xff0c;据传华为已经邀请奇瑞、赛力斯、北汽以及江淮汽车入股新公司&#xff0c;这将使华为成为中国智能汽车平台的重要主导者。 根据澎湃新闻的报道&#xff0c;知情人透露&#x…

装饰模式学习

背景 首先明确装饰模式是结构型设计模式的一种&#xff0c;但是结构型设计模式有什么特点呢。装饰模式的业务是给人穿衣服。 步骤 历史发展 版本1&#xff1a;只有一个Person类&#xff0c;这个类由三部分构成&#xff0c;本身的有参构造函数&#xff0c;给当前对象传不同衣…

外包干了5个月,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

信创之国产浪潮电脑+统信UOS操作系统体验8:安装Docker并进行测试验证scratch镜像

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 一、前言 今日在进行Docker容器相关知识的学习&#xff0c;不过学习环境都不是基于统信UOS操作系统的&#xff0c;为了实验&#xff0c;老猿觉得手头国产浪潮电脑统信UOS操作系统就是原生的linux操作系统&a…

LiveData源码分析,粘性事件,数据倒灌

最近面试天天被虐&#xff0c;有个问题问的很频繁&#xff0c;就是 LiveData 的数据倒灌问题怎么解决。 我不知道有多少人连数据倒灌是什么都没听过的&#xff0c;更不要说什么解决方案啦。 我按照我的理解描述一下数据倒灌&#xff1a;就是设置了 LiveData 的数据之后&#…

“rhdf5filters.so’ not found when install ‘glmGamPoi‘ package

在R中安装glmGamPoi包的时候&#xff0c;出现了如下报错&#xff1a; install.packages(glmGamPoi) 尝试方案一&#xff1a; sudo apt install pkg-config libhdf5-dev安装lighdf5-dev&#xff0c;并将安装路径链接至usr/lib/文件。 locate rhdf5filters.so sudo ln -s /hom…

武汉建筑类初级职称助理工程师电子版证书申报

武汉建筑类初级职称助理工程师电子版证书申报 目前大家较为关注的是湖北省的助理工程师/初级职称评审出来之后是否可以网上查询。市面上还有一些地级市的区人社职改办出纸质版证书&#xff0c;职称证书、红头文件、评审表齐全&#xff0c;但是查询方式还是老一套的查询方式&am…

三勾商城新功能发布-多包裹订单

在不同场景下&#xff0c;商家可能需一笔订单需要分成多个包裹、分批发货&#xff0c;来看看怎么操作吧。 前端截图 后台截图 三勾小程序商城基于springbootelement-plusuniapp打造的面向开发的小程序商城&#xff0c;方便二次开发或直接使用&#xff0c;可发布到多端&#xf…

职场人最好的姿势是仰卧起坐

曾经看过一个回答说“职场人最好的姿势是仰卧起坐”。 卷累的就躺&#xff0c;休息好了再继续卷&#xff0c;卷是常态&#xff0c;“仰卧起坐”也好&#xff0c;“卷的姿势”也好&#xff0c;都是在反复“卷起”的过程中寻找一些舒适和平衡&#xff0c;“卷”得更持久罢了.....…

Linux 进程(一)

1 操作系统 概念&#xff1a;任何计算机系统都包含一个基本的程序集合&#xff0c;称为操作系统(OS)。笼统的理解&#xff0c;操作系统包括 内核&#xff08;进程管理&#xff0c;内存管理&#xff0c;文件管理&#xff0c;驱动管理&#xff09; 其他程序&#xff08;例…

LeetCode(41)单词规律【哈希表】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 单词规律 1.题目 给定一种规律 pattern 和一个字符串 s &#xff0c;判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配&#xff0c;例如&#xff0c; pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连…

Elasticsearch 快照如何工作?

作者&#xff1a;Lutf ur Rehman Elastic 提供许多由讲师指导的面对面和虚拟现场培训以及点播培训。 我们的旗舰课程是 Elasticsearch 工程师、Kibana 数据分析和 Elastic 可观测性工程师。 所有这些课程都会获得认证。有关这些课程的详细介绍&#xff0c;请参考我之前的文章 “…

20.Oracle11g中的触发器

oracle11g中的触发器 一、触发器的概述1、什么是触发器2、触发器的类型3、触发器的组成4、触发器的作用 二、触发器的创建语法1、创建语法2、数据库启动触发器3、 用户登录触发器&#xff1a; 三、对触发器的基本操作点击此处跳转下一节&#xff1a;21.Oracle的程序包(Package)…

QNX下多窗口叠加融合方案

目的&#xff1a;QNX下EGL多窗口叠加融合方案 环境&#xff1a; 系统&#xff1a;QNX 环境&#xff1a;8155/8295问题&#xff1a; EGL有时候在同一个进程中因为引入不同的功能&#xff0c;在不同的线程中进行窗口的绘制和融合&#xff0c;QNX下的融合方案&#xff0c;实测使…

夸克大模型助力学术科研提效 四大优势提升知识正确性

当严谨的学术科研与创新的大模型技术结合在一起&#xff0c;会擦出什么样的火花&#xff1f;日前&#xff0c;夸克大模型甫一推出便以优秀的性能成为国产大模型中的“学霸”。在中国科学技术协会近期主办的“大模型应用场景研讨会”上&#xff0c;夸克大模型在快速阅读、创作润…

求臻医学胃癌关爱日:美味的高“盐”值杀手

胃癌的发病率具有广泛的地域差异&#xff0c;在东南亚国家尤为高发。韩国是胃癌发病率排名第一的国家&#xff0c;其次为日本&#xff0c;中国紧随其后&#xff0c;由于中国人口基数大&#xff0c;其绝对患胃癌人数为全球第一&#xff0c;每年有100多万新诊断患者&#xff0c;其…

nvm for windows使用与node/npm/yarn的配置

1 下载 nvm for windows download – github 下拉到Assets, 下载.exe文件 2 安装 安装到如下文件夹中 目录可以自己选, 可以换别的名字, 自己记住即可 新手建议全部看完再进行个人配置, 或者使用与博主一致的路径 D:\DevelopEnvironment\nvm3 配置nvm使用的镜像 node_mir…