平衡二叉搜索树之 AVL 树的模拟实现【C++】

平衡二叉搜索树之 AVL 树的模拟实现【C++】

文章目录

AVL树的简单介绍

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

AVL树就可以解决上述问题,让搜索树的查找效率在任何情况下都能稳定是O(logN)

AVL树解决上述问题的方法是:
保证每个结点的左右子树高度之差的绝对值不超过1
这样就能保证树中的节点分布接近满二叉树,高度非常接近logN【N为树中节点的个数】,进而让一次查找的效率为O(logN)

为什么是保证每个结点的左右子树高度之差的绝对值不超过1,而不是保证左右子树高度一样呢?
其实很简单:
因为在一些情况下绝对不可能做到左右子树高度一样,例如:

在这里插入图片描述


此时无论如何改变树的形态,都不可能做到每个结点的左右子树高度相等


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

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

全部的实现代码放在了文章末尾

准备工作

创建两个文件,一个头文件AVLTree.hpp,一个源文件test.cpp

【因为模板的声明和定义不能分处于不同的文件中,所以把成员函数的声明和定义放在了同一个文件AVLTree.hpp中】

  1. AVLTee.hpp:存放包含的头文件,命名空间的定义,成员函数和命名空间中的函数的定义
  2. test.cpp:存放main函数,以及测试代码

包含头文件

  • iostream:用于输入输出
  • cmath:提供数学函数

类的成员变量

实现AVL树最主要的就是保证树中节点的左右子树高度差不超过1

而维护这一条件的方法并不是唯一的,我使用的是平衡因子来维护

平衡因子时每个节点都有的,它的值就是这个节点的左右子树高度之差【一般是右子树高度-左子树高度

在这里插入图片描述

构造函数和拷贝构造

构造函数没什么好说的,默认构造就行了

AVLTree():_root(nullptr){ }

拷贝构造:
因为节点都是从堆区new出来的,所以要深拷贝

使用递归实现深拷贝:
因为拷贝构造不能有多余的参数,但是递归函数又必须使用参数记录信息

在这里插入图片描述

然后在拷贝构造里面调用一下这个函数就行了

AVLTree(const AVLTree& obj){  根节点的父亲节点是nullptr _root =Copy(obj._root,nullptr);}

swap和赋值运算符重载

交换两颗二叉搜索树的本质就是交换两颗数的资源(数据),而它们的资源都是从堆区申请来的,然后用指针指向这些资源

在这里插入图片描述

并不是把资源存储在了二叉搜索树对象中

所以资源交换很简单,直接交换_root指针的指向即可

voidSwap(AVLTree& obj){  std::swap(_root, obj._root);}

赋值运算符重载

AVLTree&operator=(AVLTree obj){ this->Swap(obj);return*this;}

为什么上面的两句代码就可以完成深拷贝呢?
这是因为:

使用了传值传参,会在传参之前调用拷贝构造,再把拷贝构造出的临时对象作为参数传递进去

赋值运算符的左操作数,*this再与传入的临时对象obj交换,就直接完成了拷贝

在函数结束之后,存储在栈区的obj再函数结束之后,obj生命周期结束

obj调用析构函数,把指向的从*this那里交换来的不需要的空间销毁


析构函数

使用递归遍历,把所有从堆区申请的节点都释放掉:
因为析构函数不能有多余的参数,但是递归函数又必须使用参数记录信息
所以再封装一个成员函数,专门用来递归释放:

在这里插入图片描述

然后在拷贝构造里面调用一下这个函数就行了

~AVLTree(){ Destroy(_root); _root =nullptr;}

find

具体流程:
从根节点开始,将目标值(传入的key)与当前节点的key进行比较。
如果目标值小于当前节点值,则在左子树中继续查找;
如果目标值大于当前节点值,则在右子树中继续查找。

这个过程一直进行,直到找到与目标值或者到达空节点为止。

把上述过程转成代码:

在这里插入图片描述

insert[重要]

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。

那么AVL树的插入过程可以分为两步:

  1. 先按照二叉搜索树的方式插入新节点
  2. 再调整节点的平衡因子

因为新节点一定是插在叶子节点或者只有一个孩子节点的节点
所以插入节点后一定会影响新节点的父亲节点的平衡因子,可能会影响祖先节点

插入节点后具体分以下3种情况:

  1. 插入节点的父亲节点(以下简称parent)的平衡因子等于2/-2此时parent的左右子树高度差已经超过1,已经违反了AVL树的规则需要旋转进行处理

插入节点的父亲节点(以下简称parent)的平衡因子等于1/-1
那么就说明插入之前parent的平衡因子一定是0【如果插入前是2/-2的话,一定早就被旋转了】
所以parent在插入之前是叶子节点
即以parent为根的子树插入之前的高度就是1,插入之后高度就变成了2
所以插入前后以parent为根的子树的高度增加了,自然就影响parent的祖先的平衡因子

