[数据结构] AVL树的插入旋转 和 概念理解

news/2024/11/15 17:31:16/

文章目录

  • 定义 && 性质
    • 定义
    • 性质
  • 实现
    • 思路
    • 架构
    • 节点
    • AVL树
      • 框架
      • Insert(插入)
      • 左单旋
      • 右单旋
      • 左右双旋
      • 右左双旋

定义 && 性质

定义

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

而AVL树可以较好的解决上述问题:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

性质

AVL树是一种 自平衡二叉搜索树,严格满足以下性质:

  1. 对于任何一个节点,其左子树和右子树的高度差(即平衡因子)不超过1。
  2. AVL树的每个节点存储的键值大于其左子树中所有节点的键值,小于其右子树中的所有节点的键值。这也是二叉搜索树的基本性质。
  3. 它的左右子树都是AVL树

实现

思路

  • 核心思想是通过旋转操作保持树的平衡性
  • 在 AVL 树中,每个节点都有一个平衡因子,并且平衡因子的值只能是 0,1 或 -1
  • 当插入或删除节点导致某个节点的平衡因子绝对值大于 1 时,就需要通过旋转操作来调整整棵树的平衡性。

在这里插入图片描述

当右子树高的时候,平衡因子+1,当左子树高时,平衡因子-1
对上述的树,就需要通过旋转让其平衡

架构

  • 包含两个结构体,一个用来进行节点的实现,一个用于实现 AVLTree 的各项功能
#pragma oncetemplate<class K, class V>
struct AVLTreeNode //节点
{// 成员变量 和 成员函数
};template<class K, class V>
struct AVLTree
{typedef AVLTreeNode<K, V> Node; //重命名
public:// 公有成员函数
private:// 私有成员函数
private:// 成员变量Node* _root = nullptr;
};

节点

  1. 将节点的实现在结构体中,节点包含 左右子树指针、父节点指针、键值对和平衡因子。
  2. AVL 树的节点需要记录其左右子树指针和父节点指针,以支持 AVL 树的旋转操作。同时还需要记录键值对,以实现对键值对信息的存储。最后,需要记录平衡因子用于判断是否需要进行旋转的操作。
  3. 用一个构造函数初始化成员变量
template<class K, class V>
struct AVLTreeNode //节点
{AVLTreeNode<K, V>* _left; //指向左子树AVLTreeNode<K, V>* _right; //指向右子树AVLTreeNode<K, V>* _parent; //指向父节点pair<K, V> _kv; //键值对int _bf; //平衡因子AVLTreeNode(const pair<K, V> & kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}
};

AVL树

框架

  • 将待实现的功能放到这里
  • 成员变量为_root根节点
template<class K, class V>
struct AVLTree
{typedef AVLTreeNode<K, V> Node; //重命名
public:// 插入bool Insert(const pair<K, V>& kv){}// 中序遍历/打印void InOrder(){}//判断是否平衡bool IsBalance(){}private:// 判断是否平衡bool _IsBalance(Node* root){}// 求AVL树最大高度int Height(Node* root){}// 左单旋void RotateL(Node* parent){}// 右单旋void RotateR(Node* parent){}// 左右双旋void RotateLR(Node* parent){}// 右左双旋void RotateRL(Node* parent){}void _InOrder(Node* root){}private:// 根节点Node* _root = nullptr;
};

Insert(插入)

