数据结构:AVL树讲解(C++)

AVL树

    • 1.AVL树的概念
    • 2.平衡因子
    • 3.节点的定义
    • 4.插入操作
    • 5.旋转操作(重点)
      • 5.1左单旋
      • 5.2右单旋
      • 5.3左右双旋
      • 5.4右左双旋
    • 6.一些简单的测试接口
    • 7.完整代码

1.AVL树的概念

普通二叉搜索树:二叉搜索树

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序普通的二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法(AVL树是以这两位的名字命名的):当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1超过了需要对树中的结点进行调整(旋转),即可降低树的高度,从而减少平均搜索长度。

AVL树也是二叉搜索树,但有以下特点:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
  • 如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)。



2.平衡因子

AVL树的实现有很多种,本文引入平衡因子来维持高度稳定。
本文平衡因子的定义:右子树高度 - 左子树的高度

依据每个节点的平衡因子,我们可以判断树的情况:

  • 平衡因子在(-1, 0, 1),当前节点所在子树是稳定的。
  • 平衡因子为2或-2,当前节点所在子树是不稳定的。

插入节点后平衡因子的更新:

  • 插入节点在右子树,平衡因子加一
  • 插入节点在左子树,平衡因子减一

插入节点后平衡因子的不同情况(重点):

  • 当前节点所在子树平衡因子为0,子树高度不变,不需要更新
    (原来一边高一边低,新插入在低一方,变成完全平衡)。
  • 当前节点所在子树平衡因子为1或-1,子树高度变化,需要向上更新。
    (原来完全平衡,现在一边高,子树整体高度加1,会影响到祖先的平衡,故需要向上更新看祖先所在子树是否平衡)
  • 当前节点所在子树平衡因子为2或-2,子树高度变化且不平衡,无需向上更新,对当前子树进行旋转操作。
    (当前子树已不平衡,向上更新没有意义,旋转操作是AVL树的核心,可以降低当前子树的高度且不影响上面的树结构)

在这里插入图片描述



3.节点的定义

节点除了需要增加一个平衡因子,还需要增加一个父亲指针,方便我们进行平衡因子的向上更新和旋转操作。

template<class K, class V>
struct ALVTreeNode
{ALVTreeNode<K, V>* _left;ALVTreeNode<K, V>* _right;ALVTreeNode<K, V>* _parent;pair<K, V> _kv;  //存储键值对int _bf;  //平衡因子ALVTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0),_kv(kv){}
};



4.插入操作

PS:因为多加了一个父亲指针,所以插入时要注意更新父亲指针,平衡因子更新按前面的分析来还是比较简单的,旋转操作后面单独讲。

bool Insert(const pair<K, V>& kv)
{if (_root == nullptr) //一开始为空树,直接插入即可{_root = new Node(kv);return true;}//找插入位置加插入Node* cur = _root;  //记录插入位置Node* parent = nullptr;  //待插入位置的父亲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 (kv.first > parent->_kv.first)  //插入在父亲的右边{parent->_right = cur;}else  //插入在父亲的左边{parent->_left = cur;}cur->_parent = parent;  //注意更新父亲指针//调整平衡因子while (parent)  //是有可能调整到根部的{if (cur == parent->_right)  //如果新插入的在右子树{parent->_bf++;}else if (cur == parent->_left)  //如果新插入的在左子树{parent->_bf--;}if (parent->_bf == 0) //插入后高度不变{break;}else if (parent->_bf == 1 || parent->_bf == -1)  //插入后高度变化,但当前子树依然平衡,需要向上更新{parent = parent->_parent;cur = cur->_parent;}else if (parent->_bf == 2 || parent->_bf == -2)  //插入后高度变化,并且当前子树已经不平衡,旋转{//旋转操作(先省略)break;}else //存在大于2小于-2的情况,树原来就不平衡,应该报错{assert(false);}}return true;
}



5.旋转操作(重点)

5.1左单旋

主体思想:

在这里插入图片描述


平衡因子的调整:
在这里插入图片描述


代码加细节处理:

在这里插入图片描述

