C++_进阶:AVL树

文章目录

    • 1. AVL树的概念
    • 2. AVL树节点的定义
    • 3. AVL树的插入
    • 4. AVL树的旋转
      • 4.1 右单旋
      • 4.2 左单旋
      • 4.3 左右双旋
      • 4.4 右左双旋
    • 5.AVL树的验证
    • 6. AVL树模拟实现

在这里插入图片描述

1. AVL树的概念

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

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

📋一棵AVL树是具有以下性质的二叉搜索树

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

在这里插入图片描述

🔅如果一棵二叉搜索树是高度平衡(满足以上两个条件)的,它就是AVL树。如果它有n个结点,其高度可保持在 O(logn) , 搜索时间复杂度O(logn)

2. AVL树节点的定义

AVL的节点在原来二叉搜索树的节点上,增加了两个成员:父节点指针变量,以及平衡因子变量

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

3. AVL树的插入

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

  • 按照二叉搜索树的方式插入新节点
  • 调整节点的平衡因子

🔅更新平衡因子规则:
1.📝 插入的是左子树的节点,平衡因子- -,插入的是右子树的节点,平衡因子++
在这里插入图片描述
2. 📝调节后的平衡因子为1/-1,0,2/-2 往下需要做的是不同的。调节后的平衡因子为0时,停止向上更新平衡因子

在这里插入图片描述
调节后的平衡因子为1/-1时,继续向上调整平衡因子

在这里插入图片描述
调节后的平衡因子为2/-2时,就要调整树了,并且不再向上调整平衡因子
在这里插入图片描述
在这里插入图片描述
代码示例:

//接收一个 键值对pair类型 作为插入的值
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--;elseparent->_bf++;// 更新后检测双亲的平衡因子//如果平衡因子为0,停止向上更新平衡因子if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){//调节后的平衡因子为1/-1时,继续向上调整平衡因子// 插入前双亲的平衡因子是0,插入后双亲的平衡因为为1 或者 -1 ,说明以双亲为根的二叉树// 的高度增加了一层,因此需要继续向上调整cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){// 调节后的平衡因子为2/-2时,就要调整树了,并且不再向上调整平衡因子// 双亲的平衡因子为2/-2,违反了AVL树的平衡性,需要对以pParent// 为根的树进行旋转处理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 {RotateLR(parent);}break;}else{assert(false);}}return true;
}

4. AVL树的旋转

🔅AVL树之所以能保持平衡,原因在于AVL树会 旋转,上面Insert代码中如果平衡因子== 2/-2时,RotateL,RotateR,RotateRL,RotateLR就是调整该树的函数。

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

4.1 右单旋

1️⃣ 📝新节点插入较高左子树的左侧—左左:右单旋在这里插入图片描述
🔅最终根据情况重新设定平衡因子:cur的平衡因子变为0,parent的平衡因子变为0。

void  RotateR(Node* parent)
{// subL: parent的左孩子// subLR: Parent左孩子的右孩子Node* subL = parent->_left;Node* subLR = subL->_right;// 旋转完成之后,10的右孩子作为父亲的左孩子parent->_left = subLR;// 如果10的左孩子的右孩子存在,更新亲双亲if (subLR)subLR->_parent = parent;// 因为30可能是棵子树,因此在更新其双亲前必须先保存30的双亲Node* parentParent = parent->_parent;// 30作为 10的右孩子subL->_right = parent;// 更新30的双亲parent->_parent = subL;// 如果30是根节点,根新指向根节点的指针if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{	// 如果30是子树,可能是其双亲的左子树,也可能是右子树if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}// 根据调整后的结构更新部分节点的平衡因子parent->_bf = subL->_bf = 0;
}

4.2 左单旋

2️⃣ 📝新节点插入较高右子树的右侧—右右:左单旋
在这里插入图片描述
🔅最终根据情况重新设定平衡因子:cur的平衡因子变为0,parent的平衡因子变为0。

//与右旋逻辑大体一致
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;
}

4.3 左右双旋

