【数据结构】AVL树——平衡二叉搜索树

个人主页:东洛的克莱斯韦克-CSDN博客

祝福语:愿你拥抱自由的风

目录

二叉搜索树

AVL树概述

平衡因子

旋转情况分类

左单旋

右单旋

左右双旋

右左双旋

AVL树节点设计

AVL树设计

详解单旋

左单旋

右单旋

详解双旋

左右双旋

平衡因子情况如下

右左双旋

平衡因子情况如下


二叉搜索树

【C++】详解二叉搜索树-CSDN博客

AVL树概述

平衡树:左子树的高度等于右子树的高度

不平衡树:左子树的高度不等于等于右子树的高度

二叉搜索树很难是一颗平衡树。

对二叉树进行插入和删除的操作,或插入大量的数据不够随机,都会是使二叉搜索树不够平衡。

极端情况下,二叉树会退化成类似链表的结构,那么二叉搜索树查询数据的效率荡然无存。

在二叉树的基础上加入平衡的概念就是平衡二叉搜索树,也叫AVL树

AVL树也不是一颗绝对的平衡树,AVL树的平衡是相对的,它允许左子树和右子树的高度为 1 ,但不能超过 1

平衡是相对的很好理解,因为一个父亲节点最多只能有两个孩子节点,而数据又是一个一个插入的,所以一定会出现左子树和右子树高度差为 1 的情况。

B树可达到绝对平衡,因为B树是多叉结构——一个父亲节点有多个孩子节点

如果左子树和右子树的高度差为 2 就视为打破平衡

如果打破平衡,就需要通过旋转这一操作让左右子树的高度差小于等于 1 。

AVL树是保持一种相对平衡的状态,而不是绝对平衡。那么AVL树搜索数据的效率只能是接近O(logN)

AVL树只是保证了搜索效率的下限,而不是提高了上限

平衡因子

平衡因子这一概念并不是AVL树所必备的——从代码实现的角度来说,如果不加入平衡因子的概念理解起来会比较抽象。

平衡因子:让每个节点存一个整型,该整形值的大小等于右子树的高度减左子树的高度

平衡因子等于 0 左右子树平衡

平衡因子等于 1左右子树相对平衡,右树偏高

平衡因子等于 -1 :左右子树相对平衡,左树树偏高

平衡因子等于 2 -2左右子树不平衡

平衡因子的更新:

插入父亲节点的右边平衡因子加加,插入父亲节点的右边平衡因子减减

父亲节点更新后的平衡因子等于 1 或 -1 ,需要不断往上(溯源)更新,直到父亲节点的平衡因子为 0 或 更新至整棵树的根节点就停止更新

如果父亲节点的平衡因子为 2 或 -2 时,需要对这棵子树旋转,旋转后更新平衡因子

示例

旋转情况分类

旋转分为:

左单旋 右单旋  左右双旋  右左双旋

左单旋

:新节点插入较高右子树的右侧

具象图:

抽象图:

那么左单旋是怎么旋的呢?核心步骤为:

设父亲节点为:fathernode 孩子节点为:cur

cur的左孩子成为fathernode的右孩子,

再让fathernode成为cur的左孩子。

如下示意图

右单旋

:新节点插入较高左子树的左侧

具象图:

抽象图:

那么右单旋是怎么旋的呢?核心步骤为:

设父亲节点为:fathernode 孩子节点为:cur

cur的右孩子成为fathernode的左孩子,

再让fathernode成为cur的右孩子

如下示意图:

左右双旋

:新节点插入在较高左子树的右侧——先左单旋再右单旋

左右双旋的核心步骤为:

设父亲节点为:fathernode 

父亲的左孩子节点为:fathernodeL

父亲的左孩子节点的右孩子节点的为fathernodeLR

先让fathernodeL左单旋,再让fathernodeLR进行右单旋

这里小编直接上抽象图:

右左双旋

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

设父亲节点为:fathernode 

父亲的 右孩子节点为:fathernodeR

父亲的右孩子节点的左孩子节点的为fathernodeRL

先对fathernodeR进行右单旋,再对fathernode进行左单旋。

示意图:

AVL树节点设计

【C++】详解C++的模板-CSDN博客

AVL树的节点需要三个指针,分别指向左孩子节点,右孩子节点,父亲节点。指向父亲节点的指针是为了能溯源更新平衡因子。

需要一个整型存储平衡因子,平衡因子在构造函数的初始化列表中初始化为 0,因为新节点左右孩子都为空。

