数据结构-考研难点代码突破(C++实现树型查找 - 平衡二叉树(AVL树)的基本操作(增删))

news2025/2/24 3:18:09

文章目录

  • 1. 平衡二叉树的概念
    • AVL树的插入
    • AVL树查找效率
    • AVL树的删除(了解)
  • 2. C++代码
  • 3. 考研数据结构代码仓库

1. 平衡二叉树的概念

二叉搜索树虽然可以提高搜索效率,但如果数据接近有序的话搜索二叉树的效率退化为链表了。为了解决这个问题,提出了AVL树。

向平衡二叉树中插入新节点,保证每个节点的高度差的绝对值小于等于1。降低树的高度,提高搜索效率。这种树称为AVL树

为了确定插入或者删除节点后,这棵树的高度差的绝对值,这里给树的每一个节点引入平衡因子。
每个节点的平衡因子=这个节点的右子树高度-这个节点的左子树高度。

所以根据AVL树的定义:当平衡因子变为2或-2时,需要调整AVL树

AVL树的插入

情况一:右单旋(新节点插入较高左子树的左侧)
在这里插入图片描述
插入新节点后,b节点平衡因子为-1,a节点平衡因子为-2。

调整步骤:将a节点连接到b右节点,将原来b右子树连接到a左子树上,这样a,b节点的平衡因子又变回0

情况二:左单旋(新节点插入较高右子树的右侧)
在这里插入图片描述
插入新节点后,b节点平衡因子为1,a节点平衡因子为2

旋转思路:将bL连接到a的右树上,a再连接到b的左树上。调整平衡因子a,b平衡因子都变为0,调整a,b节点父指针,调整树的根节点,处理子树的情况,细节和右单旋类似。这样a,b节点的平衡因子又变回0

情况三:左右双旋(新节点插入较高左子树的右侧)
在这里插入图片描述
观察上图,相当于把c节点拆开c做根节点,c的左子树给b右树,c的右子树给a的左树。c的左子树连接b,c的右子树连接a。从而达到降低树的高度的目的。

根据上面的分析可知c在旋转后平衡因子一定为0.
如果新节点插入的位置在c的左子树上,经过旋转会到b的右子树上,b的平衡因子为0,a的平衡因子为1.
如果新节点插入的位置在c的右子树上,经过旋转会到a的左子树上,b的平衡因子为-1,a的平衡因子为0(如上图).

特殊情况:当b节点没有子树时,即这颗树只有cba这三个节点,此时a,b,c这三个节点的平衡因子都为0

情况四:右左双旋(新节点插入较高右子树的左侧)
在这里插入图片描述
由上图可以看到a节点的平衡因子为2,b节点的平衡因子为-1时要进行右左双旋。
特点为:b节点进行右旋,a节点进行左旋。

AVL树查找效率

如果树高为h,最坏情况下,查找需要对比h次

平衡二叉树上任一结点的左子树和右子树的高度之差不超过1。

假设以n(h)表示深度为h的平衡树中含有的最少结点数。

n(0)=0此时是空树,n(1)=1此时树只有根节点,n(2)=2……

高度为h的平衡二叉树,最少节点个数为n(h)=n(h-1)+n(h-2)+1

所以9个节点的平衡二叉树i有四层,最坏需要对比4次就可以查找到对应元素

所以平衡二叉树的事件复杂度为树高O(logN)

AVL树的删除(了解)

AVL树的删除操作与插入类似;

  1. 首先先按照二叉搜索树的删除规则进行删除
  2. 之后向上更新平衡因子,如果遇到不平衡的情况和AVL树插入操作类似进行树结构的调整,调整规则完全相同

AVL树的删除,关键就是平衡因子的调整。调整完毕后,根据平衡因子的值,选择旋转方式,并向上进行传递。

我这里使用:每次删除后,从删除根节点开始调整平衡因子,如果失去了AVL树形状,进行反转,之后依次向上递归调整。类似后续遍历的方式。

从整棵树的最下方从头开始调整。

这个方法有个弊端,就是每次删除必须向上判断平衡因子是否正确,效率比较低

2. C++代码

BalanceTree类

#include <iostream>
#include <vector>
#include <algorithm>

struct Node
{
    int _bf;
    Node *_left;
    Node *_right;
    Node *_parent;

    int _val;
    Node(int val) : _bf(0), _left(nullptr), _right(nullptr), _parent(nullptr), _val(val) {}
};