3.📝 新节点插入较高左子树的右侧—左右:先左单旋再右单旋
在这里插入图片描述

在这里插入图片描述
🔅但是实际情况中,插入的节点在son节点的左边还是右边我们是不知道的,也就是说,插入后的b树与c树的高度其实不确定的,只能确定不是h就是h-1,这会影响最终平衡因子的分布。根据bc树高度的不同可以分出三种情况:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

//左右双旋逻辑:
void RotateLR(Node* parent)
{//parent的左节点 -> curNode* subL = parent->_left;//parent的左节点的右节点 -> sonNode* subLR = subL->_right;// 记下 son 的平衡因子// 用son的平衡因子判断bc树不同的高度情况int bf = subLR->_bf;//复用左旋逻辑 , 对cur左旋RotateL(parent->_left);//复用右旋逻辑 , 对parent右旋RotateR(parent);//当b = h,c = hif (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}//当b = h-1,c = helse if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf =0;}//b = h,c = h-1else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else{assert(false);}
}

4.4 右左双旋

4️⃣新节点插入较高右子树的左侧—右左:先右单旋再左单旋
在这里插入图片描述

在这里插入图片描述

//右左双旋逻辑:与左右双旋逻辑大体一致
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf =0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}}

5.AVL树的验证

🔅AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步

  1. 验证其为二叉搜索树。如果中序遍历可得到一个有序的序列,就说明为二叉搜索树。
  2. 验证其为平衡树每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)节点的平衡因子是否计算正确。
bool _IsAVLTree(Node* pRoot)
{if (pRoot == nullptr){return true;}if (_Height(pRoot->_right) - _Height(pRoot->_left) != pRoot->_bf || abs(_Height(pRoot->_right) - _Height(pRoot->_left)>1)){cout << pRoot->_kv.first << ":" << pRoot->_kv.second << " bf=" << pRoot->_bf << endl;cout << "_Height(pRoot->_right) - _Height(pRoot->_left)=" << _Height(pRoot->_right) - _Height(pRoot->_left);return false;}return _IsAVLTree(pRoot->_left) && _IsAVLTree(pRoot->_right);
}//计算树高度函数
int _Height(Node* pRoot)
{if (pRoot == nullptr){return 0;}size_t leftH = _Height(pRoot->_left);size_t rightH = _Height(pRoot->_right);return leftH > rightH ? leftH + 1 : rightH + 1;
}

6. AVL树模拟实现

#pragma once
#include<iostream>
#include <assert.h>
using namespace std;//AVL采用键值对数结构
template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public://默认生成默认构造函数AVLTree() = default;//拷贝构造AVLTree(const AVLTree<K, V>& t){_root = Copy(t._root);}//赋值重载AVLTree<K, V>& operator=(AVLTree<K, V> t){swap(_root, t._root);return *this;}//析构函数~AVLTree(){Destroy(_root);_root = nullptr;}//插入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--;elseparent->_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 {RotateLR(parent);}break;}else{assert(false);}}return true;}//查找函数Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}//打印树,中序void InOrder(){inOrder(_root);cout << endl;}//验证函数bool IsAVLTree(){return _IsAVLTree(_root);}private:bool _IsAVLTree(Node* pRoot){if (pRoot == nullptr){return true;}if (_Height(pRoot->_right) - _Height(pRoot->_left) != pRoot->_bf || abs(_Height(pRoot->_right) - _Height(pRoot->_left)>1)){cout << pRoot->_kv.first << ":" << pRoot->_kv.second << " bf=" << pRoot->_bf << endl;cout << "_Height(pRoot->_right) - _Height(pRoot->_left)=" << _Height(pRoot->_right) - _Height(pRoot->_left);return false;}return _IsAVLTree(pRoot->_left) && _IsAVLTree(pRoot->_right);}int _Height(Node* pRoot){if (pRoot == nullptr){return 0;}size_t leftH = _Height(pRoot->_left);size_t rightH = _Height(pRoot->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}void inOrder(Node* root){if (root == nullptr){return;}inOrder(root->_left);cout << root->_kv.first<<":"<<root->_kv.second<<" ";inOrder(root->_right);}//左旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;}//右旋void  RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}parent->_bf = subL->_bf = 0;}//右左旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf =0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}}//左右旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf =0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else{assert(false);}}//摧毁树函数void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}//拷贝函数Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key, root->_value);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}private:Node* _root = nullptr;
};

