C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构)

本文目录

  • 00.BBST——平衡二叉搜索树
  • 01.AVL树
  • 02.AVL的插入
      • 2.1单旋——zig 与 zag
      • 2.2插入节点后的单旋实例
      • 2.3手玩小样例
      • 2.4双旋实例
      • 2.5小结
  • 03.AVL的删除
      • 3.1单旋删除
      • 3.2双旋删除
      • 3.3小结
  • 04.3+4重构
  • 05.综合评价AVL
      • 5.1优点
      • 5.2缺点
  • 06.代码
      • 注意
      • 插入算法
      • 删除算法
      • 完整代码:AVL.h

00.BBST——平衡二叉搜索树

本文是介绍众多平衡二叉搜索树(BBST)的第一篇——介绍AVL树。故先来引入BBST的概念。由于上一篇介绍的二叉搜索树(BST)在极度退化的情况下,十分不平衡,不平衡到只朝一侧偏,成为一条链表,复杂度可达 O ( n ) O(n) O(n),所以我们要在“平衡”方面做一些约束,以防我们的树结构退化得那么严重。

具体来说,含 n n n个节点,高度为 h h h的BST,若满足 h = O ( l o g 2 n ) h=O(log_2 n) h=O(log2n),则称为称为平衡二叉搜索树。

01.AVL树

AVL树是一种BBST(稍后会证明)。它约束自己是否平衡,主要靠一个指标——平衡因子。定义:平衡因子=左子树高度-右子树高度。如果满足 − 2 < 全部平衡因子 < 2 -2<全部平衡因子<2 2<全部平衡因子<2,则该AVL树处于平衡状态;否则,需要靠一系列措施,将其恢复平衡。

首先先证明AVL树满足BBST的要求,即 h = O ( l o g 2 n ) h=O(log_2 n) h=O(log2n)(下式)。我们可转而证明n=Ω(Φh)(即,AVL的节点数不会太少)
在这里插入图片描述

