012 C++ AVL_tree

前言

本文将会向你介绍AVL平衡二叉搜索树的实现

引入AVL树

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

平衡因子

AVL树的平衡因子是指一个节点的左子树的高度减去右子树的高度的值。在AVL树中,每个节点的平衡因子必须为-1、0或1,如果不满足这个条件,就需要通过旋转操作来重新平衡树。AVL树的平衡因子可以帮助我们判断树的平衡状态,并且在插入进行相应的调整,以保持树的平衡性。

节点的创建

除了需要增加一个_bf平衡因子,这里还多加了一个pParent的结构体指针便于我们向上遍历对平衡因子进行调整

struct AVLTreeNode
{AVLTreeNode(const T& data = T()): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;AVLTreeNode<T>* _pRight;AVLTreeNode<T>* _pParent;T _data;int _bf;   // 节点的平衡因子
};

插入节点

先按照二叉搜索树的规则将节点插入到AVL树中
新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树的平衡性
cur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:

如果cur插入到pParent的左侧,只需给pParent的平衡因子-1即可
如果cur插入到pParent的右侧,只需给pParent的平衡因子+1即可

此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2

1. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功
2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新
3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理

	// 在AVL树中插入值为data的节点bool Insert(const T& data){Node* cur = _pRoot;Node* parent = nullptr;if (_pRoot == nullptr){//直接插入_pRoot = new Node(data);//插入成功return true;}//寻找插入位置else{Node* parent = cur;while (cur){parent = cur;if (cur->_data > data){cur = cur->_pLeft;}else if (cur->_data < data){cur = cur->_pRight;}//已有else return false;}cur = new Node(data);//插入+链接if (parent->_data > data){parent->_pLeft = cur;}else{parent->_pRight = cur;}//链接cur->_pParent = parent;}//更新平衡因子while (parent){if (cur == parent->_pRight){parent->_bf++;}else if (cur == parent->_pLeft){parent->_bf--;}if (parent->_bf == 0){//插入后子树稳定,不用向上更新平衡因子return true;}else if (parent->_bf == 1 || parent->_bf == -1){return true;}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){//右左双旋(不是单独的左右有一方低,有一方高)RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){//左右双旋(不是单独的左右有一方低,有一方高)RotateR(parent);}parent = parent->_pParent;cur = cur->_pParent; }else{return false;}return true;}}

右单旋

左高右低,往右边旋(根据平衡因子判断(右子树的高度减去左子树的高度))
细节分析+代码
在这里插入图片描述

