【C++模拟实现】手撕AVL树

news2025/1/19 23:26:08

【C++模拟实现】手撕AVL树

目录

  • 【C++模拟实现】手撕AVL树
      • AVL树的介绍(百度百科)
      • AVL树insert函数的实现代码
      • 验证是否为AVL树
      • AVL树模拟实现的要点
        • 易忘点
        • AVL树的旋转思路

作者:爱写代码的刚子

时间:2023.9.10

前言:本篇博客将会介绍AVL树的模拟实现(模拟AVL树的插入),以及如何去验证是否为AVL树

AVL树的介绍(百度百科)

AVL树本质上还是一棵二叉搜索树,它的特点是:

  1. 本身首先是一棵二叉搜索树。

  2. 带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。

也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。

AVL树insert函数的实现代码

template<class K,class V>
class AVLTreeNode
{
public:
    AVLTreeNode(const pair<K,V>& kv)
    :_left(nullptr)
    ,_right(nullptr)
    ,_parent(nullptr)
    ,_kv(kv)
    ,_bf(0)
    {}
    
    AVLTreeNode* _left;
    AVLTreeNode* _right;
    AVLTreeNode* _parent;//需要设立父节点指针
    pair<K,V> _kv;

    int _bf;
};

template<class K,class V>
class AVLTree
{
    typedef AVLTreeNode<K,V> Node;
public:
    AVLTree()
    :_root(nullptr)
    {}

    
    bool insert(const pair<K,V>& kv)
    {
        if(_root==nullptr)
        {
            _root=new Node(kv);
            return true;
        }
        else
        {
            Node* cur=_root;
            Node* parent=nullptr;//设计parent指针是必要的
            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->_left=cur;
            }
            else{
                parent->_right=cur;
            }
            cur->_parent=parent;
            
            while(parent)
            {
              //及时调整父节点的平衡因子
                if(parent->_left==cur)
                {
                    --parent->_bf;
                }
                else{
                    ++parent->_bf;
                }

                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&&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 _RotateR(Node* parent)//右单旋的实现
    {
        Node*cur=parent->_left;
        Node*curRight=cur->_right;
        Node*ppnode=parent->_parent;
        
        
        cur->_right=parent;
        parent->_left=curRight;

        if(curRight)//curRight可能是nullptr
        {
            curRight->_parent=parent;
        }
        parent->_parent=cur;
        
        //处理ppnode
        if(parent==_root)//parent为头节点时需要单独处理
        {
            _root=cur;
            cur->_parent=nullptr;

        }
        else
        {
            if(ppnode->_left==parent)
            {
                ppnode->_left=cur;
            }
            else{
                ppnode->_right=cur;
            }
            cur->_parent=ppnode;
        }
        parent->_bf=cur->_bf=0;

    }
    void _RotateL(Node* parent)
    {
        Node* cur=parent->_right;
        Node* curLeft=cur->_left;
        Node* ppnode=parent->_parent;

        cur->_left=parent;
        parent->_right=curLeft;

        if(curLeft)
        {
            curLeft->_parent=cur;
        }
        parent->_parent=cur;

        if(parent==_root)
        {
            _root=cur;
            cur->_parent=nullptr;
        }
        else
        {
            if(ppnode->_left==parent)
            {
                ppnode->_left=cur;
            }
            else{
                ppnode->_right=cur;
            }
            cur->_parent=ppnode;
        }
        parent->_bf=cur->_bf=0;
    }
    void _RotateLR(Node* parent)
    {
        Node* cur=parent->_left;
        Node* curRight=cur->_right;
        int bf=curRight->_bf;

        _RotateL(cur);
        _RotateR(parent);

        //最好再处理一下平衡因子,减少耦合度
        if(bf==0)//单链情况下
        {
            parent->_bf=0;
            cur->_bf=0;
            curRight->_bf=0;
        }
        else if(bf==-1)
        {
            parent->_bf=1;
            curRight->_bf=0;
            cur->_bf=0;
        }
        else if(bf==1)
        {
            parent->_bf=-1;
            curRight->_bf=0;
            cur->_bf=0;
        }
        else{
            assert(false);
        }
    }
    void _RotateRL(Node* parent)
    {
        Node* cur=parent->_right;
        Node* curLeft=cur->_left;
        int bf=curLeft->_bf;

        _RotateR(cur);
        _RotateL(parent);

        if(bf==0)
        {
            parent->_bf=0;
            curLeft->_bf=0;
            cur->_bf=0;
        }
        else if(bf==1)
        {
            parent->_bf=0;
            curLeft->_bf=-1;
            cur->_bf=0;
        }
        else if(bf==-1)
        {
            parent->_bf=0;
            curLeft->_bf=1;
            cur->_bf=0;
        }
        else{
            assert(false);
        }
        
    }
  private:
    


    Node* _root;
};

验证是否为AVL树

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;
    }
    