class BalanceTree
{
    Node *root;

public:
    ~BalanceTree() {}

    void InorderDisplay()
    {
        _DisPlay(root);
        std::cout << "\n";
    }
    BalanceTree(const std::vector<int> buff)
    {
        for (auto &num : buff)
        {
            insert(num);
        }
    }
    bool insert(int val)
    {
        // 不允许值重复
        if (root == nullptr)
        {
            root = new Node(val);
            return true;
        }
        else
        {
            Node *prev = nullptr;
            Node *cur = root;
            while (cur != nullptr)
            {
                prev = cur;
                if (cur->_val < val)
                {
                    cur = cur->_right;
                }
                else if (cur->_val > val)
                {
                    cur = cur->_left;
                }
                else
                {
                    // 数据冗余
                    return false;
                }
            }
            // cur==nullptr
            cur = new Node(val);

            if (prev->_val > val)
                prev->_left = cur;
            else
                prev->_right = cur;

            cur->_parent = prev;

            // 更新平衡因子
            while (prev != nullptr)
            {
                // 判断插入位置
                if (prev->_left == cur)
                    prev->_bf--;
                else if (prev->_right == cur)
                    prev->_bf++;

                if (prev->_bf == 0)
                {
                    break; // 插入后仍然是AVL树
                }
                else if (prev->_bf == -1 || prev->_bf == 1)
                {
                    cur = prev;
                    prev = prev->_parent; // 这次插入影响高度,需要向上调整
                }
                else if (prev->_bf == -2 || prev->_bf == 2)
                {
                    // 不是AVL树需要调整高度
                    if (prev->_bf == -2)
                    {
                        if (cur->_bf == -1)
                        {
                            // 右单旋
                            TurnRight(prev);
                        }
                        else if (cur->_bf == 1)
                        {
                            // 左右双旋
                            TurnLeftRight(prev);
                        }
                    }
                    else if (prev->_bf == 2)
                    {
                        if (cur->_bf == -1)
                        {
                            // 右左双旋
                            TurnRightLeft(prev);
                        }
                        else if (cur->_bf == 1)
                        {
                            // 左单旋
                            TurnLeft(prev);
                        }
                    }
                }
            }
            return true;
        }
    }

    // 判断一棵树是否是AVL树
    bool isAVLTree()
    {
        return _isAVLTree(root);
    }

    void erase(int val)
    {
        _erase(val, root);
        _adjust(root);
    }

private:
    void _adjust(Node *node)
    {
        // 后续遍历调整搜索二叉树为AVL树
        if (node == nullptr)
        {
            return;
        }
        _adjust(node->_left);
        _adjust(node->_right);
        int leftHeight = _height(node->_left);
        int rightHeight = _height(node->_right);
        node->_bf = rightHeight - leftHeight;

        if (node->_bf == 2 || node->_bf == -2)
        {
            Node *left = node->_left;
            Node *right = node->_right;

            if (node->_bf == -2)
            {
                if ((left != nullptr && left->_bf == -1) || (right != nullptr && right->_bf == -1))
                {
                    // 右旋
                    TurnRight(node);
                }
                else if ((left != nullptr && left->_bf == 1) || (right != nullptr && right->_bf == 1))
                {
                    // 左右双旋
                    TurnLeftRight(node);
                }
            }
            else if (node->_bf == 2)
            {
                if ((left != nullptr && left->_bf == -1) || (right != nullptr && right->_bf == -1))
                {
                    // 右左旋
                    TurnRightLeft(node);
                }
                else if ((left != nullptr && left->_bf == 1) || (right != nullptr && right->_bf == 1))
                {
                    // 左旋
                    TurnLeft(node);
                }
            }
        }
    }
    // 搜索二叉树删除方式
    void _erase(int val, Node *&node)
    {
        // 删除节点值为val的节点,并且返回删除后这个节点的指针
        if (node == nullptr)
        {
            return;
        }

        else
        {
            if (node->_val > val)
            {
                _erase(val, node->_left);
            }
            else if (node->_val < val)
            {
                _erase(val, node->_right);
            }
            else
            {
                if (node->_left == nullptr)
                {
                    Node *del = node;
                    node = node->_right;
                    if (node != nullptr)
                        node->_parent = del->_parent;
                    delete del;
                }
                else if (node->_right == nullptr)
                {
                    Node *del = node;
                    node = node->_left;
                    if (node != nullptr)
                        node->_parent = del->_parent;
                    delete del;
                }
                else
                {
                    // 找要删除节点的后继
                    Node *right_min_node = node->_right;
                    while (right_min_node->_left != nullptr)
                    {
                        right_min_node = right_min_node->_left;
                    }

                    // 记录right_min_node这个节点的值,吧这个节点的值和node节点交换,在删除right_min_node这个节点即可
                    // right_min_node这个节点的左子树一定为空,在上面顶点流程会处理
                    int tmp = right_min_node->_val;
                    _erase(tmp, node->_right);
                    node->_val = tmp;
                }
            }
        }
    }
    void _DisPlay(Node *root)
    {
        if (root == nullptr)
            return;
        _DisPlay(root->_left);
        std::cout << root->_val << " ";
        _DisPlay(root->_right);
    }