本文就到这里,感谢你看到这里❤️❤️! 我知道一些人看文章喜欢静静看,不评论🤔,但是他会点赞😍,这样的人,帅气低调有内涵😎,美丽大方很优雅😊,明人不说暗话,要你手上的一个点赞😘!

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

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

相关文章

git 学习--GitHub Gitee码云 GitLab

1 集中式和分布式的区别 1.1 集中式 集中式VCS必须有一台电脑作为服务器&#xff0c;每台电脑都把代码提交到服务器上&#xff0c;再从服务器下载代码。如果网络出现问题或服务器宕机&#xff0c;系统就不能使用了。 1.2 分布式 分布式VCS没有中央服务器&#xff0c;每台电脑…

JavaScript === 和 ==

JavaScript 中&#xff0c; 和 是比较操作符。 &#xff08;严格等于&#xff09; 功能&#xff1a;比较两个值是否相等&#xff0c;同时要求它们的类型也必须相同。示例&#xff1a; 5 5 // true&#xff0c;因为类型和数值都相同 5 5 // false&#xff0c;因…

LeetCode 热题100-30 两两交换链表中的节点

两两交换链表中的节点 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4…

Elasticsearch 实现距离查询、排序和筛选

Elasticsearch 实现距离查询、排序和筛选 前言 在现代应用中&#xff0c;位置相关的查询需求越来越普遍。无论是查找附近的餐厅、计算两个地点之间的距离&#xff0c;还是根据用户位置进行排序和筛选&#xff0c;Elasticsearch 都提供了强大的地理位置查询功能。本文将介绍如…

将 hugo 博客搬迁到服务器

1. 说明 在 Ubuntu 22.04 上使用 root 账号&#xff0c;创建普通账号&#xff0c;并赋予 root 权限。 演示站点&#xff1a;https://woniu336.github.io/ 魔改hugo主题: https://github.com/woniu336/hugo-magic 2. 服务器配置 建立 git 用户 adduser git安装 git sudo apt …

docker升级docker pull mysql:5.7.37异常

一、使用背景 我们在使用docker拉取mysql命令时&#xff0c;数据库服务器&#xff0c;网络未开通外网&#xff0c;拉取镜像失败 但是我们还是想用docker部署则可以通过以下方式获取 前提&#xff1a;环境网络通可以pull mysql镜像 [rootVM-20-10-centos opt]# docker ps CO…

python | 图片转换为 pdf 实现方法

目录 一、PIL 库简介及安装使用方法 &#xff08;一&#xff09;python 不同版本下 PIL 的使用方法 二、图片转换为 pdf 的两种实现方法 &#xff08;一&#xff09;简易版——pdf 页面尺寸跟随图片大小 &#xff08;二&#xff09;常用版——pdf 每页尺寸统一为 A4 一、P…

ECMAScript 性能优化技巧与陷阱

ECMAScript 性能优化技巧与陷阱 在现代Web开发中&#xff0c;JavaScript&#xff08;ECMAScript的实现&#xff09;已成为构建高性能应用的核心语言。随着应用规模的扩大和复杂性的增加&#xff0c;性能优化变得尤为重要。本文将深入探讨ECMAScript性能优化的技巧与常见陷阱&a…

c++指南 继承和多态

继承和多态 继承的概念 继承是面向对象编程的一个重要特性&#xff0c;它允许新创建的类&#xff08;称为子类或派生类&#xff09;继承现有类&#xff08;称为基类或父类&#xff09;的属性和方法。 基类与子类 基类&#xff1a;提供了可以被继承的属性和方法。 子类&…

shellcode汇编复习