在这里插入图片描述

所以需要再向上继续更新祖先节点的平衡因子

插入节点的父亲节点(以下简称parent)的平衡因子等于0
那么就说明插入之前parent的平衡因子一定是1/-1
所以parent在插入之前是只有一个孩子节点的节点
即以parent为根的子树插入之前的高度就是2,插入之后高度还是2
所以插入前后以parent为根的子树的高度没有改变,自然就不会影响parent的祖先的平衡因子

在这里插入图片描述

所以不需要再向上取跟新祖先节点的平衡因子
直接插入结束

在这里插入图片描述

当parent的平衡因子为1/-1时,如何向上更新祖先节点的平衡因子呢?

其实也简单:
就是把原来的parent当做新的cur,把parent的父节点作为新的parent

再判断新的cur是父亲节点的左还是右,据此再更新新的parent的平衡因子

cur = parent; parent = parent->_parent; 因为以之前的parent为根的子树,高度增加了1 右因为平衡因子=右子树高度-左子树高度 所以: if(cur==parent->_left){  parent->_bf--;}else{  parent->_bf++;}

然后,再重复判断一下新的parent的平衡因子的3种情况,进行处理

  1. 新的parent的平衡因子变成了0,插入就结束
  2. 新的parent的平衡因子变成了1/-1,就再重复这个过程,继续向上更新祖先节点的平衡因子
  3. 新的parent的平衡因子变成了2/-2,就旋转

怎么旋转?

旋转分以下4种:

左单旋

所有的需要左单旋的情况,都可以画成下图的抽象图

在这里插入图片描述

具体情况描述:

  1. 插入前paernt的平衡因子为1,并且它的右边(PR)的平衡因子为0
  2. 新插入的节点插在子树c上,并使子树c的高度增加1
  3. 插入后并向上跟新后:paernt的平衡因子变成2,并且它的右边(PR)的平衡因子变成了1

此时就使用左单旋:
把(下图中的)PRL链接在parent的右子树上,再把parent连接在PR的左子树上
把PR作为这颗子树新的根

这样就可以做到:在不违反搜索树规则【所有左子树上的值<根<右子树】的基础上,尽可能地让树平衡

将上述过程转换成代码:

在这里插入图片描述

右单旋

所有的需要右单旋的情况,都可以画成下图的抽象图

在这里插入图片描述


具体情况描述:

  1. 插入前paernt的平衡因子为-1,并且它的左边(PL)的平衡因子为0
  2. 新插入的节点插在子树a上,并使子树a的高度增加1
  3. 插入后并向上跟新后:paernt的平衡因子变成-2,并且它的左边(PL)的平衡因子变成了-1

此时就使用左单旋:
把(下图中的)PLR链接在parent的左子树上,再把parent连接在PL的右子树上
把PL作为这颗子树新的根

这样就可以做到:在不违反搜索树规则【所有左子树上的值<根<右子树】的基础上,尽可能地让树平衡

将上述过程转换成代码:

在这里插入图片描述

左右双旋

所有的需要左右双旋的情况,都可以画成下图的抽象图

在这里插入图片描述

具体情况描述:

  1. 插入前paernt的平衡因子为-1,并且它的左边(PL)的平衡因子为0
  2. 新插入的节点插在子树b或者c上,并使子树b或者c的高度增加1
  3. 插入后并向上跟新后:paernt的平衡因子变成-2,并且它的左边(PL)的平衡因子变成了1

此时使用一次单旋,是解决不了的,旋了之后还是有平衡因子为2/-2的节点

但是如果我们对PL进行左单旋之后,就可以发现可以对parent使用右旋来使树,在不违反搜索树规则【所有左子树上的值<根<右子树】的基础上,尽可能地接近平衡

RotateL(parent->_left);RotateR(parent);

右左双旋

所有的需要右左双旋的情况,都可以画成下图的抽象图

在这里插入图片描述

具体情况描述:

  1. 插入前paernt的平衡因子为1,并且它的右边(PR)的平衡因子为0
  2. 新插入的节点插在子树b或者c上,并使子树b或者c的高度增加1
  3. 插入后并向上跟新后:paernt的平衡因子变成2,并且它的右边(PR)的平衡因子变成了-1

此时使用一次单旋,是解决不了的,旋了之后还是有平衡因子为2/-2的节点

但是如果我们对PR进行右单旋之后,就可以发现可以对parent使用左单旋来使树,在不违反搜索树规则【所有左子树上的值<根<右子树】的基础上,尽可能地接近平衡

RotateR(parent->_right);RotateL(parent);

旋转的平衡因子更新

左单旋和右单旋

因为插入的情况都只有一种,所以平衡因子的更新很简单,看上面画的示意图就行


parentPR(PL)的平衡因子最后都变成0,其他节点的平衡因子没有变化


左右双旋和右左双旋