整体思路
在这里插入图片描述
在这里插入图片描述

	void RotateR(Node* pParent){Node* pPnode = pParent->_pParent;Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;if (subLR){pParent->_pLeft = subL->_pRight;subL->pParent = pParent;}subL->_pRight = pParent;pParent->_pParent = subL;//旋转部分子树if (pPnode){//是左子树if (pPnode->_pLeft == pParent){pPnode->_pLeft = subL;subL->pParent = pPnode;}//是右子树else{pPnode->_pLeft = subL;subL->pParent = pPnode;}}//旋转整棵子树else{_pRoot = subL;subL->pParent = nullptr;}//调节平衡因子pParent->_bf = subL->_bf = 0;}

左单旋

这里作统一说明:h表示子树的高度,绿色标记的数字为节点的平衡因子,长方形表示的是一棵抽象的子树
右高左低,往左边旋(根据平衡因子判断(右子树的高度减去左子树的高度))
左单旋和右单旋的思路很像,这里就不再进行细节分析。

整体思路
在这里插入图片描述
在这里插入图片描述

void RotateL(Node* pParent){Node* pPnode = pParent->_pParent;Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;//可能为空if (subRL){pParent->_pRight = subRL;subRL->_pParent = pParent;}subR->_pLeft = pParent;pParent->_pParent = subR;//链接:旋转整棵树if (pPnode == nullptr){_pRoot = subR;subR->_pParent = nullptr;}//链接:旋转子树else{if (pPnode->_pLeft == pParent){pPnode->_pLeft = subR;subR->_pParent = pPnode;}else if (pPnode->_pRight == pParent){pPnode->_pRight = subR;subR->_pParent = pPnode;}}//更新平衡因子pParent->_bf = subR->_bf = 0;}

左右双旋

右左双旋(不是单独的左右有一方低,有一方高)

(1)第一种情况,也是最特殊的情况,即parent的右子树只有两个节点

在这里插入图片描述

(2)第二种情况,parent的左右子树是高度为h的抽象子树,新增节点插入到b子树上

在这里插入图片描述

(2)第三种情况,parent的左右子树是高度为h的抽象子树,新增节点插入到c子树上 实际上第二三种情况的分析是一致的

在这里插入图片描述

void RotateLR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;int bf = subLR->_bf;//复用RotateL(subL);RotateR(pParent);//更新平衡因子//插入右边if (bf == 1){subLR->_bf = 0;subL->_bf = -1;pParent->_bf = 0;}//插入左边else if (bf == -1){subLR->_bf = 0;subL->_bf = 0;pParent->_bf = 1;}else if (bf == 0){subLR->_bf = 0, subL->_bf = 0, pParent->_bf = 0;}else{assert(false);}}

右左双旋

左右双旋(不是单独的左右有一方低,有一方高)

(1)第一种情况,也是最特殊的情况,即parent的左子树只有两个节点

在这里插入图片描述

(2)第二种情况,parent的左右子树是高度为h的抽象子树,新增节点插入到c子树上

在这里插入图片描述

(3
)第三种情况,parent的左右子树是高度为h的抽象子树,新增节点插入到b子树上 实际上第二三种情况的分析是一致的

在这里插入图片描述

void RotateRL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;int bf = subRL->_bf;RotateR(subR);RotateL(pParent);//更新平衡因子//插入在右边if (bf == 1){subRL->_bf = 0;subR->_bf = 0;pParent->_bf = -1;}//插入在左边else if (bf == -1){subRL = 0;pParent->_bf = 0;subR->_bf = 1;}else if (bf == 0){subRL =pParent->_bf = subR->_bf = 0;}else{assert(false);}}

测试

	size_t _Height(Node* pRoot){if (pRoot == nullptr){return 0;}int leftHeight = _Height(pRoot->_pLeft);int rightHeight = _Height(pRoot->_pRight);return (leftHeight > rightHeight) ? leftHeight + 1 : rightHeight + 1;}bool _IsBalance(Node* pRoot){if (pRoot == nullptr){return true;}int leftHeight = _Height(pRoot->_pLeft);int rightHeight = _Height(pRoot->_pRight);//平衡因子异常的情况if (rightHeight - leftHeight != pRoot->_bf){cout << pRoot->_data << "平衡因子异常" << endl;return false;}//检查是否平衡return abs(rightHeight - leftHeight) < 2//检查、遍历左右子树&& _IsBalance(pRoot->_pLeft)&& _IsBalance(pRoot->_pRight);}bool IsBalance(){return _IsBalance(_pRoot);}int main()
{const int N = 30000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand());cout << v.back() << endl;}AVLTree<int> t;for (auto e : v){if (e == 41){t.Insert(e);}cout << "Insert:" << e << "->" << t.IsBalance() << endl;}cout << t.IsBalance() << endl;return 0;
}

全部代码

template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data = T()): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;AVLTreeNode<T>* _pRight;AVLTreeNode<T>* _pParent;T _data;int _bf;   // 节点的平衡因子
};// AVL: 二叉搜索树 + 平衡因子的限制
template<class T>
class AVLTree
{typedef AVLTreeNode<T> Node;
public:AVLTree(): _pRoot(nullptr){}// 在AVL树中插入值为data的节点bool Insert(const T& data){Node* cur = _pRoot;Node* parent = nullptr;//判断是否为空树if (_pRoot == nullptr){//直接插入_pRoot = new Node(data);//插入成功return true;}//寻找插入位置else{Node* parent = cur;while (cur){//记录父节点的位置,便于后续的链接操作parent = cur;//向左遍历if (cur->_data > data){cur = cur->_pLeft;}//向右遍历else if (cur->_data < data){cur = cur->_pRight;}//已有else return false;}cur = new Node(data);//插入+链接if (parent->_data > data){parent->_pLeft = cur;}else{parent->_pRight = cur;}//链接cur->_pParent = parent;}//更新平衡因子while (parent){if (cur == parent->_pRight){parent->_bf++;}else if (cur == parent->_pLeft){parent->_bf--;}if (parent->_bf == 0){//插入后子树稳定,不用向上更新平衡因子return true;}else if (parent->_bf == 1 || parent->_bf == -1){return true;}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){//右左双旋(不是单独的左右有一方低,有一方高)RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){//左右双旋(不是单独的左右有一方低,有一方高)RotateR(parent);}parent = parent->_pParent;cur = cur->_pParent; return true;}else{return false;}	}return true;}// AVL树的验证bool IsAVLTree(){return _IsAVLTree(_pRoot);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_pLeft);cout << root->_data << " ";_InOrder(root->_pRight);}void InOrder(){_InOrder(_pRoot);cout << endl;}// 根据AVL树的概念验证pRoot是否为有效的AVL树size_t _Height(Node* pRoot){if (pRoot == nullptr){return 0;}int leftHeight = _Height(pRoot->_pLeft);int rightHeight = _Height(pRoot->_pRight);return (leftHeight > rightHeight) ? leftHeight + 1 : rightHeight + 1;}bool _IsBalance(Node* pRoot){if (pRoot == nullptr){return true;}int leftHeight = _Height(pRoot->_pLeft);int rightHeight = _Height(pRoot->_pRight);//平衡因子异常的情况if (rightHeight - leftHeight != pRoot->_bf){cout << pRoot->_data << "平衡因子异常" << endl;return false;}//检查是否平衡return abs(rightHeight - leftHeight) < 2//检查、遍历左右子树&& _IsBalance(pRoot->_pLeft)&& _IsBalance(pRoot->_pRight);}bool IsBalance(){return _IsBalance(_pRoot);}// 右单旋void RotateR(Node* pParent){Node* pPnode = pParent->_pParent;Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;if (subLR){pParent->_pLeft = subL->_pRight;subL->_pParent = pParent;}subL->_pRight = pParent;pParent->_pParent = subL;//旋转部分子树if (pPnode){if (pPnode->_pLeft == pParent){pPnode->_pLeft = subL;subL->_pParent = pPnode;}else{pPnode->_pLeft = subL;subL->_pParent = pPnode;}}//旋转整棵子树else{_pRoot = subL;subL->_pParent = nullptr;}//调节平衡因子pParent->_bf = subL->_bf = 0;}// 左单旋void RotateL(Node* pParent){Node* pPnode = pParent->_pParent;Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;if (subRL){pParent->_pRight = subRL;subRL->_pParent = pParent;}subR->_pLeft = pParent;pParent->_pParent = subR;//链接:旋转整棵树if (pPnode == nullptr){_pRoot = subR;subR->_pParent = nullptr;}//链接:旋转子树else{if (pPnode->_pLeft == pParent){pPnode->_pLeft = subR;subR->_pParent = pPnode;}else if (pPnode->_pRight == pParent){pPnode->_pRight = subR;subR->_pParent = pPnode;}}//更新平衡因子pParent->_bf = subR->_bf = 0;}// 右左双旋void RotateRL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;int bf = subRL->_bf;RotateR(subR);RotateL(pParent);//更新平衡因子//插入在右边if (bf == 1){subRL->_bf = 0;subR->_bf = 0;pParent->_bf = -1;}//插入在左边else if (bf == -1){subRL = 0;pParent->_bf = 0;subR->_bf = 1;}else if (bf == 0){subRL =pParent->_bf = subR->_bf = 0;}else{assert(false);}}// 左右双旋void RotateLR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;int bf = subLR->_bf;RotateL(subL);RotateR(pParent);//更新平衡因子//插入右边if (bf == 1){subLR->_bf = 0;subL->_bf = -1;pParent->_bf = 0;}//插入左边else if (bf == -1){subLR->_bf = 0;subL->_bf = 0;pParent->_bf = 1;}else if (bf == 0){subLR->_bf = 0, subL->_bf = 0, pParent->_bf = 0;}else{assert(false);}}
private:Node* _pRoot;
};//int main()
//{
//	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15};
//	int a[] = { 4,2,6,13,5,15,7,16,14 };
//	AVLTree<int> t;
//	for (auto e : a)
//	{
//		t.Insert(e);
//	}
//	t.InOrder();
//	return 0;
//}
int main()
{const int N = 30000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand());cout << v.back() << endl;}AVLTree<int> t;for (auto e : v){if (e == 41){t.Insert(e);}cout << "Insert:" << e << "->" << t.IsBalance() << endl;}cout << t.IsBalance() << endl;return 0;
}

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

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