[结论] 高度为 h h h的AVL Tree 至少有 f i b ( ( h + 3 ) − 1 fib((h+3)-1 fib((h+3)1 个节点
[证明]
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

02.AVL的插入

插入一个节点会导致一串祖先的失衡,删除一个节点至多导致一个祖先失衡。但是,通过后续代码就可发现,删除节点比插入节点复杂的多。原因是,插入节点只要调整好了一处,这条路径上的所有祖先都可平衡,复杂度是O(1)。而删除节点是,调整好了一处平衡,另一处就会不平衡,自下而上层层调整,复杂度是O(n)

2.1单旋——zig 与 zag

zig 与 zag 分别对应右单旋和左单旋。单旋的操作改变的是两个节点的相对位置。改变的是三条线:一上一下一子树。新树根上行指向原根,新树根原子树给到原根。如下图,V到Y那去,Y到C那去。

在这里插入图片描述

2.2插入节点后的单旋实例

在下图处添加一个节点,自上而下更新高度(或平衡因子),g会率先进入不平衡状态。观察g,p,v呈一条线,而非“之”字,所以用单旋调整(之字形对应双旋)。具体来说,对g左单旋。
在这里插入图片描述

2.3手玩小样例

例题:将1,2,3,4,5,6依次插入空的AVL Tree,最终AVL Tree长成什么样?

[过程]首先正常插入1,2;插入3时,1是第一个发现不平衡的节点,zag(1),即对1进行左单旋,成功解决;正常插入4
在这里插入图片描述

插入5时,3是第一个发现不平衡的节点,zag(3),即对3进行左单旋,成功解决
在这里插入图片描述
插入6时,2是第一个发现不平衡的节点,zag(2),即对2进行左单旋,成功解决
在这里插入图片描述

2.4双旋实例

双旋的操作改变的是三个节点的相对位置。分为两种情况——zig-zag与zag-zig。

在下图处添加一个节点,自上而下更新高度(或平衡因子),g会率先进入不平衡状态。观察g,p,v呈“之”字,所以用双旋。具体来说,先zig§,再zag(g).
在这里插入图片描述

2.5小结

AVL树中插入节点引发失衡,经旋转调整后重新平衡,此时包含节点g,p,v的子树高度是不变的子树高度复原,更高祖先也必平衡,全树复衡。故在AVL树中修正插入节点引发的失衡不会出现失衡传播。

03.AVL的删除

删除一个节点至多导致一个祖先失衡。

3.1单旋删除

在这里插入图片描述

3.2双旋删除

在这里插入图片描述

3.3小结

AVL树中删除节点引发失衡,经旋转调整后重新平衡,此时包含节点g,p,v的子树高度有可能不变也有可能减小1,故在AVL树中修正删除节点引发的失衡有可能出现失衡传播。

04.3+4重构

通过观察以上插入和删除的结果示意图,发现结构是一样的——三个节点按顺序呈三角形,四个子树按原来的顺序分别挂在两个孩子节点的下边。(如下图)
在这里插入图片描述

那我们就不必关注具体的技巧了,而是将三个节点和四个子树拆开,像暴力组装魔方那样(先拆散)拼上。

template <typename T>
BinNode<T> * BST<T>::connect34(BinNode<T> * a, BinNode<T> * b, BinNode<T> * c, BinNode<T> * T1, BinNode<T> * T2, BinNode<T> *T3, BinNode<T> * T4)
{b->left = a;  b->right = c;a->left = T1; a->right = T2;c->left = T3; c->right = T4;a->parent = b; c->parent = b;if (T1) T1->parent = a;if (T2) T2->parent = a;if (T3) T3->parent = c;if (T4) T4->parent = c;a->updateHigh(); b->updateHigh(); c->updateHigh();return b;
}template <typename T>
BinNode<T> * BST<T>::rotateAt(BinNode<T> * v)
{BinNode<T> * p = v->parent;BinNode<T> * g = p->parent;BinNode<T> * T1, *T2, *T3, *T4, *a, *b, *c;if (p == g->left && v == p->left){a = v; b = p; c = g;T1 = v->left; T2 = v->right; T3 = p->right; T4 = g->right;}else if (p == g->left && v == p->right){a = p; b = v; c = g;T1 = p->left; T2 = v->left; T3 = v->right; T4 = g->right;}	else if (p == g->right && v == p->left){a = g; b = v; c = p;T1 = g->left; T2 = v->left; T3 = v->right; T4 = p->right;}else{a = g; b = p; c = v;T1 = g->left; T2 = p->left; T3 = v->left; T4 = v->right;}b->parent = g->parent; //向上链接return connect34(a, b, c, T1, T2, T3, T4);}

05.综合评价AVL

5.1优点

  1. 查找、插入、删除,最坏时间复杂度为 O ( l o g n ) O(logn) O(logn)
  2. O ( n ) O(n) O(n)的存储空间

5.2缺点

  1. 需要额外维护高度或平衡因子这一指标(后续Splay Tree可改善这一问题)
  2. 删除操作后,最多需旋转 Ω ( l o g n ) \Omega(logn) Ω(logn)
  3. 单次动态调整后,全树拓扑结构的变化量可能高达 Ω ( l o g n ) \Omega(logn) Ω(logn) (RedBlack Tree可缩到 O ( 1 ) O(1) O(1)

谢谢观看~

06.代码

注意

  1. fromParentTo()是根节点的情况
  2. connect34()向上链接别忘

插入算法

为什么不用现成的BST::insert(val)? BST::insert自带更新一串高度,旋转调整之后还得把这一串更新回来。

BinNode<T> * insert(T const & val){BinNode<T> * & X = BST<T>::search(val);if (!X){X = new BinNode<T>(val, BST<T>::hot); BinTree<T>::size++;BinNode<T> * X_copy = X;while (X_copy && AvlBalanced(X_copy)){X_copy->updateHigh();X_copy = X_copy->parent;}if (X_copy) //说明是因为遇到了不平衡节点才退出了while,现在解决不平衡问题{BinNode<T> * & tmp = BinTree<T>::fromParentTo(X_copy);tmp = BST<T>::rotateAt(tallerChild(tallerChild(X_copy))); // 内部自带单个节点更新高度}return X;}}

删除算法

受限于BST::remove的返回值仅仅是bool,所以用底层的removeAt. removeAt的返回值是接替者,但有时,接替者是NULL。还好有BST::hot,存放被删节点的父亲。实际上,BST::remove的更新高度也是从hot开始的

bool remove(T const & val) {BinNode<T> * & X = BST<T>::search(val);if (!X) return false;else{BST<T>::removeAt(X, BST<T>::hot);BinTree<T>::size--;// 与insert不同的是,remove可能要调整很多次for (BinNode<T> * g = BST<T>::hot; g; g = g->parent){int i = BF(g);if (!AvlBalanced(g)){BinNode<T> * & tmp = BinTree<T>::fromParentTo(g);tmp = BST<T>::rotateAt(tallerChild(tallerChild(g))); }else g->updateHigh();}return true;}}

完整代码:AVL.h

# pragma once
# include "BST.h"# define BF(x) (int)(getHigh(x->left) - getHigh(x->right))
# define AvlBalanced(x)  ( -2 < BF(x) && BF(x) < 2 )template <typename T>
BinNode<T> * tallerChild(BinNode<T> * x)
{return  (getHigh(x->left) > getHigh(x->right)) ? x->left : x->right;
}template <typename T>
class AVL :public BST<T>
{public:bool remove(T const & val) {BinNode<T> * & X = BST<T>::search(val);if (!X)  return false;else{BST<T>::removeAt(X, BST<T>::hot);BinTree<T>::size--;// (可优化:直到到某祖先,高度不变,停止上行。那就要在刚刚更新高度时记录中途退出的位置,以便在此处判断)for (BinNode<T> * g = BST<T>::hot; g; g = g->parent){int i = BF(g);if (!AvlBalanced(g)){BinNode<T> * & tmp = BinTree<T>::fromParentTo(g);tmp = BST<T>::rotateAt(tallerChild(tallerChild(g))); // 内部自带单个节点更新高度}else g->updateHigh();}return true;}}BinNode<T> * insert(T const & val){BinNode<T> * & X = BST<T>::search(val);if (!X){X = new BinNode<T>(val, BST<T>::hot); //这一句话将两个关系连接BinTree<T>::size++;BinNode<T> * X_copy = X;while (X_copy && AvlBalanced(X_copy)){X_copy->updateHigh();X_copy = X_copy->parent;}if (X_copy) //说明是因为遇到了不平衡节点才退出了while,现在解决不平衡问题{BinNode<T> * & tmp = BinTree<T>::fromParentTo(X_copy);tmp = BST<T>::rotateAt(tallerChild(tallerChild(X_copy))); // 内部自带单个节点更新高度}return X;}}
};

感谢观看~

附上前传:
C++数据结构之BinaryTree(二叉树)的实现
C++数据结构之BST(二叉搜索树)的实现

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

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

相关文章

spring security + oauth2 使用RedisTokenStore 以json格式存储

1.项目架构 2.自己对 TokenStore 的 redis实现 package com.enterprise.auth.config;import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis…

基于SpringBoot+Vue的漫画网站设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

41.利用matlab 平衡方程用于图像(matlab程序)

1.简述 白平衡 白平衡的英文为White Balance&#xff0c;其基本概念是“不管在任何光源下&#xff0c;都能将白色物体还原为白色”&#xff0c;对在特定光源下拍摄时出现的偏色现象&#xff0c;通过加强对应的补色来进行补偿。 所谓的白平衡是通过对白色被摄物的颜色还原&…

opencv-34 图像平滑处理-双边滤波cv2.bilateralFilter()

双边滤波&#xff08;BilateralFiltering&#xff09;是一种图像处理滤波技术&#xff0c;用于平滑图像并同时保留边缘信息。与其他传统的线性滤波方法不同&#xff0c;双边滤波在考虑像素之间的空间距离之外&#xff0c;还考虑了像素之间的灰度值相似性。这使得双边滤波能够有…

【C语言进阶】指针的高级应用(下)

文章目录 一、指针数组与数组指针1.1 指针数组与数组指针的表达式 二、函数指针2.1 函数指针的书写方式 三、二重指针与一重指针3.1 二重指针的本质3.2 二重指针的用法3.3 二重指针与数组指针 总结 一、指针数组与数组指针 (1)指针数组的实质是一个数组&#xff0c;这个数组中存…

【python】使用Selenium和Chrome WebDriver来获取 【腾讯云 Cloud Studio 实战训练营】中的文章信息

文章目录 前言导入依赖库设置ChromeDriver的路径创建Chrome WebDriver对象打开网页找到结果元素创建一个空列表用于存储数据遍历结果元素并提取数据提取标题、作者、发布时间等信息判断是否为目标文章提取目标文章的描述、阅读数量、点赞数量、评论数量等信息将提取的数据存储为…

IntelliJ IDEA 2023.2 最新变化

IntelliJ IDEA 2023.2 引入 AI Assistant&#xff0c;通过一组由 AI 提供支持的功能助力开发。 升级的 IntelliJ 分析器现在提供编辑器内提示&#xff0c;使分析进程更加直观详尽。 此版本还包括有助于简化开发工作流的 GitLab 集成&#xff0c;以及其他多项值得关注的更新和改…

在政策+市场双轮驱动下,深眸科技助力机器视觉行业走向成熟

近年来&#xff0c;随着人工智能发展的不断提速&#xff0c;机器视觉作为其重要的前沿分支&#xff0c;凭借着机器代替人眼来做测量和判断的能力&#xff0c;广泛应用于工业领域的制造生产环节&#xff0c;用来保证产品质量、控制生产流程、感知环境等&#xff0c;并迸发出强劲…

基于新浪微博海量用户行为数据、博文数据数据分析:包括综合指数、移动指数、PC指数三个指数

基于新浪微博海量用户行为数据、博文数据数据分析&#xff1a;包括综合指数、移动指数、PC指数三个指数 项目介绍 微指数是基于海量用户行为数据、博文数据&#xff0c;采用科学计算方法统计得出的反映不同事件领域发展状况的指数产品。微指数对于收录的关键词&#xff0c;在指…

用Log4j 2记录日志

说明 maven工程中增加对Log4j 2的依赖 下面代码示例的maven工程中的pom.xml文件中需要增加对Log4j 2的依赖&#xff1a; <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.20.0&…

【C语言学习】整数的输入输出、八进制和十六进制

一、整数的输入输出 只有两种形式&#xff1a;int或long long %d:int %u:unsigned %ld:long long %lu:unsigned long long 二、八进制和十六进制 以0开头就是八进制&#xff0c;以0x开头就是十六进制。 无论是八进制还是十六进制只是如何将数字表达为字符串&#xff0c;但计…

UML-构件图

目录 1.概述 2.构件的类型 3.构件和类 4.构件图 1.概述 构件图主要用于描述各种软件之间的依赖关系&#xff0c;例如&#xff0c;可执行文件和源文件之间的依赖关系&#xff0c;所设计的系统中的构件的表示法及这些构件之间的关系构成了构件图 构件图从软件架构的角度来描述…

Vue2 第二十节 vue-router (四)

1.全局前置路由和后置路由 2.独享路由守卫 3.组件内路由守卫 4.路由器的两种工作模式 路由 作用&#xff1a;对路由进行权限控制 分类&#xff1a;全局守卫&#xff0c;独享守卫&#xff0c;组件内守卫 一.全局前置路由和后置路由 ① 前置路由守卫&#xff1a;每次路由…

《每天5分钟玩转kubernetes》读书笔记

笔记 概念 Pod是脆弱的&#xff0c;但应用是健壮的。 kubelet运行在Cluster所有节点上&#xff0c;负责启动Pod和容器。kubeadm用于初始化Cluster。kubectl是k8s命令行工具。通过kubectl可以部署和管理应用&#xff0c;查看各种资源&#xff0c;创建、删除和更新各种组件。 …

python使用虚拟环境安装第三方模块和运行python脚本

1、在用户家目录下创建一个名为env38的虚拟环境 python3 -m venv env38 创建虚拟环境env38后的目录 2、 激活虚拟环境 source ~/env38/bin/activate 3、安装pyqt5 pip install pyqt5 4、写一个简单的python脚本 import sys print(sys.path)# 如果执行成功&#xff0c;没有…

Unity Shader编辑器工具类ShaderUtil 常用函数和用法

Unity Shader编辑器工具类ShaderUtil 常用函数和用法 Unity的Shader编辑器工具类ShaderUtil提供了一系列函数&#xff0c;用于编译、导入和管理着色器。本文将介绍ShaderUtil类中的常用函数和用法。 编译和导入函数 CompileShader 函数签名&#xff1a;public static bool C…

CAD随机球体颗粒过渡区3D插件

插件介绍 CAD随机球体颗粒&过渡区3D插件可用于在AutoCAD软件内生成随机分布的球体及球体外侧过渡区部件&#xff0c;适用于科研绘图、有限元建模如混凝土细观、颗粒增强复合材料、随机三维骨料及过渡区等方面的应用。 插件可指定的参数有模型的长、宽、高&#xff1b;球…

如何将 dubbo filter 拦截器原理运用到日志拦截器中?

业务背景 我们希望可以在使用日志拦截器时&#xff0c;定义属于自己的拦截器方法。 实现的方式有很多种&#xff0c;我们分别来看一下。 拓展阅读 java 注解结合 spring aop 实现自动输出日志 java 注解结合 spring aop 实现日志traceId唯一标识 java 注解结合 spring ao…

概念解析 | PointNet概述

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次解析的概念是:点云深度学习及PointNet论文概述 参考论文:Qi C R, Su H, Mo K, et al. Pointnet: Deep learning on point sets for 3d classification and segmentation[C]//Proceedings of …

yum出现Could not retrieve mirrorlist解决方法

Loaded plugins: fastestmirror, security Loading mirror speeds from cached hostfile Could not retrieve mirrorlist http://mirrorlist.centos.org/?release6&archi386&repoos error was 14: PYCURL ERROR 6 - “Couldn’t resolve host ‘mirrorlist.centos.org…