【C++技能树】手撕AVL树 --插入与旋转详解

在这里插入图片描述
Halo,这里是Ppeua。平时主要更新C++,数据结构算法,Linux与ROS…感兴趣就关注我bua!

文章目录

  • 0.平衡搜索二叉树概念
    • 0.1 平衡因子
  • 1.插入
    • 1.1 普通插入操作
    • 1.2更新平衡因子
  • 2.旋转
    • 2.1 左单旋
    • 2.2 右单旋
    • 2.3 右左双旋
    • 2.4 左右双旋
  • 3. 旋转判定
  • 4. 验证是否为AVL树
  • 5.完整源码(AVL插入旋转)

在这里插入图片描述

0.平衡搜索二叉树概念

为什么在会了搜索二叉树之后,还需要学习平衡搜索二叉树呢?在搜索二叉树部分,并没有要求树保持平衡,仅需遵循左小右大即可.

若往树中插入有序或者接近有序的值,会出现下面这种情况,退化为单支树.

f5e6a0c761ccc48519e7d23748aaeea

这样的搜索树就完全没有效率可言,他的时间复杂度为o(N).所以我们需要一棵正常(平衡)的树来解决这个问题.

否则当map和set用这样的树岂不是效率非常的低,所以这就是平衡二叉搜索树的意义.

将子树的左右平衡高度差维持在(-1/0/1)之间,若超过了这个范围,则对树进行高度调整,从而减少搜索长度.

上面的那棵树可以变化成这个样子,这样搜索的效率就变成了o(logN),大大提高了效率.

52a0ba283e5ccbecd31c8b27f781b32

这同样是一棵满二叉树.其节点个数为 2 h − 1 2^h-1 2h1,可以将其看作一颗特殊的平衡二叉搜索树.

所以平衡二叉搜索树的节点个数的公式为: 2 h − X 2^h-X 2hX,X的范围介于[1, 2 ( h − 1 ) − 1 2^{(h-1)}-1 2(h1)1]之间.

这里可以这样理解.二叉树的最大深度差为1,此时的最后一层最差的情况为只有一个节点,那么缺少的节点数为前面所有层的节点.

忽略掉常数,可以看到h可以近似等于logN.

0.1 平衡因子

上文提到,二叉搜索树需要计算其平衡因子来稳定树的平衡度.那么平衡因子怎么算呢?

左子树的最大高度为负,右子树的最大高度为正.两值相加,体现在根上的即为这棵树的平衡因子

72d1ee6e5abfb6956df96f1d6a2bf42

因为其对树的结构调整,需要大量访问parent,也就是子树的根,所以在定义这棵树的时候.我们直接将其parent存起来,方便后续查找.

template<typename K,typename V>
struct TreeNode
{	pair<K, V>_val;TreeNode<K,V>* _left;TreeNode<K,V>* _right;TreeNode<K,V>* _parent;int _bf;TreeNode(const pair<K,V> val):_val(val),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};

1.插入

我们先写一个最简单的二叉树插入操作,然后在上面加上平衡因子的更新与旋转即可.

1.1 普通插入操作

bool Insert(const pair<K, V> kv){Node* parent = _root;Node* cur = _root;if (_root == nullptr){_root = new Node(kv);}while (cur){if (kv.first > cur->_val.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_val.first){parent = cur;cur = cur->_left;}else return false;;}cur = new Node(kv);if (parent->_val.first > kv.first){parent->_left = cur;}elseparent->_right = cur;cur->_parent = parent;}

1.2更新平衡因子

当我们插入一个新的节点的时候,**新增在左则平衡因子减减,新增在右则平衡因子加加.**此时有以下这几种情况:

  1. 当更新完平衡因子,parent的平衡因子为0时,说明并没有引起这棵子树的高度变化.则不需要继续向上更新平衡因子

    d1daeeb2b4993bb965ff2d28091bd8a

  2. 当更新完平衡因子,parent的平衡因子为-1/1时,说明引起了这棵树的高度变化,需要继续更新其祖先的平衡因子

    e5a850f2ac5487b61031878fcdeb83a

  3. 当更新完平衡因子,parent的平衡因子为-2/2时,说明这棵树需要进行旋转来维持平衡

    e5a850f2ac5487b61