相关文章

学习模拟简明教程【Learning to simulate】

深度神经网络是一项令人惊叹的技术。 有了足够的标记数据&#xff0c;他们可以学习为图像和声音等高维输入生成非常准确的分类器。 近年来&#xff0c;机器学习社区已经能够成功解决诸如对象分类、图像中对象检测和图像分割等问题。 上述声明中的加黑字体警告是有足够的标记数…

OpenHarmony源码下载

OpenHarmony源码下载 现在的 OpenHarmony 4.0 源码已经有了&#xff0c;在 https://gitee.com/openharmony 地址中&#xff0c;描述了源码获取的方式&#xff0c;但那是基于 ubuntu 或者说是 Linux 的下载方式。在 windows 平台下的下载方式没有做出介绍。 我自己尝试了 wind…

PCIe协议加持,SD卡9.1规范达到媲美SSD的速度4GB/s

近日&#xff0c;SD协会&#xff08;SDA&#xff09;宣布了最新的SD Express存储卡的进化&#xff0c;将microSD Express存储卡的速度提高了一倍&#xff0c;达到2GB/s&#xff0c;并引入了4个新的SD Express速度等级&#xff0c;以确保新的SD 9.1规范中最低的顺序性能水平。这…

【Qt开发流程】之HelloWorld程序