void RotateL(Node* parent)  //左单旋,rotate->旋转
{Node* SubR = parent->_right;Node* SubRL = SubR->_left;  //这个有可能为空Node* ppnode = parent->_parent;  //原来父亲的父亲parent->_right = SubRL;if(SubRL)  SubRL->_parent = parent;SubR->_left = parent;parent->_parent = SubR;if (ppnode == nullptr)  //旋转的是整颗树{_root = SubR;SubR->_parent = nullptr;}else  //旋转的是部分{if (ppnode->_left == parent) //是左子树{ppnode->_left = SubR;}else  //是右子树{ppnode->_right = SubR;}SubR->_parent = ppnode;}//最后更新平衡因子parent->_bf = SubR->_bf = 0;
}

5.2右单旋

PS:右单旋和左单旋类似,细节处理也差不多,这里只讲主体思路。
主体思路:
在这里插入图片描述

平衡因子的调整:
在这里插入图片描述

代码:

void RotateR(Node* parent)  //右单旋细节处理和左单旋差不多
{Node* SubL = parent->_left;Node* SubLR = SubL->_right;  //这个有可能为空Node* ppnode = parent->_parent;parent->_left = SubLR;if(SubLR)  SubLR->_parent = parent;SubL->_right = parent;parent->_parent = SubL;if (ppnode == nullptr)  //旋转的是整颗树{_root = SubL;SubL->_parent = nullptr;}else  //旋转部分{if (ppnode->_left == parent)  //是左子树{ppnode->_left = SubL;}else  //右子树{ppnode->_right = SubL;}SubL->_parent = ppnode;}//最后更新平衡因子parent->_bf = SubL->_bf = 0;
}

5.3左右双旋

PS:双旋其实就是两次单旋(复用即可),有前面的基础很好理解,重点在于旋转后平衡因子的更新

旋转思路:
在这里插入图片描述

平衡因子的调整:
想知道平衡因子调整是那种情况,我们需要在旋转前记录SubRL的平衡因子bf

  • bf为0是第一种情况。
  • bf为1是第二种情况。
  • bf为-1是第三种情况。

在这里插入图片描述

代码:

void RotateLR(Node* parent)  //左右双旋
{Node* SubL = parent->_left;Node* SubLR = SubL->_right;int bf = SubLR->_bf;RotateL(SubL);RotateR(parent);if (bf == 1) //插入的是右边{SubLR->_bf = 0;SubL->_bf = -1;parent->_bf = 0;}else if (bf == -1) //插入的是左边{SubLR->_bf = 0;SubL->_bf = 0;parent->_bf = 1;}else if (bf == 0) //刚好原来parent的左边就两个节点{SubLR->_bf = SubL->_bf = parent->_bf = 0;}else  //原来就不是平衡树,出现问题{assert(false);}
}

5.4右左双旋

PS:右左双旋和左右双旋思路是差不多的,重点还是在旋转后平衡因子的更新

旋转思路:
在这里插入图片描述

平衡因子的调整:
和前面一样,旋转前确认SubRL的平衡因子bf即可
在这里插入图片描述

代码:

void RotateRL(Node* parent)  //右左双旋
{Node* SubR = parent->_right;Node* SubRL = SubR->_left;int bf = SubRL->_bf;RotateR(SubR);RotateL(parent);if (bf == 1)  //插入的是右边{SubRL->_bf = 0;SubR->_bf = 0;parent->_bf = -1;}else if (bf == -1) //插入的是左边{SubRL->_bf = 0;parent->_bf = 0;SubR->_bf = 1;}else if (bf == 0)  //原来parent的右边就两个节点{SubRL->_bf = SubR->_bf = parent->_bf = 0;}else //原来就有问题{assert(false);}
}



6.一些简单的测试接口

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;
}bool IsBalance()
{return IsBalance(_root);
}//看当前树是不是平衡树
//(1)看每个子树是否满足左右子树高度差不超过一
//(2)看平衡因子和所求的左右子树高度差是否一致
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);
}



7.完整代码