    int _height(Node *node)
    {
        if (node == nullptr)
            return 0;

        int leftHeight = _height(node->_left);
        int rightHeight = _height(node->_right);
        return std::max(leftHeight, rightHeight) + 1;
    }

    bool _isAVLTree(Node *node)
    {
        if (node == nullptr)
        {
            return true;
        }
        int leftHigh = _height(node->_left);
        int rightHigh = _height(node->_right);

        if (node->_bf != rightHigh - leftHigh)
        {
            std::cout << "ERROR:BF " << node->_bf << std::endl;
            return false;
        }

        return std::abs(rightHigh - leftHigh) < 2 && _isAVLTree(node->_left) && _isAVLTree(node->_right);
    }
    /**
     * 旋转代码结合博客流程对照
     */

    // 右单旋
    void TurnRight(Node *node)
    {
        Node *left = node->_left;
        Node *right = left->_right;

        node->_left = right;
        if (right != nullptr)
        {
            right->_parent = node;
        }

        left->_right = node;
        Node *parent = node->_parent; // 原父母
        node->_parent = left;

        // 修改平衡因子
        node->_bf = 0;
        left->_bf = 0;

        if (node == root)
        {
            // 旋转根节点
            root = left;
            root->_parent = parent;
        }
        else
        {
            left->_parent = parent;
            if (parent->_left == node)
            {
                parent->_left = left;
            }
            else
            {
                parent->_right = left;
            }
        }
    }

    // 左单旋
    void TurnLeft(Node *node)
    {
        Node *right = node->_right;
        Node *left = right->_left;

        node->_right = left;
        if (left != nullptr)
        {
            left->_parent = node;
        }

        right->_left = node;
        Node *parent = node->_parent;
        node->_parent = right;

        node->_bf = 0;
        right->_bf = 0;

        if (node == root)
        {
            node = right;
            right->_parent = parent;
        }
        else
        {
            right->_parent = parent;
            if (parent->_left == node)
            {
                parent->_left = right;
            }
            else
            {
                parent->_right = right;
            }
        }
    }

    // 左右双旋
    void TurnLeftRight(Node *node)
    {
        Node *left = node->_left;
        Node *right = left->_right;

        TurnLeft(left);
        TurnRight(node);

        // 调整平衡因子
        if (right->_bf == 1)
        {
            // 新节点插入到right节点的右子树
            right->_bf = 0;
            left->_bf = -1;
            node->_bf == 0;
        }
        else if (right->_bf == -1)
        {
            // 新节点插入到right节点的左子树上
            right->_bf = 0;
            left->_bf = 0;
            node->_bf = 1;
        }
        else if (right->_bf = 0)
        {
            right->_bf = 0;
            left->_bf = 0;
            node->_bf = 0;
        }
    }

    // 右左双旋
    void TurnRightLeft(Node *node)
    {
        Node *right = node->_right;
        Node *left = right->_left;

        TurnRight(right);
        TurnLeft(node);

        if (left->_bf == 1)
        {
            left->_bf = 0;
            right->_bf = 0;
            node->_bf = -1;
        }
        else if (left->_bf == -1)
        {
            left->_bf = 0;
            right->_bf = 1;
            node->_bf = 0;
        }
        else if (left->_bf = 0)
        {
            left->_bf = 0;
            right->_bf = 0;
            node->_bf = 0;
        }
    }
};

测试代码:

#include "BalanceTree.h"

using namespace std;