​ 关于旋转我们稍后再说,我们在代码里加上更新平衡因子的这几步操作.

while (parent)
{if (parent->_left == cur){parent->_bf--;}else parent->_bf++;if (parent->_bf == 0)break;else if (parent->_bf == -1 || parent->_bf == 1){cur = parent;parent=parent->_parent;}else if (parent->_bf == -2 || parent->_bf == 2){//旋转}
}

2.旋转

旋转根据实际的情况,可以分为四种情况:左单旋,右单旋,左右单旋,右左单旋

旋转的目的是:在不破坏AVL的前提下,降低这棵树的高度,使其重新成为AVL树

2.1 左单旋

当parent节点的平衡因子为2时,需要进行左单旋来降低整棵树的高度.

99f00fa3799b2eb8388f04c146961d8

这是最简单的情况,即让parent->right=cur->left cur->left=parent

e51ff0434783fd26331ced235bc266a

当需要旋转的是一颗子树的时候,核心操作不变,但需要更新下parent 与 pparent的关系,将pparent指向cur

95086d741a40aaa01aa14227dee4ba0

所以左单旋的代码也可以写出来了

void RotateL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;if (curleft)curleft->_parent = parent;parent->_right = curleft;cur->_left = parent;Node* pparent = parent->_parent;parent->_parent = cur;if (_root == parent){cur->_parent = nullptr;_root = cur;}else{if (pparent->_left == parent){pparent->_left = cur;}else pparent->_right = cur;cur->_parent = pparent;}parent->_bf =cur->_bf= 0;
}

2.2 右单旋

0204c9ebc93ddd12b6a1fc6aa9e9904

38eb9e421d098c02a98833aeff6a835

其旋转核心为:**parent->left=cur->right cur->right=parent **

所以右旋转的代码为:

void RotateR(Node* parent)
{Node* cur = parent->_left;Node* curleft = cur->_left;if (curleft){curleft->_parent = parent;}parent->_left = curleft;if (parent == _root){cur->_left = parent;}else{Node* pparent = parent->_parent;if (pparent->_left = parent){pparent->_left = cur;}elsepparent->_right = cur;cur->_parent = pparent;}parent->_bf = cur->_bf = 0;parent->_parent = cur;}

2.3 右左双旋

其抽象模型为:

77c0d0a19775931dbca542aafecfe67

这个模型实在有点抽象,我们分以下几种情况来讨论.

当h0时,此时60为插入的节点(60->bf0)

deb3c319d89ed5b7e1b7ab7947f59f6

旋转完成后,parent->bf cur->bf均为0

当h==1时,此时可以分为在60的左边插入或者在60的右边插入

  1. 在左边插入(curleft->bf==-1)

1cc1e4b1c03dfbb74949479a83b359e

旋转完成后,parent->bf=0 cur->bf=1 curleft->bf=0

  1. 在右边插入(curleft->bf==1)

27bd9f72cfc3de5bd1d4600090a200b

旋转完成后,parent->bf=-1 cur->bf=0 curleft->bf=0

当h==2时,此时也可以分为在左边插入以及在右边插入

  1. 在左边插入(curleft->bf==-1)

    8d3fce21b6c729a3d6bcdc24db09a4c

旋转完成后,parent->bf=0 cur->bf=1 curleft->bf=0

  1. 在右边插入(curleft->bf==1)

02483a63e12eca2b1a06d74d751ac62

旋转完成后,parent->bf=-1 cur->bf=0 curleft->bf=0

可以看出,引发右左双旋的原因为:cur->bf==-1,parent->bf==2

所以 其代码可以复用之前的旋转,但之前的旋转改变了平衡因子,我们需要依照上面的规律再特殊处理一下