插入过程主要分为两个步骤:

  1. 搜索树的插入过程。从根节点开始,与新插入的节点的键值比较大小,向左(小于当前节点的键值)或向右(大于当前节点的键值)递归地查找插入位置;如果要插入的节点的键值已经存在,则返回 false。
  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 (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_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; // 记录插入节点的父节点// 控制平衡// 1、更新平衡因子while (parent){// cur在右边,则平衡因子++if (cur == parent->_right)++parent->_bf;// cur在左边,则平衡因子--else--parent->_bf;// 平衡因子 == 0,已经平衡if (parent->_bf == 0) {break;}// == 1,向上找else if (abs(parent->_bf) == 1) {parent = parent->_parent;cur = cur->_parent;}// == 2,此时parent所在子树已经不平衡了,需要旋转处理else if (abs(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){// 左右双旋RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){//右左双旋RotateRL(parent);}else{// 正常情况下不会出现该种情况,如果出现直接报错assert(false);}}else {// 正常情况下不会出现该种情况,如果出现直接报错assert(false);}}return true;}

左单旋

抽象图(思路):

在这里插入图片描述

  1. 根据图中思路写代码:记录右侧节点和右侧节点的左子树节点。
  2. 将 subR 的左子树更改为 parent,同时将 parent 的父节点指向 subR。
  3. 将 parent 的右子树更改为 subRL,并更新 subRL 的父节点为 parent。
  4. 如果 parent 是根节点,则将 subR 设为新的根节点,并将 subR 的父节点设为 nullptr;否则将 subR 的父节点指向 parent 的父节点,并更新 parent 的父节点的左子树或右子树为 subR。
  5. 最后,将 subR 和 parent 的平衡因子都设置为 0。
void RotateL(Node* parent){// 此时右子树的高度比左子树高2,记录右侧节点Node* subR = parent->_right;Node* subRL = subR->_left;// 目的需要将subR变为新的父节点(根节点)// 将SubRL变为parent的子节点parent->_right = subRL;if (subRL)subRL->_parent = parent;// 创建此时父节点的头节点Node* ppNode = parent->_parent;subR->_left = parent;parent->_parent = subR; //此时subR到父节点的位置// 如果本来父节点为根if (_root == parent){//将subR变为根_root = subR;subR->_parent = nullptr;}// 本来parent不为根节点else{// 将subR的_parent指向ppNodeif (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_right = subR;subR->_parent = ppNode;}// 此时平衡,更新平衡因子subR->_bf = parent->_bf = 0;}

右单旋

在这里插入图片描述

  1. 根据图片思路写代码:定义 subL 和 subLR 分别为 parent 节点的左孩子和 subL 节点的右孩子。
  2. 将 subLR 节点移动到 parent 节点的位置上,即 parent 的左孩子变成 subLR,subLR 的父节点变成 parent。
  3. 将 subL 节点移动到 subLR 节点的右边,subL 的右孩子变成 parent,parent 的父节点变成 subL。
  4. 如果原先 parent 节点是根节点,将 subL 节点设为新的根节点并将其父节点设置为空;如果不是根节点,则将 subL 节点移动到 parent 节点原来的父节点的位置上,并更新子节点的父节点。
  5. 更新 subL 和 parent 节点的平衡因子,均设为 0。
void RotateR(Node* parent){// 获取左子树和左子树的右子树Node* subL = parent->_left;Node* subLR = subL->_right;// 旋转操作:将左子树的右子树变为 parent 的左子树parent->_left = subLR;if (subLR)subLR->_parent = parent;// 将 parent 变为左子树的右子树subL->_right = parent;parent->_parent = subL;// 如果 parent 原本是根节点,则设置新的根节点为 subL,否则需要调整 parent 的父节点Node* ppNode = parent->_parent;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}// 更新平衡因子:旋转后,parent 和 subL 的平衡因子都变为 0subL->_bf = parent->_bf = 0;}

左右双旋

在这里插入图片描述

在这里插入图片描述

  1. 根据图片步骤写代码:定义 subL 和 subLR 分别为 parent 节点的左孩子和右孩子的左孩子。
  2. subL 进行一次左旋操作,此时 subL 变成了 parent 节点的父节点,subLR 成为了 subL 的右孩子。
  3. parent 节点进行一次右旋操作,此时 subLR 节点被转移到 parent 的位置上,并成为 parent 的父节点。同时,subLR 的右子树成为 parent 的左子树,subLR 的左子树成为 subL 的右子树。
  4. 根据新树结构和子树的高度差,调整各个节点的平衡因子。其中,subLR 节点的平衡因子设为 0。如果 subLR 原来的平衡因子为 1,则 parent 的平衡因子、subL 的平衡因子和 subLR 的平衡因子分别设为 0,-1 和 0;如果 subLR 原来的平衡因子为 -1,则三者分别设为 1,0 和 0;如果 subLR 原来的平衡因子为 0,则三者均设为 0。
void RotateLR(Node* parent){// 定义subL,subLRNode* subL = parent->_left;Node* subLR = subL->_right;// 保存subLR节点的平衡因子int bf = subLR->_bf;// 左旋操作,将subLR上移到subL的位置RotateL(parent->_left);// 右旋操作,将subLR上移到parent的位置RotateR(parent);// 根据新树结构和子树高度调整各个节点平衡因子subLR->_bf = 0; // subLR的平衡因子设为0if (bf == 1) // 如果subLR原来的平衡因子为1{parent->_bf = 1;subL->_bf = 0;}else if (bf == -1){parent->_bf = 0;subL->_bf = 1;}else if (bf == 0){parent->_bf = 0;subL->_bf = 0;}// subLR的平衡因子取值范围应该是-1,0,1三个值,如果不在这个范围内则代表程序出现了错误else{assert(false); // 断言,程序错误}}

右左双旋

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

  1. 根据图步骤来实现代码:定义 subR 和 subRL 分别为 parent 节点的右孩子和左孩子的右孩子。
  2. 对 subR 进行一次右旋操作,此时 subR 变成了 parent 节点的父节点,subRL 成为了 subR 的左孩子。
  3. 对 parent 节点进行一次左旋操作,此时 subRL 节点被转移到 parent 的位置上,并成为 parent 的父节点。同时,subRL 的左子树成为 parent 的右子树,subRL 的右子树成为 subR 的左子树。
  4. 根据新树结构和子树的高度差,调整各个节点的平衡因子。
void RotateRL(Node* parent){// 定义subR,subRLNode* subR = parent->_right;Node* subRL = parent->_left;// 保存subRL节点的平衡因子int bf = subRL->_bf;// 右旋操作,将subR上移到parent的位置RotateR(parent->_right);// 左旋操作,将subRL上移到parent的位置RotateL(parent);// 根据新树结构和子树高度调整各个节点平衡因子subRL->_bf = 0; // subRL的平衡因子设为0if (bf == 1) // 如果subRL原来的平衡因子为1{subR->_bf = 0; parent->_bf = -1; }else if (bf == -1) {subR->_bf = 1; parent->_bf = 0; }else if (bf == 0) {subR->_bf = 0; parent->_bf = 0;}// subRL的平衡因子取值范围应该是-1,0,1三个值,如果不在这个范围内则代表程序出现了错误else{assert(false);}}

http://www.ppmy.cn/news/93601.html

相关文章

【运维知识进阶篇】Ansible自动化运维-Ansible安装与主机列表

很开心大家可以看到这篇文章&#xff0c;Ansible是一个自动化统一配置管理工具&#xff0c;集成了丰富模块以及功能组件&#xff0c;可以通过一个命令对多台服务器主机实现批量化操作&#xff0c;减少重复性工作和维护成本&#xff0c;提高工作效率。 同类软件有很多&#xff…

记录--前端小票打印、网页打印

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 一、小票打印 目前市面上的小票打印机大多采用的打印指令集为ESC/POS指令&#xff0c;它可以使用ASCII码、十进制、十六进制来控制打印&#xff0c;我们可以使用它来控制字体大小、打印排版、字体加粗…

leetcode 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度 哈希集合unordered_set C++

给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”&#xff0c;所以其长度为 3。 示例 2: 输入: s “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “…

Hadoop Streaming使用简介

一、Hadoop Streaming 它是hadoop的一个工具&#xff0c;用来创建和运行一类特殊的map/reduce作业。所谓的特殊的map/reduce作业可以是可执行文件或脚本本件&#xff08;python、PHP、c等&#xff09;。Streaming使用“标准输入”和“标准输出”与我们编写的Map和Reduce进行数据…

离婚时才知道丈夫年入300万,丈夫藏的是真够深的

据媒体报道&#xff0c;近日&#xff0c;江苏宜兴法院公开了一起离婚案件&#xff0c;引起了社会广泛关注。 这起案件的争议点在于夫妻共同财产的分割问题。 据报道&#xff0c;丈夫李某在离婚案中声称自己仅有10万元存款&#xff0c;而妻子张某则是家庭主妇&#xff0c;对丈夫…

Go 语言 map 是并发安全的吗?

原文链接&#xff1a; Go 语言 map 是并发安全的吗&#xff1f; Go 语言中的 map 是一个非常常用的数据结构&#xff0c;它允许我们快速地存储和检索键值对。然而&#xff0c;在并发场景下使用 map 时&#xff0c;还是有一些问题需要注意的。 本文将探讨 Go 语言中的 map 是否…

跨境电商环境搭建和买家账号培养的关键考虑因素

作为跨境电商环境搭建和买家账号培养的专业技术开发人员&#xff0c;我深知在亚马逊、速卖通、阿里国际、速卖通、美客多、shopee、Lazada、ebay、Temu等平台上运营的卖家面临的挑战 其中&#xff0c;补单是一项关键的工作&#xff0c;它能帮助卖家增加商品列表和评价数量&…

Day53【动态规划】1143.最长公共子序列、1035.不相交的线、53.最大子序和

1143.最长公共子序列 力扣题目链接/文章讲解 视频讲解 本题最大的难点还是定义 dp 数组 本题和718.最长重复子数组区别在于这里不要求是连续的了&#xff0c;但要有相对顺序 直接动态规划五部曲&#xff01; 1、确定 dp 数组下标及值含义 dp[i][j]&#xff1a;取 text1…