int main(int argc, char const *argv[])
{
    BalanceTree tree({3, 4, 2, 5, 6, 1, 7});
    tree.InorderDisplay();
    std::cout << tree.isAVLTree() << std::endl;
    tree.erase(4);
    tree.InorderDisplay();
    std::cout << tree.isAVLTree() << std::endl;
    tree.erase(5);
    tree.InorderDisplay();
    std::cout << tree.isAVLTree() << std::endl;
    return 0;
}

运行结果:
在这里插入图片描述

3. 考研数据结构代码仓库

考研数据结构代码仓库

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

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

相关文章

跨境群店安全运营神器—超级浏览器

有点跨境电商经验的跨境人都知道&#xff0c;物理隔离是比较安全稳妥的防关联办法。但是多台电脑多条网络的办公方式&#xff0c;不仅设备成本高&#xff0c;人员的费用也高得吓人。后来大家开始使用VPS来防关联。VPS是一种虚拟专用服务器&#xff0c;它是一种将一台服务器分割…

企业微信机器人发送消息

前言 随着科技的发展,各企业公司的业务不断发展,那么就需要强有力的沟通软件,其中企业微信、钉钉的能力得到了大众的认可,今天这篇文章就讲其中的一个功能-调用企业微信机器人(下文简称应用)进行消息传递。它的好处有哪些呢?自然是可以让相关人员及时追踪任务进度。 一、…

记住这12个要点,你也能打造出让HR和技术主管前一亮的前端简历

第一篇章&#xff1a;吸引HR 如果你想在众多简历中脱颖而出&#xff0c;需要注意以下几点&#xff1a; 1、突出你的亮点&#xff1a; 给你的简历一个吸引人的文件命名和头部&#xff0c;突出你的关键技能和经验。 2、采用简洁的语言&#xff1a; 用简单易懂的语言来描述你的…

JavaScript-XHR-深入理解

JavaScript-XHR-深入理解1. XHR(Asynchronous JavaScript And XML)初始1.1. xhr request demo1.2. status of XHRHttpRequest1.3. send synchronous request by xhr1.4. onload监听数据加载完成1.5. http status code1.6. get/post request with josn/form/urlcoded1.7. encaps…

mysql和sqlserver查询数据库表的数量的方法

一、mysql查询数据库表的数量 1、查询mysql下所有数据库表的数量 SELECT COUNT(*) TABLES, table_schema FROM information_schema.TABLES GROUP BY table_schema; 2、查询指定数据库的表的数量 SELECT COUNT(*) TABLES, table_schema FROM information_schema.TABLES WHER…

LeetCode第494题-目标和-python实现-图解思路与手撕代码

LeetCode第494题-目标和-python实现-图解思路与手撕代码 文章目录一、题目描述二、解题思路与代码实现1.解题思路2.代码实现总结一、题目描述 二、解题思路与代码实现 1.解题思路 这道题可以进行递归&#xff0c;遍历数组&#xff0c;对于当前这个数字&#xff0c;要么加上要…

Revit项目浏览器的标准设置应用和快速视图样板?

一、Revit项目浏览器的标准设置应用 设计院阶段的BIM应用&#xff0c;主要是Revit出施工图方面&#xff0c;需要涉及到很多标准的制定方面的问题&#xff0c;而且这个标准不仅仅是一个命名标准&#xff0c;还有很多的符合本院的出图标准等等&#xff0c;本期就不做详细讨论&…

【论文阅读】SCRFD: Sample and Computation 重分配的高效人脸检测

原始题目Sample and Computation Redistribution for Efficient Face Detection中文名称采样和计算 重分配的 高效人脸检测发表时间2021年5月10日平台ICLR-2022来源Imperial College&#xff0c; InsightFace文章链接https://arxiv.org/pdf/2105.04714.pdf开源代码官方实现&…

重压之下,特斯拉并不心甘情愿地召回FSD

/ 导读 /近日&#xff0c;美国国家公路交通安全管理局&#xff08;NHTSA&#xff09;宣布&#xff0c;其将召回近37万辆已安装或待安装全自动驾驶测试版&#xff08;FSD Beta&#xff09;的汽车。其实早在今年1月份的时候&#xff0c;NHTSA就要求特斯拉提交召回申请。而特斯拉在…

LabVIEW快速创建事件插件