void RotaRL(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateR(parent);RotateL(parent->_left);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}if (bf == -1){parent->_bf = 0;cur->_bf = 1;curright->_bf = 0;}if (bf == 1){parent->_bf = -1;cur->_bf = 0;curright->_bf = 0;}
}

2.4 左右双旋

其抽象模型为:

ea71377250854e9e30ddcce8fc42f3b

这个模型实在有点抽象,我们分以下几种情况来讨论.

h0 此时60为插入的节点(curright->bf0)

d30d04a914ff74ea0017aadfec25d01

旋转完成后,parent->bf=cur->bf=curright->bf=0

当h==1时,此时可以分为在60的左边插入或者在60的右边插入

  1. 在左边插入(curright->bf==-1)

e0bd4568684f60c958743920758317e

旋转完成后,parent->bf=1 cur->bf=0 curleft->bf=0

  1. 在右边插入(curright->bf==1)

960b1d4215886847c0cebaf7d0d44ce

旋转完成后,parent->bf=0 cur->bf=-1 curright->bf=0

当h==2时,此时也可以分为在左边插入以及在右边插入

  1. 在左边插入(curright->bf==-1)

    a02e3047b7bf065be4859070dab9b4b

旋转完成后,parent->bf=1 cur->bf=0 curright->bf=0

  1. 在右边插入(60->bf==1)

79eeeda5389aad2f9cd95dbc7796ccb

旋转完成后,parent->bf=0 cur->bf=-1 curright->bf=0

可以看出,引发左右双旋的原因为:cur->bf1,parent->bf-2

void RotaLR(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;}if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}
}

3. 旋转判定

旋转的判定为:同号单旋,异号双旋(将cur旋转为parent同号)

if (parent->_bf == -2 || parent->_bf == 2)
{if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotaRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotaLR(parent);}break;
}

4. 验证是否为AVL树

bool IsAVLTree()
{return IsAVLTree(_root);
}
bool IsAVLTree(Node* root)
{if (root == nullptr)return true;int leftheight = Height(root->_left);int rightheight= Height(root->_right);if (rightheight - leftheight != root->_bf){cout << "异常" << rightheight<< " " << leftheight<< " "<<root->_bf<<endl;return false;}return abs(rightheight - leftheight) < 2 && IsAVLTree(root->_left) && IsAVLTree(root->_right);
}
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;
}

5.完整源码(AVL插入旋转)

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
template<typename K,typename V>
struct TreeNode
{	pair<K, V>_val;TreeNode<K,V>* _left;TreeNode<K,V>* _right;TreeNode<K,V>* _parent;int _bf;TreeNode(const pair<K,V> val):_val(val),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};
template<typename K,typename V>
class AVLBSTree {typedef TreeNode<K,V> Node;
public:bool Insert(const pair<K, V> kv){Node* parent = _root;Node* cur = _root;if (_root == nullptr){_root = new Node(kv);return true;}while (cur){if (kv.first > cur->_val.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_val.first){parent = cur;cur = cur->_left;}else return false;;}cur = new Node(kv);if (parent->_val.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)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){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotaRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotaLR(parent);}break;}else assert(false);}     return true;}void RotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;if (curleft)curleft->_parent = parent;parent->_right = curleft;cur->_left = parent;Node* pparent = parent->_parent;parent->_parent = cur;if (_root == parent){cur->_parent = nullptr;_root = cur;}else{if (pparent->_left == parent){pparent->_left = cur;}else pparent->_right = cur;cur->_parent = pparent;}parent->_bf =cur->_bf= 0;}void RotateR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;if (curright){curright->_parent = parent;}parent->_left = curright;cur->_right = parent;if (parent == _root){cur->_left = parent;}else{Node* pparent = parent->_parent;if (pparent->_left == parent){pparent->_left = cur;}elsepparent->_right = cur;cur->_parent = pparent;}parent->_bf = cur->_bf = 0;parent->_parent = cur;}void RotaRL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curleft->_bf = 0;}if (bf == -1){parent->_bf = 0;cur->_bf = 1;curleft->_bf = 0;}if (bf == 1){parent->_bf = -1;cur->_bf = 0;curleft->_bf = 0;}}void RotaLR(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;}if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}}bool IsAVLTree(){return IsAVLTree(_root);}bool IsAVLTree(Node* root){if (root == nullptr)return true;int leftheight = Height(root->_left);int rightheight= Height(root->_right);if (rightheight - leftheight != root->_bf){cout << "异常" << rightheight<< " " << leftheight<< " "<<root->_bf<<endl;return false;}return abs(rightheight - leftheight) < 2 && IsAVLTree(root->_left) && IsAVLTree(root->_right);}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;}
private:Node* _root = nullptr;
};