shellcode汇编复习 一、 汇编代码复习1.1 基础寄存器1. EAX (Accumulator Register)2. EBX (Base Register)3. ECX (Count Register)4. EDX (Data Register)5. ESI (Source Index Register)6. EDI (Destination Index Register) 二、 基础指令1. mov - 数据传送2. add - 加法3.…

JAVA IO之基础知识

简介 IO 即 Input/Output&#xff0c;输入和输出。数据输入到计算机内存的过程即输入&#xff0c;反之输出到外部存储&#xff08;比如数据库&#xff0c;文件&#xff0c;远程主机&#xff09;的过程即输出。数据传输过程类似于水流&#xff0c;因此称为 IO 流。IO 流在 Java…

Ansys Zemax|如何有效地模拟散射

附件下载 联系工作人员获取附件 概要 OpticStudio中&#xff0c;有两个用来提升散射模拟效率的工具&#xff1a;Scatter To List以及Importance Sampling。在这篇文章中&#xff0c;我们详细讨论了这两个工具&#xff0c;并且以一个杂散光分析为例示范了如何使用Importance S…

Shell工具——cut

cut 是一个用于在 Unix 和 Linux 系统中提取文本行中特定部分的命令行工具。它通常用于从文件或命令输出中提取列、字段或字符&#xff0c;特别是在处理由分隔符分割的文本数据时&#xff08;例如CSV文件&#xff09;。 基本语法 cut OPTION [FILE...]其中&#xff0c;OPTION…

机器学习调优方法总结

目录 一、问题 问题1&#xff1a;数据输入 问题2&#xff1a;output和target维度不匹配 问题3&#xff1a;NLP中处理数据有哪些方法&#xff1f; 二、改进 改进1&#xff1a;改变归一化函数 改进1.1&#xff1a;用StandardScaler替换MinMaxScale 改进1.2&#xff1a;数…

简单的jar包重打包Failed to get nested archive for entry 报错处理

简单的jar包重打包Failed to get nested archive for entry 报错处理 1. 需求 公司有一个后端项目&#xff0c;项目已经打好了jar包&#xff0c;现在我们发现jar包依赖的子包有问题&#xff0c;其中的一个mybatis xml文件查询数据不正确&#xff0c;我们需要替换项目&#xf…

批量将labelme的json文件转为png图片查看

文章目录 前提修改 l a b e l m e labelme labelme然后你就可以在这个环境下用代码批量修改了 前提 安装anaconda或者miniconda安装labelme 修改 l a b e l m e labelme labelme 查看labelme所处环境的路径&#xff1a;conda info --envs 比如我的是在py39_torch里面 修改la…

秋招力扣Hot100刷题总结——链表

1. 反转链表题目连接 题目要求&#xff1a;给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 代码及思路 遍历所有节点&#xff0c;将所有节点的next指向前一个节点由于要改变节点的next指向&#xff0c;而链表是单向的&#xff0c;因此需要…

Radiance Field Learners As UAVFirst-Person Viewers 翻译

作为无人机第一人称视角的辐射场学习者 引言。第一人称视角&#xff08;FPV&#xff09;在无人机飞行轨迹的革新方面具有巨大的潜力&#xff0c;为复杂建筑结构的导航提供了一条令人振奋的途径。然而&#xff0c;传统的神经辐射场&#xff08;NeRF&#xff09;方法面临着诸如每…

PyQt5 QSS

一、 二、 三、课堂练习 1.课时122.QSS基础_哔哩哔哩_bilibili import sys, os from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtPrintSupport import QPrinter,QPageSetup…

【Mac】植物大战僵尸杂交版 for Mac(经典策略塔防游戏)游戏介绍

游戏介绍 植物大战僵尸杂交版 for Mac是一款非常受欢迎的策略塔防游戏&#xff0c;植物大战僵尸游戏以其独特的主题、幽默的风格和富有挑战性的关卡设计而著称。玩家需要种植各种植物来防御入侵的僵尸&#xff0c;每种植物都有其特定的功能和攻击方式。植物大战僵尸杂交版&…