LabVIEW快速创建事件插件此插件包含在LabVIEW2018及更高版本中。如果使用的是LabVIEW2017或更早版本&#xff0c;则只需从此处下载并安装它。在控件和控制终端上添加新的“创建>事件结构”&#xff1a;选择此选项将在控件上为指定事件配置新的事件结构&#xff1a;一些附加说…

jupyter使用指北:如何打开.ipynb文件|修改jupyter notebook的默认路径|在jupyter按照包

文章目录打开.ipynb文件、修改jupyter的默认路径笨办法好办法用jupyter notebook直接安装包运行代码打开.ipynb文件、修改jupyter的默认路径 比如&#xff0c;在该目录下有一个.ipynb文件&#xff0c;想用jupyter notebook直接打开&#xff1a; 笨办法 先进入jupyter再把文…

FFMPEG自学二 ⾳频编码实战

一、FFmpeg编码流程二、流程关键函数avcodec_find_encoder&#xff1a;根据指定的AVCodecID查找注册的编码器。 avcodec_alloc_context3&#xff1a;为AVCodecContext分配内存。 avcodec_open2&#xff1a;打开编码器。 avcodec_send_frame&#xff1a;将AVFrame⾮压缩数据给…

LVGL Styles

LVGL StylesGet started按钮添加标签按钮添加风格滑动条值显示StylesSize stylesBackground stylesBorder stylesOutline stylesShadow stylesImage stylesArc stylesText stylesLine stylesGet started 按钮添加标签 /*** brief 按钮事件回调函数* param e */ void btn_eve…

【Python实战】一大波高颜值主播来袭:快看,某网站颜值排名,为了这个排名我可是大费周章啦,第一名不亏是你...(人脸检测+爬虫实战)

导语 民间一直有个传闻......「听说某站的小哥哥小姐姐颜值都很高哦&#xff01;」 &#xff08;不是颜值高才能加入&#xff0c;是优秀的人恰好颜值高&#xff09; 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末…

Qt C++ 自定义仪表盘控件03

简介仪表盘是工控领域不可缺少的一类软件UI元素&#xff0c;通常出现在各类电子看板软件上&#xff0c;以及一些高级的上位机软件界面上&#xff0c;目的是将繁杂的数据转化为可视化的图表能大幅提高后台管理效率。本文分享了几个经典常用的仪表盘控件&#xff0c;在项目中可以…

系列一、SQL

一、SQL分类 二、DDL 定义&#xff1a;Data Definition Language&#xff0c;数据定义语言&#xff0c;用来定义数据库对象(数据库&#xff0c;表&#xff0c;字段) 2.1、数据库操作 2.1.1、查询所有数据库 show databases; 2.1.2、查询当前数据库 select database(); 2.…

Pytorch平均池化nn.AvgPool2d()使用记录

【pytorch官方文档】&#xff1a;https://pytorch.org/docs/stable/generated/torch.nn.AvgPool2d.html?highlightavgpool2d#torch.nn.AvgPool2dtorch.nn.AvgPool2d()作用在由多通道组成的输入特征中进行2D平均池化计算函数torch.nn.AvgPool2d(kernel_size, strideNone, paddi…

在 ubuntu 中切换使用不同版本的 python

引言有时我们不得不在同一台 ubuntu 中使用不同版本的 python 环境。本文的介绍就是可以在 ubuntu 上同时安装几个不同版本的 python&#xff0c;然后你可以随时指定当前要使用的 python 版本。步骤检查当前的 python 版本$ python3 --version python 3.6.8我的版本是 3.6.8假设…

Renegade:基于MPC+Bulletproofs构建的anonymous DEX

1. 引言 白皮书见&#xff1a; Renegade Whitepaper: Protocol Specification, v0.6 开源代码见&#xff1a; https://github.com/renegade-fi/renegade&#xff08;Renegade p2p网络每个节点的核心网络和密码逻辑&#xff09;https://github.com/renegade-fi/mpc-bulletpr…

OSPF(开放式最短路径优先协议)、ACL(访问控制列表)、NAT

一、OSPF -- &#xff08;开放式最短路径优先协议&#xff09; 基于组播更新 --- 224.0.0.5 224.0.0.6 1、协议类型&#xff1a;无类别链路状态的IGP协议 无类别&#xff1a;带精确掩码链路状态&#xff1a;不共享路由&#xff0c;共享拓扑&#xff08;共享LSA&#xff09;…