当前位置: 首页 > news >正文

C++之AVL树

目录

AVL树的概念

AVL树的操作

元素的插入

查找合适的位置

更新平衡因子和AVL树的旋转操作

AVL树的旋转分类

右单旋

左单旋

左右双旋

右左双旋 

 求AVL树的高度

判断AVL树是否是平衡树

测试代码 

AVL树的性能


之前我们已经学习了搜索二叉树,我们已经知道了搜索二叉树在插入有序的元素时,最终会退化为链表结构,导致查找元素的效率变低,所以我们引申出了平衡搜索二叉树,平衡搜索二叉树有两种,一种是通过高度平衡,一种是通过颜色来平衡。本期我们学习的是基于高度来进行平衡的平衡搜索二叉树------AVL树。

AVL树的概念

 AVL树是由俄罗斯科学家G. M. Adelson-Velsky和E. M. Landis发明的一种以高度进行平衡的平衡搜索二叉树。

AVL树具有以下特点:

  1. AVL树的任意一个子树都是AVL树。
  2. AVL树的任意一个节点的平衡因子的绝对值不超过1(-1,0,1)。 

何为平衡因子,平衡因子就是当前节点的所在的树的右子树的高度-左子树高度所得的值。 

AVL树的结构如下。

template<class k,class v>
struct AVLTreeNode
{AVLTreeNode<k, v>* _left;AVLTreeNode<k, v>* _right;AVLTreeNode<k, v>* _parent;pair<k, v> _pair;//平衡因子int _bf;//构造函数进行初始化AVLTreeNode(const pair<k, v>& pair):_left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){_pair = pair;}
};template<class k,class v>
class AVLTree
{typedef AVLTreeNode<k, v> Node;
public:AVLTree(){_root = nullptr;}
private:Node*_root;
};

AVL树的操作

元素的插入

在AVL树中元素的插入分为三步:

  1. 与普通搜索二叉树的插入一样,先找到合适的位置,然后将元素插入。
  2. 更新平衡因子。
  3. 根据平衡因子进行AVL树的旋转,保证整个树符合AVL的特点。至于旋转有几种,我们下面会讲到。 

查找合适的位置

	if (_root == nullptr){_root = new Node(pair);return true;}//1.找到合适的位置将元素插入Node* parent = nullptr;Node* cur = _root;while (cur){if ( pair.first<cur->_pair.first ){parent = cur;cur = cur->_left;}else if (cur->_pair.first < pair.first){parent = cur;cur = cur->_right;}else{return false;}}//插入当前元素cur = new Node(pair);if (pair.first < parent->_pair.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}

更新平衡因子和AVL树的旋转操作