【Qt开发流程】之HelloWorld程序 目的编写程序新建项目文件说明及界面设计 程序运行及发布程序运行程序发布手动构建使用windeployqt进行构建 设置应用程序图标修改快捷键类型列表命令行编译程序命令行编译.ui文件自定义类项目模式及项目文件介绍项目模式项目文件 目的 从Hell…

通过bat脚本控制Oracle服务启动停止

1、将Oracle服务全部设置为手动启动 初始安装Oracle之后服务启动状态&#xff1a; 2、服务功能介绍 3、构建服务启动/停止bat脚本 注意&#xff1a;编码选择ANSI(如果编码不是ANSI运行脚本会显示乱码) echo off :main cls echo 注&#xff1a;请保证该脚本是使用管理员权限…

Iceberg学习笔记(1)—— 基础知识

Iceberg是一个面向海量数据分析场景的开放表格式&#xff08;Table Format&#xff09;&#xff0c;其设计的目的是解决数据存储和计算引擎之间的适配的问题 表格式&#xff08;Table Format&#xff09;可以理解为元数据以及数据文件的一种组织方式&#xff0c;处于计算框架&…

Java —— 抽象类和接口

目录 1. 抽象类 1.1 抽象类概念 1.2 抽象类语法与特性 1.3 抽象类的作用 2. 接口 2.1 接口的概念 2.2 接口的语法规则与特性 2.3 实现多个接口(解决多继承的问题) 2.4 接口间的继承 2.5 抽象类和接口的区别 2.6 接口的使用实例 2.7 Clonable 接口和深拷贝 2.7.1 Cloneable接口 …

探索arkui(2)--- 布局(列表)--- 1(列表数据的展示)

前端开发布局是指前端开发人员宣布他们开发的新网站或应用程序正式上线的活动。在前端开发布局中&#xff0c;开发人员通常会展示新网站或应用程序的设计、功能和用户体验&#xff0c;并向公众宣传新产品的特点和优势。前端开发布局通常是前端开发领域的重要事件&#xff0c;吸…

Apache Airflow (八) :DAG任务依赖设置

&#x1f3e1; 个人主页&#xff1a;IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;加入大数据技术讨论群聊&#xff0c;获取更多大数据资料。 &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你大数据的个人空间-豹…

44、echarts图形自动轮播tooltip提示,并显示高亮