image-20230905164632777

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

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

相关文章

openGauss学习笔记-71 openGauss 数据库管理-创建和管理普通表-删除表中数据

文章目录 openGauss学习笔记-71 openGauss 数据库管理-创建和管理普通表-删除表中数据 openGauss学习笔记-71 openGauss 数据库管理-创建和管理普通表-删除表中数据 在使用表的过程中&#xff0c;可能会需要删除已过期的数据&#xff0c;删除数据必须从表中整行的删除。 SQL不…

Nodejs 第十六章(ffmpeg)

FFmpeg 是一个开源的跨平台多媒体处理工具&#xff0c;可以用于处理音频、视频和多媒体流。它提供了一组强大的命令行工具和库&#xff0c;可以进行视频转码、视频剪辑、音频提取、音视频合并、流媒体传输等操作。 FFmpeg 的主要功能和特性&#xff1a; 格式转换&#xff1a;…

还没用熟 TypeScript 社区已经开始抛弃了

根据 rich-harris-talks-sveltekit-and-whats-next-for-svelte 这篇文章的报道&#xff0c; Svelte 计划要把代码从 TS 换到 JS 了。 The team is switching the underlying code from TypeScript to JavaScript. That and the update will then allow the team to incorporate…

边缘计算AI智能安防监控视频平台车辆违停算法详解与应用

随着城市车辆保有量呈现高速增长趋势&#xff0c;交通拥堵、违章行为也日益泛滥。因为车辆未停放在指定区域导致的车位浪费、占用/堵塞交通要道、车辆剐蹭等问题层出不穷。通过人工进行违法停车的监控&#xff0c;不仅让监控人员工作负荷越来越大&#xff0c;而且存在发现不及时…

第18章_瑞萨MCU零基础入门系列教程之GPT

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

redis--windows配置--redis基础

写在前面&#xff1a; 文章目录 win安装配置密码配置服务服务已经存在 可视化工具运行类型基础类型 帮助文档命令通用命令string命令hashlistsetsortedset win安装 下载地址 然后一路next就可以了。 记得添加到环境变量 配置密码 在目录打开配置文件 搜索requirepass …

K8S:Yaml文件详解及编写示例

文章目录 一.Yaml文件详解1.Yaml文件格式2.YAML 语法格式 二.Yaml文件编写及相关概念1.查看 api 资源版本标签2.yaml编写案例&#xff08;1&#xff09;相关标签介绍&#xff08;2&#xff09;Deployment类型编写nginx服务&#xff08;3&#xff09;k8s集群中的port介绍&#x…

CS5817规格书|CS5817芯片参数|多功能便携式显示器方案芯片规格

CS5817支持最高4K 60Hz是集睿致远&#xff08;ASL&#xff09; 新推出的多功能显示控制器芯片&#xff0c;CS5817产品可应用于便携显示器、电竞显示器、桌面显示器、一体式台式机和嵌入式显示系统。 Type-C/DP/HDMI2.0输入转LVDS/eDP/VBO 芯片, 高度集成了多种输入输出接口, 并…

Vue的详细教程--入门

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Vue的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.Vue是什么 二. Vue的特点及优势 三.使用…

uniapp——实现在线选座功能——技能提升

首先声明一点&#xff1a;下面的内容是从一个uniapp的程序中摘录的&#xff0c;并非本人所写&#xff0c;先做记录&#xff0c;以免后续遇到相似需求抓耳挠腮。 这里写目录标题 效果图代码——html部分cu-custom组件anil-seat组件 代码——jscss部分 效果图 代码——html部分 …