因为左右双旋和右左双旋的新节点既可以插在子树b上,又可以插在子树c上
而插在这两颗子树上的平衡因子更新是不同的

下图是左右双旋新节点插在子树b上的
最后:parent的平衡因子=1,PL的平衡因子=0,PLR的平衡因子=0

在这里插入图片描述


下图是左右双旋新节点插在子树c上的
最后:parent的平衡因子=0,PL的平衡因子=-1,PLR的平衡因子=0

在这里插入图片描述

而且当h=0时,还有一种情况:
下图是左右双旋的h=0的旋转图
最后:parent的平衡因子=0,PL的平衡因子=0,PLR的平衡因子=0

在这里插入图片描述

综上:
左右双旋代码

在这里插入图片描述

右左双旋同理

在这里插入图片描述

insert的全部代码

boolInsert(const T& data){ if(_root ==nullptr) 树为空,则直接新增节点 {  赋值给二叉搜索树的成员变量`_root`指针 _root =newNode(data);returntrue; 返回true,代表插入成功 } Node* cur = _root; 从根节点开始 定义parent来保存cur的父亲节点 假设根节点的父亲节点为nullptr Node* parent =nullptr;while(cur){ 

Read more

Python保姆级下载安装教程-->Windows版本

Python保姆级下载安装教程-->Windows版本

Windows版本保姆级下载安装 一、下载Python  1、点击下载官网地址 Python官方网站地址https://www.python.org/downloads/ 2、官网页面如下: 3、点击下载界面: 上面最新的版本是3.14.2版本,一般来说新版较之老版优化了一些内容且版本向下兼容,但是不建议下载最新版本,因为python在很多地方使用时没有更新到最新版本,向下兼容性并不好,但也不要太低版本的,很多不适用。 点击Downloads,选择适合自己电脑系统的版本,我的电脑是Windows系统,就选择了Windows,点击后会跳转到另一个页面 【Stable Releases】:稳定发布版本,是官方完成全面测试、修复已知 Bug 的成熟版本,运行稳定、风险低,无论入门学习还是机器视觉项目开发,都优先选这个版本; 【Pre-releases】:预发布版本,属于测试阶段的 “体验版”,可能包含新功能但存在未修复的 Bug,稳定性差,小白或做实际项目(如机器视觉开发)千万别选,易出现代码报错、

By Ne0inhk
python+uniapp微信小程序的便捷理疗店服务预约系统的研究与实现

python+uniapp微信小程序的便捷理疗店服务预约系统的研究与实现

目录 * 研究背景与意义 * 系统需求分析 * 技术选型与架构设计 * 核心功能实现 * 数据处理与安全 * 性能优化策略 * 测试与部署 * 总结与展望 * 开发技术 * 源码文档获取/同行可拿货,招校园代理 :文章底部获取博主联系方式! 研究背景与意义 阐述传统理疗店预约系统的痛点(如效率低、管理混乱),分析微信小程序结合Python后端在便捷性、用户体验和数据处理上的优势,提出研究价值。 系统需求分析 * 功能需求:用户预约、服务分类展示、订单管理、支付接口、评价系统、商家后台管理。 * 非功能需求:响应速度(小程序端<1s)、数据安全性(HTTPS+JWT)、跨平台兼容性(UniApp多端适配)。 技术选型与架构设计 * 前端:UniApp(Vue3语法)+ uView UI组件库,实现微信/支付宝等多端适配。 * 后端:Python Flask/

By Ne0inhk
Python——Windows11环境安装配置Python 3.12.5

Python——Windows11环境安装配置Python 3.12.5

目录 * 一、下载Python * 二、下载Python步骤 * 三、安装Python * 四、验证Python * 4.1、验证Python环境 * 4.2、验证pip * 4.3、更新pip * 4.4、pip镜像源切换(永久切换,全局生效,清华镜像源和阿里云镜像源二选一即可) * 4.4.1、清华镜像源 * 4.4.2、阿里云镜像源 * 4.5、安装依赖包(检验是否成功) * 五、配置环境变量(可选) 一、下载Python 或者百度网盘下载 链接: https://pan.baidu.com/s/1Rc8g1mZrfDtOexev2JK7NA?pwd=

By Ne0inhk
Python 入门超详细指南:环境搭建 + 核心优势 + 应用场景(零基础友好)

Python 入门超详细指南:环境搭建 + 核心优势 + 应用场景(零基础友好)

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 先搞懂:计算机与编程的核心概念 * 1.1 什么是计算机? * 1.2 什么是编程? * 二. 认识 Python:起源、优势与应用场景 * 2.1 Python 的 “前世今生” * 2.2 Python 的优缺点以及应用场景大盘点 * 三. Python 的就业前景:理性看待 “钱景” * 四. 环境搭建:Python+PyCharm(一步到位) * 4.1 安装 Python

By Ne0inhk