template <class K>
class AVLTreeNode
{
public:AVLTreeNode(const K& key) //构造函数:_key(key), _left(nullptr), _right(nullptr), _FatherNode(nullptr), _bf(0){}K _key; //键值   AVLTreeNode<K>* _left;//左孩子AVLTreeNode<K>* _right;//右孩子AVLTreeNode<K>* _FatherNode;//父亲  int _bf;//平衡因子};

AVL树设计

template <class K>
class AVLTree
{typedef AVLTreeNode<K> node; node* _root;public:AVLTree()  //构造函数,初始化为空树:_root(nullptr){}bool Insert(const K& key)//插入元素{
//if (nullptr == _root) //是否是空树{_root = new node(key);  return true;}
//node* cur = _root;node* fathernode = nullptr;while (cur)  //查找插入的位置,如果树中已经有要插入的值,则插入失败,{if (cur->_key < key){fathernode = cur;cur = cur->_right;}else if (cur->_key > key){fathernode = cur;cur = cur->_left;}else{return false;}}cur = new node(key); //新插入节点 if (fathernode->_key < cur->_key) //判断新节点应该是左孩子还是右孩子{fathernode->_right = cur;cur->_FatherNode = fathernode;}else{fathernode->_left = cur;cur->_FatherNode = fathernode;}//while (fathernode)//更新平衡因子{if (cur == fathernode->_left){fathernode->_bf--;}else  if (cur == fathernode->_right){fathernode->_bf++;}//if (fathernode->_bf == 0){// 更新结束break;}else if (fathernode->_bf == 1 || fathernode->_bf == -1){// 继续往上更新cur = fathernode;fathernode = fathernode->_FatherNode;}else if (fathernode->_bf == 2 || fathernode->_bf == -2){// 子树不平衡了,需要旋转if (fathernode->_bf == 2 && cur->_bf == 1){RevolveLeft(fathernode);//左单旋}else if (fathernode->_bf == -2 && cur->_bf == -1){RevolveRight(fathernode);//右单旋}else if (fathernode->_bf == 2 && cur->_bf == -1){RevolveRightLeft(fathernode); //右左双旋   }else if (fathernode->_bf == -2 && cur->_bf == 1){RevolveLeftRight(fathernode);//左右双旋}else{assert(false);   //平衡因子出问题了}break;}}return true;}}

下面通过代码的细节来深入理解旋转

详解单旋

左单旋

完整代码如下

void RevolveLeft(node *& fathernode)//左单旋      
{node* cur = fathernode->_right; //父亲节点的右孩子fathernode->_right = cur->_left; //更改指向关系if (cur->_left != nullptr) //特殊情况cur->_left->_FatherNode = fathernode;//更改指向关系cur->_FatherNode = fathernode->_FatherNode;//更改指向关系if (fathernode->_FatherNode != nullptr) //为空是特殊情况,{if (fathernode->_FatherNode->_right == fathernode){fathernode->_FatherNode->_right = cur;//更改指向关系}else{fathernode->_FatherNode->_left = cur;//更改指向关系}}cur->_left = fathernode;//更改指向关系fathernode->_FatherNode = cur;//更改指向关系fathernode->_bf = 0; //更新平衡因子cur->_bf = 0;}

处理指向关系时,一定不要忘了更改父亲的指向关系

经过左单旋之后,父亲节点和右孩子节点的平衡因子都为 0 ,可参考上文图示。

特殊情况如下,如果不处理特殊情况,程序很容易崩溃

右单旋

void RevolveRight(node *& fathernode) //右单旋
{node* cur = fathernode->_left; //父亲节点的左节点fathernode->_left = cur->_right;//更新指向关系if (cur->_right != nullptr) //特殊情况cur->_right->_FatherNode = fathernode;//更新指向关系cur->_FatherNode = fathernode->_FatherNode;//更新指向关系if (fathernode->_FatherNode != nullptr)//特殊情况{if (fathernode->_FatherNode->_right == fathernode){fathernode->_FatherNode->_right = cur;//更新指向关系}else{fathernode->_FatherNode->_left = cur;//更新指向关系}}cur->_right = fathernode;//更新指向关系fathernode->_FatherNode = cur;//更新指向关系fathernode->_bf = 0;//更新平衡因子cur->_bf = 0;
}

详解双旋

左右双旋

左右双旋只需复用左单旋和右单旋即可,但平衡因子的更新却比较麻烦

完整代码如下

	void RevolveLeftRight(node *& fathernode)//左右双旋    {node* fathernodeL = fathernode->_left; //父亲节点的左孩子节点node* fathernodeLR = fathernodeL->_right;//父亲节点的左孩子节点的右孩子节点int bf = fathernodeLR->_bf; //保存平衡因子,实际是为了判断是插入了fathernodeLR左边还是右边还是fathernodeLR本身插入RevolveLeft(fathernodeL);RevolveRight(fathernode);//更新平衡因子if (bf == 0){fathernode->_bf = 0;fathernodeL->_bf = 0;fathernodeLR->_bf = 0;}else if (bf == -1){fathernode->_bf = 1;fathernodeL->_bf = 0;fathernodeLR->_bf = 0;}else if (bf == 1){fathernodeL->_bf = -1;fathernode = 0;fathernodeLR = 0;}else{assert(false);}}

平衡因子情况如下

右左双旋

完整代码如下

	void RevolveRightLeft(node *& fathernode) //右左双旋 {node* fathernodeR = fathernode->_right; node* fathernodeRL = fathernodeR->_left;int bf = fathernodeRL->_bf;RevolveRight(fathernodeR);RevolveLeft(fathernode);if (bf == 0){fathernode->_bf = 0;fathernodeR->_bf = 0;fathernodeRL->_bf = 0;}else if (bf == 1){fathernode->_bf = -1;fathernodeR->_bf = 0;fathernodeRL->_bf = 0;}else if (bf == -1){fathernodeR->_bf = 1;fathernode->_bf = 0;fathernodeRL->_bf = 0;}else{assert(false); }}

平衡因子情况如下

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

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

相关文章

宿舍管理系统代码详解(操作界面)

目录 一、前端代码 1.样式展示 2.代码详解 <1>主页面列表部分 &#xff08;1&#xff09;template部分 &#xff08;2&#xff09;script部分 <2>新增页面 &#xff08;1&#xff09;template部分 &#xff08;2&#xff09;script部分 <3>修改页面…

【头歌】计算机网络DHCP服务器配置第四关配置路由器子接口答案

头歌计算机网络DHCP服务器配置第四关配置路由器子接口操作步骤 任务描述 本关任务&#xff1a;配置路由器的子接口。 操作要求 在第一关的拓扑图的基础上&#xff0c;配置路由器及 PC 机&#xff0c;具体要求如下&#xff1a; 1、打开路由器物理接口 F0/0 &#xff1b; 2、配置…

设计模式——概述

1.设计模式定义 ​ 设计模式是软件设计中常见问题的典型解决方案,可用于解决代码中反复出现的设计问题。设计模式的出现可以让我们站在前人的肩膀上&#xff0c;通过一些成熟的设计方案来指导新项目的开发和设计&#xff0c;以便于我们开发出具有更好的灵活性和可扩展性&#…

港口与航运3D三维虚拟仿真展区让更多人了解到海洋知识

在短短20天内&#xff0c;搭建起200多家线上3D展厅&#xff0c;听起来似乎是一项艰巨的任务。然而&#xff0c;对于我们的3d云展平台而言&#xff0c;这早已成为常态。连续三年&#xff0c;我们已成功为众多会展公司在短时间内构建出几百家甚至上千家的线上3D展会&#xff0c;见…

通过date命令给日志文件添加日期

一、背景 服务的日志没有使用日志工具&#xff0c;每次重启后生成新日志文件名称相同&#xff0c;新日志将会把旧日志文件冲掉&#xff0c;旧日志无法保留。 为避免因旧日志丢失导致无法定位问题&#xff0c;所以需要保证每次生成的日志文件名称不同。 二、解决 在启动时&am…

使用uniapp编写的微信小程序进行分包

简介&#xff1a; 由于小程序发布的时候每个包最多只能放置2MB的东西&#xff0c;所以把所有的代码资源都放置在一个主包当中不显示&#xff0c;所以就需要进行合理分包&#xff0c;&#xff0c;但是分包后整个小程序最终不能超过20MB。 一般情况下&#xff0c;我习惯将tabba…

牛客NC362 字典序排列【中等 DFS Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/de49cf70277048518314fbdcaba9b42c 解题方法 DFS&#xff0c;剪枝Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回…

Go 错误日志处理

是不是所有的 if err ! nil 的地方都应该输出错误日志&#xff1f; 打印过多的错误日志会导致日志文件变得冗长和难以阅读。 其次&#xff0c;重复的错误信息会增加冗余。 此外&#xff0c;每一层都打印错误日志&#xff0c;一旦错误信息设计不当&#xff0c;可能会导致上下…

vite+js配置

vite js 配置路径 npm install types/node --save-dev vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue //需要引入 import path from path// https://vitejs.dev/config/ export default defineConfig({plugins: [vue()],resolve: {a…

Java | Leetcode Java题解之第118题杨辉三角

题目&#xff1a; 题解&#xff1a; class Solution {public List<List<Integer>> generate(int numRows) {List<List<Integer>> ret new ArrayList<List<Integer>>();for (int i 0; i < numRows; i) {List<Integer> row new…

windows ollama 指定模型下载路径

为Ollama指定模型的下载路径 在Windows系统中&#xff0c;如果想为Ollama指定模型的下载路径&#xff0c;可以通过设置环境变量来实现。以下是详细的步骤&#xff1a; 确定默认下载路径&#xff1a; 默认情况下&#xff0c;Ollama的模型可能会下载到C:\Users\<用户名>…

中心入侵渗透

问题1. windows登录的明文密码&#xff0c;存储过程是怎么样的&#xff1f;密文存在哪个文件下&#xff1f;该文件是否可以打开&#xff0c;并且查看到密文&#xff1f; 回答&#xff1a; Windows登录的明文密码的存储过程是&#xff1a; 当用户尝试登录Windows时&#xff0…

VCRUNTIME140_1.dll丢失是怎么回事?vcruntime140_1.dll无法继续执行代码的处理方法

VCRUNTIME140_1.dll丢失是怎么回事&#xff1f;问出这样的问题的人&#xff0c;一般是遇到vcruntime140_1.dll无法继续执行代码的问题了&#xff0c;找不到VCRUNTIME140_1.dll文件&#xff0c;那么程序就肯定是启动不了的&#xff0c;程序的启动是需要VCRUNTIME140_1.dll文件的…

【Mybatis】映射文件中获取单个参数和多个参数的写法

xml的映射文件中获取接口方法中传来的参数是直接用#{}的方式来获取的 那么接下来&#xff0c;我们就具体来说一下获取参数里边的各种规则和用法 1.单个参数&#xff0c;比如上面的getOneUser&#xff0c;只有一个id值作为参数 Mybatis对于只有一个参数的情况下&#xff0c;不…

unity回到低版本报错解决

用高版本2022打开过后的再回到2020就报了一个错。 报错如下&#xff1a; Library\PackageCache\com.unity.ai.navigation1.1.5\Runtime\NavMeshSurface.cs 看了一下是Library&#xff0c;然后我删除了整个Library文件夹&#xff0c;重启启动生成Library&#xff0c;然后还是…

基于BP神经网络和小波变换特征提取的烟草香型分类算法matlab仿真,分为浓香型,清香型和中间香型

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ...................................................................................... …

学至乎没而后止也

开场白 学至后没而后止也这个题目的原话来自与荀子《劝学》。大家知道什么意思吗&#xff1f;学习要学到你人没了&#xff0c;才算停止了。通俗点说就是只要没学死就往死里学&#xff0c;高雅点说就是要保持终身学习。 在以前说终身学习好像是一种良好习惯或品德&#xff0c;…

系统思考—决策

风险来自于你不知道你在做什么。——沃伦巴菲特 今天和一个合作伙伴的创始人交流&#xff0c;她提出了一个引人深思的问题&#xff1a;“策略性陪伴和战略复盘&#xff0c;什么原因不由客户自己来做&#xff1f;”这个问题让我深入思考了第三方策略性陪伴顾问的独特价值和重要…

推荐几款新手学习编程的网站

免费在线开发平台 介绍一款编程平台&#xff0c;专为学生和开发者量身打造&#xff01;平台拥有近4000道编程题目&#xff0c;支持多种编程语言&#xff08;包括C、C、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3和C#&#xff09;&#xff0c;为您提供全面的学…

Python在忘mysql密码后该如何重新连mysql

步骤一 先到mysql的bin目录下 步骤二 用mysqld delete mysql 把之前的库删了 步骤三 通过管理员模式进去后 用命令mysqld --skip-grant-tables越过验证 再输入mysql -u root 直达账户 步骤四 用FLUSH PRIVILEGES; ALTER USER rootlocalhost IDENTIFIED BY new_password; 指…