<C++> AVLTree

目录

1. AVL概念

2. AVL树节点的定义

3. AVL树的插入

4. AVL树的旋转

5. AVL树的验证

6. AVL树的删除

7. AVL树的性能

暴力搜索、二分搜索、二叉搜索树、二叉平衡搜索树(AVL、红黑树)、多叉平衡搜索树(B树)、哈希表 

1. AVL概念

        二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下

        因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

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

  • 它的左右子树都是AVL树
  • 左右子树高度之差 (简称平衡因子的绝对值不超过1 (-1 / 0 / 1) ,不是绝对的0是因为做不到绝对的平衡(例如,非满二叉树)

平衡因子:右子树高度 - 左子树高度

        如果一棵二叉搜索树是高度平衡的,它就是AVL 树。如果它有 n 个结点,其高度可保持在
,搜索时 间复杂度 O( log N  )
        
        满二叉树:2^h - 1 = N
        
                                                        -> 约等于logN        
        
        AVL树:2^h - X = N        (X范围 [ 1, 2^(h - 1) - 1 ] 

2. AVL树节点的定义

        直接写key—value版本,因为它与单key版本,只是多了一个对应value(pair就解决了),可以看二叉搜索树一文再理解两个版本的关系

        加入平衡因子会更加方便

template<class K, class V>
struct AVLTreeNode
{int _bf;    //balance factorpair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;AVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};

3. AVL树的插入

        AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式找到合适点插入新节点

  2. 调整节点的平衡因子

  • 新增在左边,parent平衡因子--
  • 新增在右边,parent平衡因子++
  • 如果插入后,节点的平衡因子变成了0,那么说明这个节点的左右子树高度平衡了,高度没有增大或减小,所以不会对往上的祖先产生影响,就不需要往上更新祖先的平衡因子
  • 如果插入后,节点的平衡因子变成了1 或 -1,那么说明这个节点的左右子树高度不平衡,高度增大了或减小了,会对其祖先的平衡因子产生影响,所以使用 parent 指针向上更新平衡因子(这就体现了 parent 指针的用武之地),直到某一个祖先的平衡因子变为 0 或根节点就停止!!!(因为如果一个节点的高度没变,即平衡因子为0,那么它就不影响parent节点)
  • 如果插入后,节点的平衡因子变成了2 或 -2,说明parent所在的子树的高度变化且不平衡了!这时就要体现 AVL 平衡的特点,对 parent 所在子树进行旋转
  • 不会出现3 或 -3以上情况,因为它们必然会经历2 或 -2的情况,那时我们早就已经处理完了
bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// ... 控制平衡// 更新平衡因子while (parent){if (cur == parent->_left){parent->_bf--;}else // if (cur == parent->_right){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){RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}break;}else  //正常代码不会执行到这里,所以抛异常{assert(false);}}return true;}

4. AVL树的旋转

        如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

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

        cur 的 left 链接到 parent 的 right,再将 parent 链接到 cur 的 left

        在操作时,不仅要更新_bf平衡因子,还要额外注意父节点指针,一不留心就会出现三叉链,而且还要注意,parent 是不是 root,如果不是根节点,那么还要提前记录 parent 的 parent,以便使cur 的 parent 指向修改

符合左单旋的场景无穷尽,下图直接概括了左单旋的抽象总结图: 

         但是无论h高度是多少,节点的操作是一样的

//左单旋void RotateL(Node* parent){++_rotateCount;Node* cur = parent->_right;Node* curleft = cur->_left;//开始链,注意父亲链parent->_right = curleft;if (curleft)	//这里判断cur的right是不是为空,为空的话就不能使用指针链接{curleft->_parent = parent;}cur->_left = parent;Node* ppnode = parent->_parent;parent->_parent = cur;//判断parent的父亲的情况if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}//记得父亲链cur->_parent = ppnode;}//不要忘了平衡因子parent->_bf = cur->_bf = 0;}

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

        右单旋与左单旋完全对称

简单分析后即可总结出上图:

	//右单旋void RotateR(Node* parent){++_rotateCount;Node* cur = parent->_left;Node* curright = cur->_right;//开始链,注意父亲链parent->_left = curright;if (curright)	//这里判断cur的right是不是为空,为空的话就不能使用指针链接{curright->_parent = parent;}Node* ppnode = parent->_parent;cur->_right = parent;parent->_parent = cur;//判断parent的父亲的情况if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}//记得父亲链cur->_parent = ppnode;}//不要忘了平衡因子parent->_bf = cur->_bf = 0;}

新节点插入较高右子树的左侧--- 右左:先右单旋再左单旋
     上面的两种情况,都是极端的左或右,新增节点与祖先处于一条直线。而双旋的情况更复杂,新增节点与祖先节点的连线是一条折线
双旋本质相当于第一次的旋转是预处理:将树修正为左单旋情况或右单旋情况,第二次就是简单的单旋情况
       
        那么只需复用上面的左右单旋函数即可解决问题,但是复用两个函数后,30、90、60这三个节点的平衡因子都会成为0!这是不正确的,因为h大小分情况,并且在60的左右插入位置不同,导致结果的平衡因子也不同。
分类讨论:
所以对于不同情况,要进行分类的处理平衡因子
	//右左双旋void RotateRL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;RotateR(parent->_right);RotateL(parent);//分情况更新平衡因子if (bf == 0){cur->_bf = 0;curleft->_bf = 0;parent->_bf = 0;}else if (bf == 1){cur->_bf = 0;curleft->_bf = 0;parent->_bf = -1;}else if (bf == -1){cur->_bf = 1;curleft->_bf = 0;parent->_bf = 0;}else{assert(false);}}

        代码解析:在插入一个节点之后,向上更新平衡因子,直到遇见以上四种情况,开始旋转处理

新节点插入较高左子树的右侧 --- 左右:先左单旋再右单旋
        同理
	//左右双旋void RotateLR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateL(parent->_left);RotateR(parent);//分情况更新平衡因子if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}}
        如果程序出错了,数据量很大调试就很难受了,这时候可以一段一段注释调试,对于不同场景可能不合适,所以还可以手写一个判断函数,让计算机协助你找到错误,打开监视窗口、打断点,要带着思路、有预测结果的去调试,中间可以使用条件断点(可以手写if,里面写一些语句,程序走到这里时停下)

总结:

        假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR,当pSubR的平衡因子为1时,执行左单旋;当pSubR的平衡因子为-1时,执行右左双旋
  2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL,当pSubL的平衡因子为-1是,执行右单旋;当pSubL的平衡因子为1时,执行左右双旋

旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。

5. AVL树的验证

	//递归求高度,测试代码是否正确int Height(){return Height(_root);}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;}//测试代码是否正确,是不是AVL结构bool IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr)return true;int leftHight = Height(root->_left);int rightHight = Height(root->_right);if (rightHight - leftHight != root->_bf){cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;return false;}return abs(rightHight - leftHight) < 2&& IsBalance(root->_left)&& IsBalance(root->_right);}

我们使用随机生成的数来测试 

#include "AVLTree.h"
#include <vector>const int N = 1000000;int main()
{AVLTree<int, int> at;vector<int> v;v.reserve(N);srand(time(0));for (int i = 0; i < N; i++){v.push_back(rand());}for (auto e : v){at.Insert(make_pair(e, e));}cout << at.IsBalance() << endl;return 0;
}

6. AVL树的删除

        按搜索树的规则查找节点进行删除,与插入时对节点的平衡因子更新相反,如果删除节点之后 parent 平衡因子为0,那么需要继续往上更新,如果是 1 或 -1,那么不需要往上更新

        而且删除时,双旋情况更多,平衡因子的更新更复杂

7. AVL树的性能

        AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 logN 但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

        因此,如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合

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

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

相关文章

【C++ Primer Plus习题】7.2

问题: 解答: #include <iostream> using namespace std;#define MAX 10int input(float* grade, int len) {int i 0;for (i 0; i < len; i){cout << "请输入第" << i 1 << "个高尔夫成绩(按0结束):";cin >> grade[i]…

更改了ip地址怎么改回来

在日常的网络使用中&#xff0c;‌我们有时会因为特定的需求更改设备的IP地址&#xff0c;‌比如解决IP冲突、‌访问特定网络资源或进行网络测试等。‌然而&#xff0c;‌更改IP地址后&#xff0c;‌我们可能又因为某些原因需要将IP地址改回原来的设置。‌本文将详细介绍如何改…

视频号单场直播GMV超500万!开学季助力品牌高效转化

开学在即&#xff0c;友望数据发现&#xff0c;不少学习机、学练机、智能机器人、词典笔等学习相关的电子教育产品开始畅销 ▲ 图片来源&#xff1a;友望数据-商品排行榜 新学年开始&#xff0c;家长们又要为孩子新的学业操碎心&#xff0c;而教育培训商家也在开学季迎来了他们…

PS如何抠人像图--5步实现完美抠图

1、菜单栏--选择--选择主体 2、菜单栏--选择--选择并遮住 3、选择原图--右下角添加纯色背景 4、文件--导出--导出为png图片 5、原图与抠图效果对比 相关参考视频&#xff1a; 【ps教程】揭秘PS抠头发&#xff0c;这才是真正的教学&#xff0c;快收藏吧_哔哩哔哩_bilibili 一分…

挂载5T大容量外接硬盘到ubuntu

挂载5T大容量外接硬盘到ubuntu S1&#xff1a;查看硬盘 使用 $ sudo fdisk -l找到对应盘&#xff0c;例如下图所示 /dev/sdc S2: 创建分区 使用 $ sudo fdisk /dev/sdc对上硬盘进行创建分区&#xff1b;可以依次使用以下指令 m &#xff1a;查看命令&#xff1b; g &…

从开题到答辩:ChatGPT超全提示词分享!(下)【建议收藏】

数据收集 1. "请帮我找出关于如何收集【研究领域】社交媒体数据进行消费者行为研究的五篇指导性文章&#xff0c;并概述它们的主要方法论摘要。" 2. "我需要对【特定领域】市场的消费者偏好进行调查。能否提供一份包含调查问卷设计原则和示例的草稿&#xff1f;…

cola_os学习笔记(下)

cola_os学习笔记&#xff08;上&#xff09; os文件夹 cola_device.c ​ .h放在.c的同层级。作者采用了字符设备注册的方式&#xff0c;在.h中可以看到设备属性。也就是把LED这些设备抽象&#xff0c;外面传入"LED1"这样的参数&#xff0c;使我联想到java的new一个…

编译错误cc:not found总结

一、错误 cc: not found 系统无法找到名为cc的编译器。 注&#xff1a;在大多数Linux系统中&#xff0c;cc通常是C编译器的链接&#xff08;link&#xff09;或别名&#xff0c;它通常指向gcc&#xff08;GNU Compiler Collection&#xff09;或其他C编译器。 二、可能导致…

「OC」CAlayer——巧用动画实现一个丝滑的折叠cell

「OC」CAlayer——巧用动画实现一个丝滑的折叠cell 前言 在这个暑假集训后的时间&#xff0c;都在家里做着学习笔记的整理&#xff0c;深入学习了CALayer的相关知识&#xff0c;掌握了第三方库Masonry自动布局的用法&#xff0c;以及学习了MVC的相关内容&#xff0c;正好组内…

chapter08-面向对象编程——(Object类详解)——day09

目录 319-运算符 320-查看Jdk源码 321-子类重写equals 322-equals课堂练习1 323-equals重写练习2 324-equals重写练习3 325-hashCode 326-toString 327-finalize 319-运算符 引用的都是同一个地址&#xff0c;所以返回true 320-查看Jdk源码 equals只能判断引用类型是…

艾体宝干货丨Redis与MongoDB的区别

Redis&#xff08;Remote Dictionary Server&#xff0c;远程字典服务器&#xff09;和 MongoDB 是两类知名的 NoSQL数据库&#xff0c;其以非结构化的方式存储数据。与传统关系数据库使用表格、行和列来组织数据不同&#xff0c;NoSQL数据库采用了不同的数据存储模型。Redis是…

探索极速Python:Sanic框架的魔力

文章目录 探索极速Python&#xff1a;Sanic框架的魔力背景&#xff1a;为什么选择Sanic&#xff1f;Sanic是什么&#xff1f;如何安装Sanic&#xff1f;简单的库函数使用方法场景应用示例常见Bug及解决方案总结 探索极速Python&#xff1a;Sanic框架的魔力 背景&#xff1a;为什…

【位置编码】【Positional Encoding】直观理解位置编码!把位置编码想象成秒针!

【位置编码】【Positional Encoding】直观理解位置编码&#xff01;把位置编码想象成秒针&#xff01; 你们有没有好奇过为啥位置编码非得长成这样&#xff1a; P E ( p o s , 2 i ) s i n ( p o s 1000 0 2 i / d m o d e l ) P E ( p o s , 2 i 1 ) c o s ( p o s 1000 …

AcWing895. 最长上升子序列

这个代码不知道怎么说&#xff0c;反正就是对着代码手算一次就懂了&#xff0c;无需多言&#xff0c;就是俩for循环里面的第二层for的循环条件是j<i,j是从下标1往下标i-1遍历的&#xff0c;每次a【j】<a【i】就在答案数组f【i】上面做出更新。基本的输入样例已经可以覆盖…

红黑树刨析(删除部分)

文章目录 红黑树删除节点情景分析情景1&#xff1a;删除节点左右子树都为空情景1.1&#xff1a;删除节点为红色情景1.2&#xff1a;删除节点为黑色情况1.2.1&#xff1a;删除节点的兄弟节点是红色情景1.2.2&#xff1a;删除节点的兄弟节点是黑色情景1.2.2.1&#xff1a;删除节点…

Cpp学习手册-基础学习

首先你要去网上下载对应的运行软件&#xff0c;先把对应的 C 环境配置好&#xff0c;配置好了我们就可以开始我们的C 学习之旅了。希望通过学习我们能够成为一个比较不错的 C 开发工程师。我也会持续更新 C 知识。 1. C语法基础 当我通过 CLion 工具创建了一个新的 Project 。…

Redis中的 大/热 key问题 ,如何解决(面试版)

big key 什么是 big key? big key&#xff1a;就是指一个内存空间占用比较大的键(Key) 造成的问题&#xff1a; 内存分布不均。在集群模式下&#xff0c;不同 slot分配到不同实例中&#xff0c;如果大 key 都映射到一个实例&#xff0c;则分布不均&#xff0c;查询效率也…

自建电商网站整合Refersion教程

前言&#xff1a;   先介绍一下Refersion有啥用&#xff0c;如果你有一个自己的跨境电商独立站点&#xff0c;想找一些网红帮忙推广销售自己的商品&#xff0c;然后按照转化订单比例给网红支付佣金&#xff0c;这件事情对双方来说透明性和实时性很重要&#xff0c;Refersion就…

C++ | Leetcode C++题解之第382题链表随机节点

题目&#xff1a; 题解&#xff1a; class Solution {ListNode *head;public:Solution(ListNode *head) {this->head head;}int getRandom() {int i 1, ans 0;for (auto node head; node; node node->next) {if (rand() % i 0) { // 1/i 的概率选中&#xff08;替…

Unity URPShader支持多光源处理

//声明变体并且引用文件 #pragma shader_feature _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" //在数据结构体中声明需要使用的数据 struct Attributes {float4 posit…