#pragma once
#include <iostream>
#include <assert.h>
#include <utility>
using namespace std;template<class K, class V>
struct ALVTreeNode
{ALVTreeNode<K, V>* _left;ALVTreeNode<K, V>* _right;ALVTreeNode<K, V>* _parent;pair<K, V> _kv;  //存储键值对int _bf;ALVTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0),_kv(kv){}
};template<class K, class V>
class AVLTree
{
public:typedef ALVTreeNode<K, V> Node;bool Insert(const pair<K, V>& kv){if (_root == nullptr) //一开始为空树,直接插入即可{_root = new Node(kv);return true;}Node* cur = _root;  //记录插入位置Node* parent = nullptr;  //待插入位置的父亲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 (kv.first > parent->_kv.first)  //插入在父亲的右边{parent->_right = cur;}else  //插入在父亲的左边{parent->_left = cur;}cur->_parent = parent;//调整平衡因子while (parent)  //是有可能调整到根部的{if (cur == parent->_right)  //如果新插入的是右子树{parent->_bf++;}else if (cur == parent->_left)  //如果新插入的是左子树{parent->_bf--;}if (parent->_bf == 0) //插入后高度不变{break;}else if (parent->_bf == 1 || parent->_bf == -1)  //插入后高度变化,但当前子树依然平衡,需要向上更新{parent = parent->_parent;cur = cur->_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)  //左子树的右边高,左右双旋{RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1)  //右子树的左边高,右左双旋{RotateRL(parent);}else  //原来就不是平衡树{assert(false);}break;}else //树原来就不平衡,应该报错{assert(false);}}return true;}////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;}bool IsBalance(){return IsBalance(_root);}//看当前树是不是平衡树//(1)看每个子树是否满足左右子树高度差不超过一//(2)看平衡因子和所求的左右子树高度差是否一致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);}private:void RotateL(Node* parent)  //左单旋,rotate->旋转{Node* SubR = parent->_right;Node* SubRL = SubR->_left;  //这个有可能为空Node* ppnode = parent->_parent;  //原来父亲的父亲parent->_right = SubRL;if(SubRL)  SubRL->_parent = parent;SubR->_left = parent;parent->_parent = SubR;if (ppnode == nullptr)  //旋转的是整颗树{_root = SubR;SubR->_parent = nullptr;}else  //旋转的是部分{if (ppnode->_left == parent) //是左子树{ppnode->_left = SubR;}else  //是右子树{ppnode->_right = SubR;}SubR->_parent = ppnode;}//最后更新平衡因子parent->_bf = SubR->_bf = 0;}void RotateR(Node* parent)  //右单旋细节处理和左单旋差不多{Node* SubL = parent->_left;Node* SubLR = SubL->_right;  //这个有可能为空Node* ppnode = parent->_parent;parent->_left = SubLR;if(SubLR)  SubLR->_parent = parent;SubL->_right = parent;parent->_parent = SubL;if (ppnode == nullptr)  //旋转的是整颗树{_root = SubL;SubL->_parent = nullptr;}else  //旋转部分{if (ppnode->_left == parent)  //是左子树{ppnode->_left = SubL;}else  //右子树{ppnode->_right = SubL;}SubL->_parent = ppnode;}//最后更新平衡因子parent->_bf = SubL->_bf = 0;}void RotateLR(Node* parent)  //左右双旋{Node* SubL = parent->_left;Node* SubLR = SubL->_right;int bf = SubLR->_bf;RotateL(SubL);RotateR(parent);if (bf == 1) //插入的是右边{SubLR->_bf = 0;SubL->_bf = -1;parent->_bf = 0;}else if (bf == -1) //插入的是左边{SubLR->_bf = 0;SubL->_bf = 0;parent->_bf = 1;}else if (bf == 0) //刚好原来parent的左边就两个节点{SubLR->_bf = SubL->_bf = parent->_bf = 0;}else  //原来就不是平衡树,出现问题{assert(false);}}void RotateRL(Node* parent)  //右左双旋{Node* SubR = parent->_right;Node* SubRL = SubR->_left;int bf = SubRL->_bf;RotateR(SubR);RotateL(parent);if (bf == 1)  //插入的是右边{SubRL->_bf = 0;SubR->_bf = 0;parent->_bf = -1;}else if (bf == -1) //插入的是左边{SubRL->_bf = 0;parent->_bf = 0;SubR->_bf = 1;}else if (bf == 0)  //原来parent的右边就两个节点{SubRL->_bf = SubR->_bf = parent->_bf = 0;}else //原来就有问题{assert(false);}}Node* _root = nullptr;
};

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

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

相关文章

安装MySQL时出现 由于找不到 MSVCR120.dll,无法继续执行代码。重新安装程序可能会解决此问题。

--------------------------- mysqld.exe - 系统错误 --------------------------- 由于找不到 MSVCR120.dll&#xff0c;无法继续执行代码。重新安装程序可能会解决此问题。 --------------------------- 确定 --------------------------- 安装MySQL时出现 “This appl…

分布式服务框架设计

目录 服务框架的设计 服务框架的功能 服务框架的性能指标 服务治理需要哪些功能 服务框架的设计 尽管不同的分布式服务框架实现细节存在差异&#xff0c;但是核心功能差异不大&#xff0c;下面的架构图描绘了一个分布式服务框架的整体逻辑架构 总共分为 3 层&#xff1a;1…

Ansible自动化安装部署及使用

目录 前言 一、环境概况 修改主机名&#xff08;可选项&#xff09; 二、安装部署 1.安装epel扩展源 2.安装Ansible 3.修改Ansible的hosts文件 4.生成密钥 三、Ansible模块使用介绍 Command模块 Shell模块 User模块 Copy模块 File模块 Hostname模块 Yum模块 Ser…

Ubuntu重启后进入initramfs导致无法开机解决方案

今天&#xff0c;我的电脑意外关机&#xff0c;重新开机后打开了虚拟机。该虚拟机使用的是 Ubuntu 22.04 系统。但重启后&#xff0c;系统一直显示(initramfs):&#xff0c;导致无法正常启动。最后&#xff0c;在网上查找了一些解决方案&#xff0c;成功解决了这个开机问题。在…

圣杯布局/双飞翼布局/flex/grid等,实现CSS三栏自适应布局的几种方法

简介 三栏布局是网页设计中常用的布局&#xff0c;即网页中的内容被分为三块&#xff1a;左侧/中间/右侧。其中两侧部分宽度固定&#xff0c;中间部分宽度自适应的根据浏览器宽度撑满剩余空间。而三栏布局也有很多变形&#xff0c;比如两栏或者N栏布局&#xff0c;上中下三栏布…

Linux shell编程学习笔记21:用select in循环语句打造菜单

一、select in循环语句的功能 Linux shell脚本编程提供了select in语句&#xff0c;这是 Shell 独有的一种循环语句&#xff0c;非常适合终端&#xff08;Terminal&#xff09;这样的交互场景&#xff0c;它可以根据用户的设置显示出带编号的菜单&#xff0c;用户通过输入不同…

爱德华的台灯真的好用?爱德华、书客、好视力护眼台灯对比测评

如今孩子近视的比例越来越大&#xff0c;而且时间越来越提前&#xff0c;一些上小学的孩子开始近视佩戴眼镜。其实造成近视的原因不外乎作业坐姿不标准&#xff0c;学业时间太长缺少户外的运动&#xff0c;也包括可能灯光光源的问题造成对于视力的影响。如果希望孩子有一个较好…

3.26每日一题(线性非齐次的特解如何设)

1、非齐次方程有e的2x次幂&#xff1a;特解也有e的2x次幂 2、e的2x次幂前面有特殊的一元方程&#xff1a;特解要设为一般的特征方程&#xff08;axb&#xff09; 3、求线性齐次特征方程的特征根&#xff1b; 4、判断e的 rx 次幂中的 r 和特征根有没有重合的个数&#xff1a;…

【每日一题】数组中两个数的最大异或值

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;哈希集合 其他语言python3 写在最后 Tag 【哈希集合】【位运算-异或和】【数组】【2023-11-04】 题目来源 421. 数组中两个数的最大异或值 题目解读 找出数组中两个数的最大异或结果。 解题思路 一看数据量达到了 …

Ubuntu20.04搭建RISC-V和qemu环境

1. 前言 risc-v是一个非常有潜力的指令集框架&#xff0c;最近对其产生了浓厚的兴趣&#xff0c;由于之前对于这方面的知识储备很少&#xff0c;在加上网上的教程都是点到为止&#xff0c;所以安装过程异常曲折。好在最后一步一步积累摸索&#xff0c;终于利用源码安装完成。看…

【中国知名企业高管团队】系列59:TCL

今天华研荟为大家介绍TCL公司的情况和创始人李东生先生的故事。 一、关于TCL TCL创立于1981年&#xff0c;前身为中国首批13家合资企业之一&#xff1a;TTK家庭电器(惠州)有限公司&#xff0c;该公司为中港合资企业&#xff0c;最初从事卡式录音磁带的生产制造&#xff0c;19…

基于单片机的超声波测距仪

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、本课题研究的主要内容二、超声波测距仪的整体方案2.2 超声波测距仪设计原理 三、超声波测距仪系统硬件电路的设计3.1 超声波测距仪的基本结构 四、 超声波测距仪系统的软件设计4.1 主程序软件设计仿真 五、结…

2019数二(二重积分的不等式问题)

注&#xff1a; 1、在相同积分区域内的积分比较大小&#xff1a;被积函数大的积分值大&#xff0c;被积函数小的积分值小 2、在区间[0&#xff0c;Π/2]上 &#xff1a;sinx < x < tanx

c面向对象编码风格(上)

面向对象和面向过程的基本概念 面向对象和面向过程是两种不同的编程范式&#xff0c;它们在软件开发中用于组织和设计代码的方式。 面向过程编程&#xff08;Procedural Programming&#xff09;是一种以过程&#xff08;函数、方法&#xff09;为核心的编程方式。在面向过程…

C语言查看各数据类型所占大小

编译器&#xff1a;VC2010 #include<stdio.h> int main() {printf("%d\n",sizeof(char));printf("%d\n",sizeof(short));printf("%d\n",sizeof(int));printf("%d\n",sizeof(long));printf("%d\n",sizeof(long long))…

【sql注入】sql关卡1~4

前言&#xff1a; 靶场自取 level-1 测试注入点 POC: 1,1,1,1"",1/1,1/0 》存在注入点 爆破 POC: id-1andextractvalue(1,concat(0x7e,user(),0x7e))-- level-2 尝试注入点 POC1:admin POC2:admin POC3:adminandsleep(3)-- POC4: adminandif(1,1,0)0-- POC…

最新 vie-vite框架下 jtopo安装使用

官方地址 官方源码 安装下载 1.官方好像都没有给git地址&#xff0c;尝试npm安装报错 2.找到1.0.5之前的版本npm i jtopo2&#xff0c;安装成功后使用报错&#xff0c;应该是版本冲突了 1.本地引入&#xff0c; 点击官方源码下载&#xff0c;需要jtopo_npm文件 2.引入到本…

Jetpack:030-Jetpack中的状态

文章目录 1. 概念介绍2. 使用方法2.1 可监听对象2.2 获取状态值2.3 修改状态值2.4 重组函数 3. 示例代码4. 内容总结 我们在上一章回中介绍了Jetpack中网格布局相关的内容&#xff0c;本章回中主要 介绍状态。闲话休提&#xff0c;让我们一起Talk Android Jetpack吧&#xff0…

【SpringCloud Alibaba -- Nacos】Linux 搭建 Nacos 集群

搭建 Nacos 集群 架构 centos安装docker https://docs.docker.com/engine/install/centos/ 详细配置过程 MySql8 mysql数据库配置 数据库脚本 nacos/conf/nacos-mysql.sql Nacos2 application.properties 修改为mysql spring.datasource.platformmysqldb.num1 db.url…

【工具】Github统计代码行数工具推荐(VScode插件、兼容任何平台、不用下载安装包)

需求&#xff1a; 1&#xff09;被要求统计代码行数&#xff1b; 2&#xff09;不想打开Linux&#xff0c;懒得下载Windows版本GitStats&#xff1b; 3&#xff09;打开了Linux但也不记得find命令行怎么用&#xff1b; 4&#xff09;打开了Linux&#xff0c;装好了Gitstats但自…