while(parent){//2.平衡因子的更新if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}//3.根据平衡因子,判断当前节点所在的树需不需要进行旋转,1,-1,0不用旋转,2,-2要进行旋转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){RRotate(parent);}else if (parent->_bf == 2 && cur->_bf == 1){LRotate(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RLRotate(parent);}else if (parent->_bf == -2 && cur->_bf == 1){LRRotate(parent);}else{assert(false);}break;}else{assert(false);}}
  • 如果左边都比右边高,就进行右单旋。 
  • 如果右边都比左边高,就进行左单旋。
  • 如果从下往上看,cur是左边比右边高,parent右边比左边高,就进行右左双旋。
  • 如果从下往上看,cur是右边比左边高,parent左边比右边高,就进行左右双旋。

AVL树的旋转分类

右单旋

右旋的示意图如下。

 

代码如下。 

	//右旋void RRotate(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentparent = parent->_parent;subL->_right = parent;parent->_parent = subL;parent->_left = subLR;if (subLR){subLR->_parent = parent;}if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (parent == parentparent->_left){parentparent->_left=subL;subL->_parent = parentparent;}else{parentparent->_right=subL;sub->_parent = parent->_parent;}}subL->_bf = parent->_bf = 0;}
左单旋

左单旋示意图如下。

代码如下。

	//左单旋void LRotate(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentparent = parent->_parent;parent->_right = subRL;if (subRL){subRL->_parent = parent;}subR->_left = parent;parent->_parent = subR;if (parent == parentparent->_left){parentparent->_left = subR;subR->_parent = parentparent;}else{parentparent->_right = subR;subR->_parent = parentparent;}parent->_bf = subR->_bf = 0;}
左右双旋

左右双旋示意图如下。

对于左右双旋而言,平衡因子的更新使我们要格外注意的,因为双旋是基于两个单旋的,但是单旋之后,两个节点的平衡因子是全部变成0的,但是实际上,元素插入位置的不同,最终单旋之后的平衡因子并不一定是0,这是要根据插入的位置进行更改的。图示如下。

代码如下。

	void LRRotate(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;LRotate(subL);RRotate(parent);if (subLR->_bf == -1){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (subLR->_bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else{subLR->_bf = subL->_bf = parent->_bf = 0;}else{assert(false);}}
右左双旋 

右左双旋示意图如下。

 

同样的,对于右左双旋而言,平衡因子的更新如左右双旋一样,也是需要在插入的元素的位置不同时,进行修改的。示意图如下。 

 

右左双旋代码。

	//右左双旋void RLRotate(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;//这里要用刚开始的平衡因子,在进行左右双旋之后,平衡因子会发生变化int bf = subRL->_bf;RRotate(subR);LRotate(parent);if (bf == 1){subRL->_bf = 0;parent->_bf = -1;subR->_bf = 0;}else if (bf == -1){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 1;}else if (bf == 0){subRL->_bf = subR->_bf = parent->_bf = 0;}else{assert(false);}}

 求AVL树的高度

	int height(){_height(_root);}int _height(Node* root){if (root == nullptr){return 0;}else{int LeftHeight = _height(root->_left);int RightHeight = _height(root->_right);return (LeftHeight > RightHeight) ? LeftHeight + 1 : RightHeight + 1;}}

与之前求二叉树的高度的代码类似,即求出左右子树的高度,整个二叉树的高度就是左右子树中较大的+1。

 

判断AVL树是否是平衡树

bool IsBalance(){return _IsBalance(_root);}//判断AVL树是否是平衡树
bool _IsBalance(Node* root){if (root == nullptr){return true;}//对当前树进行检查int LeftHeight = _height(root->_left);int RightHeight = _height(root->_right);if (RightHeight - LeftHeight != root->_bf){cout << root->_pair.first << "现在是:" << root->_bf << endl;cout << root->_pair.first << "应该是:" << RightHeight - LeftHeight << endl;return false;}return abs(RightHeight - LeftHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}

判断AVL树是否是平衡树,即判断右子树的高度减去左子树的高度是否等于根节点的平衡因子的值。这里我们用到了子函数,因为在类外我们无法访问根节点。基于此情况,我们一般设置子函数来进行函数的设置,这也是之后进行编码的一种方法。 

测试代码 


void TestAVLTree()
{AVLTree<int, int> t;//int a[] = {5,4,3,2,1,0};int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){t.insert(make_pair(e,e));cout << "Insert" << e << ":" << t.IsBalance() << endl;}t.InOrder();cout << endl;cout << t.IsBalance() << endl;
}

运行结果如下。

 插入的元素在进行了右左双旋之后成为了AVL树,符合预期。

AVL树的性能

因为AVL树的特点保证了任意一个子树的左右高度差的绝对值不超过1,所以一般情况下,我们会认为AVL树就是一个完全二叉树,所以AVL树的高度我们就认为是logN,所以在查找元素时的事假复杂度就是O(logN)。所以,对于AVL树而言,查找一个元素的效率是很高的。

但是,在插入元素时,往往伴随着AVL树的旋转,有时候每插入一个元素就会进行一次旋转,所以旋转的次数较多。

基于此,如果是想创建一个查找的数据结构,推荐使用AVL树,但是如果对应的修改操作非常多,则不推荐使用AVL树。

以上便是AVL树的所有内容。

本期内容到此结束^_^

 


http://www.mrgr.cn/news/28036.html

相关文章:

  • openai 论文Scaling Laws for Neural Language Models学习
  • 2024 同一个网段,反弹shell四种方法【linux版本】bash、python、nc、villian反弹shell图解步骤
  • vue项目npm run serve出现【- Network: unavailable】(从排查到放弃)
  • UEFI学习(五)——启动框架
  • 嵌入式交叉编译:glib(未成功)
  • 自动驾驶仿真:软件在环(SIL)测试详解(精简版入门)
  • VUE3初学者必备的快速开发入门指南
  • 系统架构设计师教程 第5章 5.6 基于构件的软件工程 笔记
  • Dubbo从入门到实战
  • ??Nginx实现会话保持_Nginx会话保持与Redis的结合_Nginx实现四层负载均衡
  • 嵌入式通信原理—SPI总线通信原理与应用
  • 2024中国算力大会 2024 China Computational Power Conference
  • GB28181在融合指挥调度系统应用方案探究和技术实现
  • 解决跨境电商平台账号无法访问的常见问题
  • springboot老年康复中心—计算机毕业设计源码27406
  • FreeRTOS实战指南 — 3.1 C语言链表
  • 斗破C++编程入门系列之二十七:数组、指针和字符串:string类(一星斗师)
  • 【C++】unordered系列
  • MongoDB的详细安装教程
  • string类的模拟实现
  • 【AI大模型】ChatGPT模型原理介绍(下)
  • 编程辅助工具下一个热门应用场景是什么?(一)
  • Java:继承和多态(2)
  • matlab边缘点提取函数
  • 106、解析Java中1000个常用类:Timer类,你学会了吗?
  • 猫头虎分享:Python库 SQLAlchemy 的简介、安装、用法详解入门教程