【小沐学CAD】嵌入式UI开发工具:GL Studio

文章目录 1、简介2、软件功能3、应用行业3.1 航空3.2 汽车3.3 防御3.4 工业3.5 电力与能源3.6 医疗3.7 空间3.8 科技 结语 1、简介 https://disti.com/gl-studio/ DiSTI 是 HMI 软件、虚拟驾驶舱、仪表、信息娱乐、集群显示器和嵌入式 UI 解决方案的领先提供商。 而它的GL Stu…

芯片工程师求职题目之CPU篇(4)

1. 在组相联cache中&#xff0c;用于替换cache line的算法有哪些&#xff1f; LRU(Least Recently Used)算法&#xff1a;该算法会跟踪每个cache line的age(年龄)情况&#xff0c;并在需要时替换掉近期最少使用的cache line。MRU(Most Recently Used)算法&#xff1a;这与LRU相…

buuctf crypto 【密码学的心声】解题记录

1.打开可以看到一个曲谱 2.看到曲谱中的提示埃塞克码可以想到ascii码&#xff0c;没有八可以联想到八进制&#xff0c;而八进制又对应着三位的二进制&#xff0c;然后写个脚本就好了 oct [111,114,157,166,145,123,145,143,165,162,151,164,171,126,145,162,171,115,165,143,…

Nacos单机启动的两种方式

说明&#xff1a;直接双击nacos的启动脚本&#xff0c;默认是集群&#xff08;cluster&#xff09;的方式&#xff1b; 需要单机启动&#xff0c;有以下两种方式&#xff1b; 方式一&#xff1a;命令行 在当前目录打开命令窗口&#xff0c;输入以下命令启动nacos startup.…

Redis 高性能设计之epoll和IO多路复用深度解析

I/O多路复用模型是什么 I/O&#xff1a;网络I/O多路&#xff1a;多个客户端连接&#xff08;连接就是套接字描述符&#xff0c;即socket或者channel&#xff09;&#xff0c;指的是多条TCP连接复用&#xff1a;用一个进程来处理多条的连接&#xff0c;使用单进程就能的够实现同…

交叉编译工具链-Ubuntu 安装说明

交叉编译工具链-Ubuntu 安装说明 【实验目的】 了解交叉编译工具链的安装方法与使用方法 【实验环境】 1、 ubuntu 14.04 发行版 【注意事项】 1、实验步骤中以“$”开头的命令表示在 ubuntu 环境下执行 【实验步骤】 1、安装交叉编译工具链 在 ubuntu 下打开一个终端并进入到家…

JavaWeb 学习笔记 1:MyBatis

JavaWeb 学习笔记 1&#xff1a;MyBatis 以往都是在 Spring Boot 中整合 MyBatis 进行使用&#xff0c;本篇文章将展示如何在一个新的 Maven 项目中使用 MyBatis。 MyBatis 官方的入门教程可以作为本文的参考。 1.快速入门 1.1.导入表数据 执行包含测试数据的SQL文件&#x…

10 个不错的 C 语言开源项目

今天给大家分享10个超赞的C语言开源项目&#xff0c;希望这些内容能对大家有所帮助&#xff01; 01 Webbench Webbench是一个在 Linux 下使用的非常简单的网站压测工具。 它使用fork()模拟多个客户端同时访问我们设定的URL&#xff0c;测试网站在压力下工作的性能。 最多可以…

【办公自动化】用Python在Excel中查找并替换数据(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

【网络编程】TCP Socket编程

TCP Socket编程 1. ServerSocket2. Socket3. TCP的长短连接4. Socket 通信模型5. 代码示例&#xff1a;TCP 回显服务器 流套接字&#xff1a; 使用传输层TCP协议 TCP: 即Transmission Control Protocol&#xff08;传输控制协议&#xff09;&#xff0c;传输层协议。 TCP的特点…