自动轮播方法 参数myChart代表echarts的实例名称, options指定图表的配置项和数据, num类目数量(原因&#xff1a;循环时达到最大值后&#xff0c;使其从头开始循环), time轮播间隔时长 //自动轮播显示高亮--tooltip提示 export function autoHover(myChart, option, num, ti…

【漏洞复现】IP-guard WebServer 远程命令执行

漏洞描述 IP-guard是一款终端安全管理软件,旨在帮助企业保护终端设备安全、数据安全、管理网络使用和简化IT系统管理。互联网上披露IP-guard WebServer远程命令执行漏洞情报。攻击者可利用该漏洞执行任意命令,获取服务器控制权限。 免责声明 技术文章仅供参考,任何个人和…

2024年软件测试面试必看系列,看完去面试你会感谢我的!!

朋友圈点赞的测试用例 功能测试 1点赞后是否显示结果 2.点赞后是否可以取消; 3.点赞取消后是否可以重复点赞; 4.共同好友点赞后&#xff0c;是否有消息提醒; 5.非共同好友点赞后&#xff0c;是否有消息提醒; 6.点击点赞人昵称&#xff0c;是否可以跳转到他/她的主页; 7.自己能…

Spring IOC/DI和MVC及若依对应介绍

文章目录 一、Spring IOC、DI注解1.介绍2.使用 二、Spring MVC注解1.介绍2.使用 一、Spring IOC、DI注解 1.介绍 什么是Spring IOC/DI&#xff1f; IOC(Inversion of Control&#xff1a;控制反转)是面向对象编程中的一种设计原则。其中最常见的方式叫做依赖注入&#xff08;…

【考研】数据结构(更新到顺序表)

线性表的定义和基本操作 学习目标 线性表定义&#xff1a;具有相同数据类型的n个数据元素的有序序列。 顺序表定义&#xff1a; 特点 基本操作 定义 静态&#xff1a; #include<stdio.h> #include<stdlib.h>#define MaxSize 10//静态 typedef struct{int …

Sonar生成PDF错误Can‘t get Compute Engine task status.Retry..... HTTP error: 401

报错及修改&#xff1a; 报错&#xff1a;INFO: Can’t get Compute Engine task status.Retry… org.sonarqube.ws.connectors.ConnectionException: HTTP error: 401, msg: , query: org.apache.commons.httpclient.methods.GetMethod7a021f49 ERROR: Problem generating PD…

K8S基础笔记

1、namespace 名称空间用来对集群资源进行隔离划分&#xff0c;默认只隔离资源&#xff0c;不隔离网络k8s默认的名称空间为default 查看k8s的所有命名空间 kubectl get namespace 或者 kubectl get ns 创建名称空间 kubectl create ns 名称 或使用yaml方式 编写yamlkub…

Linux---(七)Makefile写进度条(三个版本)

文章目录 一、前提引入&#x1f397;️下面的代码什么现象&#xff1f;&#x1f397;️下面的代码什么现象&#xff1f; 二、缓冲区三、回车换行&#x1f397;️注意&#x1f397;️图解&#x1f397;️老式回车键造型&#xff08;意思是充当两个动作&#xff09;&#x1f397;…

【Python】给定n个十六进制正整数,输出它们对应的八进制数。

3.问题描述 给定n个十六进制正整数&#xff0c;输出它们对应的八进制数。 样例输入 2 39 123ABC 样例输出 71 4435274 n int(input()) li [] # 创建列表 for i in range(n):li.append(input()) # 输入数据 for num in li:if len(num) < 100000: # 判断长度是否符…

Spring IOC - Bean的生命周期之依赖注入

在Spring启动流程中&#xff0c;创建的factoryBean是DefaultListableBeanFactory&#xff0c;其类图如下所示&#xff1a; 可以看到其直接父类是AbstractAutoireCapableBeanFactory&#xff0c;他主要负责完成Bean的自动装配和创建工作。 具体来说&#xff0c;AbstractAutowire…

计算机网络———ipv6简解

文章目录 1.前言&#xff1a;2. ipv6简单分析&#xff1a;2.1.地址长度对比2.2. ipv6包头分析2.3. ipv6地址的压缩表示&#xff1a;2.3. NDP&#xff1a;2.4. ipv6地址动态分配&#xff1a; 1.前言&#xff1a; 因特网地址分配组织)宣布将其最2011年2月3日&#xff0c;IANA (In…