    bool _Isbalance()
    {
        return _Isbalance(_root);
    }
    bool _Isbalance(Node* root)
    {
        if(root==nullptr)
        {
            return true;
        }

        int right=_Height(root->_right);
        int left=_Height(root->_left);

        if(root->_bf!=right-left)
        {
            cout<<"平衡因子异常"<<root->_bf<<" "<<right<<" "<<left<<endl;
            return false;
        }

        return abs(right-left)<2&&_Isbalance(root->_left)&&_Isbalance(root->_right);


    }
  • 根据AVL树的特性引入两个成员函数_Height函数用于计算二叉树的高度

  • 以下为验证结果:
    在这里插入图片描述

AVL树模拟实现的要点

易忘点

一定要时刻注意_parent指针的修改!尤其旋转函数中需要判断旋转后的二叉树的根节点是否还有父亲节点,如果有,需要在旋转前先保存,之后再链接上。

AVL树的旋转思路

  1. 新增在左,parent平衡因子减减
  2. 新增在右,parent平衡因子加加
  3. 更新后parent平衡因子 == 0,说明parent所在的子树的高度不变,不会再影响祖先,不用再继续沿着到eot的路径往上更新
  4. 更新后parent平衡因子 == 1 0r -1,说明parent所在的子树的高度变化,会再影响祖先,需要继续沿着到root的路径往上更新更新后
  5. 更新后parent平衡因子 == 2 or -2,说明parent所在的子树的高度变化且不平衡,对parent所在子树进行旋转,让他平衡
  6. 更到根节点,插入结束

由于AVL树画图较为麻烦,作者先不画了,可以看看其他大佬的博客,一些需要注意的地方已经写在代码注释里了,AVL树的删除之后有机会可以模拟实现一下。
AVL树的调试较为麻烦,模拟实现可以提高自己的调试能力。

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

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

相关文章

《自然语言处理(NLP)的最新进展:Transformers与GPT-4的浅析》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Stable Diffusion 免费升级 SDXL 1.0,哪些新特性值得关注?体验如何?5 分钟带你体验!

一、引言 7 月 26 日&#xff0c;Stability AI 发布了 SDXL 1.0&#xff0c;号称目前为止&#xff0c;最厉害的开放式图像生成大模型。 它到底有没有网上说的那么炸裂&#xff1f;真的已经实现了像 midjourney 一样 靠嘴出图 的功能吗&#xff1f;相对于之前的版本&#xff0c;…

【redis进阶】Redis String数据类型为什么不好用

保存1 亿张10字节图片标识&#xff0c;String结构用了 6.4GB 的内存&#xff0c;为什么&#xff1f;如何优化&#xff1f; 数据量多时&#xff0c;比较占空间 存储数量量较多的时候&#xff0c;可以使用list数据结构来替代String&#xff0c;以二级编码的方式将数据存入redis…

[杂谈]-电动汽车有哪些不同类型

电动汽车有哪些不同类型&#xff1f; 文章目录 电动汽车有哪些不同类型&#xff1f;1、概述2、纯电动汽车&#xff08;BEV&#xff09;3、燃料电池电动汽车&#xff08;FCEV&#xff09;4、插电式混合动力汽车 (PHEV&#xff09;5、混合动力电动汽车 (HEV)6、轻度混合动力HEV7、…

树形DP()

没有上司的舞会 Ural 大学有 N 名职员&#xff0c;编号为 1∼N。 他们的关系就像一棵以校长为根的树&#xff0c;父节点就是子节点的直接上司。 每个职员有一个快乐指数&#xff0c;用整数 Hi 给出&#xff0c;其中 1≤i≤N。 现在要召开一场周年庆宴会&#xff0c;不过&am…

非结构化数据之XPath学习

1、XPath语法 XPath 是一门在 XML 文档中查找信息的语言。 XPath 可用来在 XML 文档中对元素和属性进行遍历。 <?xml version"1.0" encoding"ISO-8859-1"?> <bookstore> <book><title lang"eng">Harry Potter</t…

实战教程:如何将自己的Python包发布到PyPI上

1. PyPi的用途 Python中我们经常会用到第三方的包&#xff0c;默认情况下&#xff0c;用到的第三方工具包基本都是从Pypi.org里面下载。 我们举个栗子: 如果你希望用Python实现一个金融量化分析工具&#xff0c;目前比较好用的金融数据来源是 Yahoo 和 Google。你可能需要读取…

13-RocketMQ主从同步(HA实现)源码原理

slave每次接收到master发过来的一批commitlog数据时&#xff0c;会看master传过来的这段commitlog的起始端&#xff0c;对应的全局物理偏移量&#xff0c;和slave本地存储的批commitlog数据的最大物理偏移量&#xff0c;是否相等 如果相等&#xff0c;也说明master端没有给sla…

TCP详解之三次握手和四次挥手

TCP详解之三次握手和四次挥手 1. TCP基本认识 1.1 什么是 TCP TCP是面向连接的、可靠的、基于字节流的传输层通信协议。 1.2 TCP协议段格式 我们先来看看TCP首部协议的格式 我们先来介绍一些与本文关联比较大的字段&#xff0c;其他字段不做详细阐述。 序列号&#xff1a…

【javaweb课设源码】图书管理系统SSM Mysql 期末课设

文章目录 简介 简介 本系统使用Javaweb技术制作&#xff0c;数据库为mysql 附带论文报告文档 printf("需要源码&#xff0c;可以baidu学长敲代码")&#xff1b;

input子系统框架、外设驱动开发

一、input子系统基本框架 Linux内核为了两个目的&#xff1a; 简化纯输入类外设&#xff08;如&#xff1a;键盘、鼠标、游戏杆、轨迹球、触摸屏。。。等等&#xff09;的驱动开发统一输入类外设产生的数据格式&#xff08;struct input_event&#xff09;&#xff0c;更加方…

【LeetCode题目详解】第九章 动态规划part10 121. 买卖股票的最佳时机 122.买卖股票的最佳时机II (day49补)

本文章代码以c为例&#xff01; 股票问题是一个动态规划的系列问题 一、力扣第121题&#xff1a;买卖股票的最佳时机 题目&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#x…

Android学习之路(14) AMS与PMS详解

Android 系统启动流程与 Zygote、SystemServer 在讲解 Zygote 之前&#xff0c;考虑到不同的系统版本源码都不相同&#xff0c;以下分析的源码基于 Android 8.0.0。 init 进程 当系统启动时&#xff0c;init 进程是继 Linux 内核启动后第二个启动的进程&#xff0c;它是在用…

AOP代理中Cglib使用场景

有接口时会使用JDK动态代理 没有接口实现类的情况下使用Cglib进行动态代理

layui手机端使用laydate时间选择器被输入法遮挡的解决方案

在HTML中&#xff0c;你可以使用input元素的readonly属性来禁止用户输入&#xff0c;但是这将完全禁用输入&#xff0c;而不仅仅是禁止弹出输入法。如果你想允许用户在特定条件下输入&#xff0c;你可以使用JavaScript来动态地切换readonly属性。 readonly属性 增加readonly属…

【iOS】MVC

文章目录 前言一、MVC各层职责1.1、controller层1.2、model层1.3、view层 二、总结三、优缺点3.1、优点3.2、缺点 四、代码示例 前言 MVC模式的目的是实现一种动态的程序设计&#xff0c;使后续对程序的修改和扩展简化&#xff0c;并且使程序某一部分的重复利用成为可能。除此…

【交叉熵损失torch.nn.CrossEntropyLoss详解-附代码实现】

CrossEntropyLoss 什么是交叉熵softmax损失计算验证CrossEntropyLoss 输入输出介绍验证代码 什么是交叉熵 交叉熵有很多文章介绍&#xff0c;此处不赘述。只需要知道它是可以衡量真实值和预测值之间的差距的&#xff0c;因而用交叉熵来计算损失的时候&#xff0c;损失是越小越…

【JavaScript手撕代码】new

目录 手写 手写 /* * param {Function} fn 构造函数 * return {*} **/ function myNew(fn, ...args){if(typeof fn ! function){return new TypeError(fn must be a function)}// 先创建一个对象let obj Object.create(fn.prototype)// 通过apply让this指向obj, 并调用执行构…

SHIB去零计划:创新金融未来,打造稳定数字资产新范式

SHIB去零计划&#xff0c;由星火有限公司发起&#xff0c;以区块链去中心化手段解决信任危机&#xff0c;对抗垄断与不公平问题&#xff0c;破解经济制裁&#xff0c;实现稳定数字资产的快速有效、平等互利交易。星火有限公司&#xff0c;一家跨国运营集团&#xff0c;主营业务…

UIStackView入门使用两个问题

项目中横向一排元素&#xff0c;竖向一排元素&#xff0c;可以使用UIStackView。UIStackView的原理不做介绍&#xff0c;这里主要讲两个初次使用容易出现的两个问题。 首先创建一个stackview -(UIStackView*)titleStackView{if(_titleStackView nil){_titleStackView [UISta…