【C++】AVL树详解

目录

一、AVL树的概念

二、AVL树节点的定义

三、AVL树的操作

3.1 AVL树的平衡因子

3.2 AVL树的插入

3.3 AVL树的旋转

3.4 AVL树的验证

 四、AVL树的完整代码


上一篇已经对关联式容器set/map/multiset/multimap进行了简答的介绍,大家可能发现它们有一个共同点:其底层都是按照二叉搜索树来实现的,但是学习二叉搜索树时,已经知道当树中插入的元素有序或接近有序时,二叉搜索树的会变得极不平衡,查找操作的时间复杂度可能达到 O(n),甚至退化成链表。因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

一、AVL树的概念

AVL树是一种自平衡二叉搜索树,它得名于它的发明者 Adelson-Velsky 和 Landis。AVL树通过在每次插入或删除节点时进行旋转操作来保持树的平衡,以确保每个结点的左右子树高度之差的绝对值不超过1,降低树的高度,从而实现较高效率的查找、插入和删除操作。

AVL树的性质:

  • 它的左右子树都是AVL树
  • 每个节点的左子树和右子树的高度差(平衡因子)不超过1。
  • 如果插入或删除操作导致树失去平衡,AVL树会通过旋转操作(包括单旋转和双旋转)来重新平衡。
  • 若一个AVL树有n个节点,它的查找、插入和删除操作的时间复杂度都是 O(log_2 n),高度可保持在 O(log_2 n)。

二、AVL树节点的定义

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;//平衡因子balance factorAVLTreeNode(const pair<K,V> kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}
};

三、AVL树的操作

3.1 AVL树的平衡因子

AVL 树的平衡因子是指每个节点的左子树高度减去右子树高度的值,即左子树高度 - 右子树高度。对于任意一个节点,其平衡因子可以是 -1、0 或 1。

AVL 树的平衡因子定义如下:

  • 如果一个节点的平衡因子为 -1,表示该节点的左子树比右子树高度高 1;
  • 如果一个节点的平衡因子为 0,表示该节点的左子树和右子树高度相等;
  • 如果一个节点的平衡因子为 1,表示该节点的右子树比左子树高度高 1。

AVL 树通过保持每个节点的平衡因子为 -1、0 或 1 来确保树的平衡。当插入或删除节点后,需要通过旋转操作来调整各个节点的平衡因子,以确保整棵树仍保持平衡状态。

3.2 AVL树的插入

在 AVL 树中插入元素的过程如下:

  1. 按照二叉搜索树的规则,找到新元素应该插入的位置,将其作为叶子节点插入到树中
  2. 在插入新元素后,从插入点开始向上回溯,更新每个祖先节点的平衡因子,并检查它们是否失去了平衡。
  3. 如果发现某个节点失去了平衡,则需要对其进行旋转操作,以恢复整棵树的平衡。

步骤一:按照二叉搜索树的规则插入新元素

template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){Node* pnode = new Node(kv);if (_root == nullptr){_root = pnode;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < pnode->_kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > pnode->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}if (parent->_kv.first < pnode->_kv.first){parent->_right = pnode;pnode->_parent = parent;}else{parent->_left = pnode;pnode->_parent = parent;}//开始处理平衡因子//...private:Node* _root = nullptr;
};

步骤二:更新每个祖先节点的平衡因子,检查它们是否失去了平衡。

在插入元素之前,parent的平衡因子分为三种情况:0,1,-1,插入后分为以下两种情况:

  • 如果cur插入到parent的左侧,只需给parent的平衡因子-1即可
  • 如果cur插入到parent的右侧,只需给parent的平衡因子+1即可
bool Insert(const pair<K, V>& kv){//.....//开始处理平衡因子cur = pnode;while (parent){if (cur == parent->_left){parent->_bf--;}else{parent->_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){//失去了平衡,需要进行旋转//旋转操作。。。}else{assert(false);}}return true;}

插入后,parent节点的平衡因子发生变化,有这么三种情况:0,正负1, 正负2。

  • 情况1:parent的平衡因子变为0(parent的平衡因子一定是由1或-1变成0)

9的平衡因子由-1变成0,但是以9为根节点的这棵树整体高度不变,所以不会再对9的祖先节点造成影响,所以不需要再向上更新平衡因子,也不需要进行旋转的操作。
同理,由1变为0也是一样

操作:parent的平衡因子变为0时,不需要再向上更新平衡因子。

  • 情况2:parent的平衡因子变为 -1/1(parent的平衡因子一定是0由变成1或-1)

插入1后,1的父节点3的平衡因子变成-1,3这个节点的平衡因子是正常的,但是3的平衡因子从0变成-1时,它的左边变高了,3的父节点9的平衡因子变成-2,发生错误,我们就需要对9进行调整同理,如果变成1也是一样(例如插入18)。
所以插入新节点后,新节点的父节点(parent)的平衡因子如果变成了-1或1,我们就需要沿着这个父节点到根的路径依次更新父节点的祖先节点的平衡因子,如果某个祖先节点平衡因子更新后变成2或者-2(绝对值大于1),就立即进行旋转调整.

  • 情况3:parent的平衡因子变为 -2/2,此时违反平衡树的性质,需要对其进行旋转处理。

步骤三:如果发现某个节点失去了平衡,则需要对其进行旋转操作。

3.3 AVL树的旋转

根据节点插入位置的不同,AVL树的旋转分为四种:

1. 新节点插入较高左子树的左侧---左左:右单旋

右单旋步骤如下:

  1. 设key为12的节点为parent,parent的左节点为cur(key为6),cur的右节点为curR
  2. 将curR链接在parent的左边,parent链接在cur的右边。
  3. 调整各节点的父节点
  4. 将parent和cur的平衡因子置0。

注:

  • 使用右单旋的情况为:parent->_bf == -2 && cur->_bf == -1
  • 图中的a,b,c 均为抽象节点,h 可取值0、1、2...,虽然不同大小的h对应的情况很多,在此也不一一赘叙、但经过总结,只要满足新节点插入较高左子树的左侧,就可以使用右旋。
    下面是h=0 和h = 1的右单旋示例:(这里将cur命名为subL,curR命名为subLR)
  • 需要注意的是,此处的parent 可能是根节点,也可能是子树。如果是子树,可能是左子树也可能是右子树,这时就需要先备份一份parent的parent(下面命名为parentParent),将旋转后的根节点的_parent和parentParent指向parent的指针修改。
  • cur节点的右孩子可能存在,也可能不存在。当h等于0时,cur就没有右孩子。

右单旋的实现如下:

//右单旋void RotateR(Node* parent){Node* cur = parent->_left;Node* curR = cur->_right;//因为parent可能是棵子树,因此在更新其双亲前必须先保存parent的双亲Node* parentParent = parent->_parent;parent->_left = curR;parent->_parent = cur;cur->_right = parent;cur->_parent = parentParent;//旋转完成之后,cur的右孩子作为parent的左孩子//如果cur的右孩子存在,更新亲双亲if (curR){curR->_parent = parent;}// 如果parent是根节点,_root指向新的根节点if (_root == parent){_root = cur;}else //否则就要判断原来的parent是父节点的左子树还是右子树{if (parentParent->_left == parent){parentParent->_left = cur;}else{parentParent->_right = cur;}}// 根据调整后的结构更新部分节点的平衡因子parent->_bf = cur->_bf = 0;}

2. 新节点插入较高右子树的右侧---右右:左单旋

使用左单旋的情况为:parent->_bf == 2 && cur->_bf == 1

左单旋步骤如下:

  1. 将curL链接在parent的右边
  2. 将parent链接在cur的左边
  3. 调整各节点的父节点
  4. 将cur和parent的平衡因子调整为0

 左单旋的抽象节点分析与右单旋类似,所以在此不再花费篇幅进行讲解

左单旋的实现如下:

//左单旋void RotateL(Node* parent){Node* parentParent = parent->_parent;Node* cur = parent->_right;Node* curL = cur->_left;parent->_right = cur->_left;parent->_parent = cur;cur->_parent = parentParent;cur->_left = parent;if (curL){curL->_parent = parent;}if (_root == parent){_root = cur;//cur->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = cur;}else{parentParent->_right = cur;}}parent->_bf = cur->_bf = 0;}

3.新节点插入较高左子树的右侧---左右:先左单旋再右单旋

使用左右双旋的情况为:parent->_bf == -2 && cur->_bf == 1

旋转步骤:

  1. 对cur进行左单旋(cur为parent的左节点)
  2. 对parent进行右单旋
  3. 调整平衡因子

对抽象节点进行分析:

  • 当 h=0 时,curR就是新增(curR->_bf = 0),旋转后curR = 0,cur = 0,parent = 0
  • 当 h=1 时,新增节点可能是curR的左节点,也可能是右节点。
    若为左节点,curR->_bf = -1,cur->_bf = 1,parent->_bf = -2。
    旋转后:curR->_bf = 0,cur->_bf= 0,parent->_bf = 1
    若为右节点,curR->_bf = 1,cur->_bf = 1,parent->_bf = -2。
    旋转后:curR->_bf = 0,cur->_bf = -1,parent->_bf = 0
  • 当 h=2、3、4...,结果与h=1相同

综上,平衡因子调整规则为:

  • 当curR->_bf = 0时:调整为curR->_bf = 0,cur->_bf = 0,parent->_bf = 0
  • 当curR->_bf = -1时:调整为curR->_bf = 0,cur->_bf= 0,parent->_bf = 1
  • 当curR->_bf = 1时:调整为curR->_bf = 0,cur->_bf = -1,parent->_bf = 0

注:

  • 旋转之前,保存curR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子。

左右双旋实现如下:

//先左再右void RotateLR(Node* parent){//对parent的left左旋,再对parent右旋Node* cur = parent->_left;Node* curR = cur->_right;int bf = curR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0)//curR是新增的{parent->_bf = cur->_bf = curR->_bf = 0;}else if (bf == -1)//curR的left是新增的{parent->_bf = 1;cur->_bf = 0;curR->_bf = 0;}else if (bf == 1)//curR的right是新增的{parent->_bf = 0;cur->_bf = -1;curR->_bf = 0;}else{assert(false);}}

4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

使用右左双旋的情况为:parent->_bf == 2 && cur->_bf == -1

旋转步骤:

  1. 对subR进行右单旋
  2. 对parent进行左单旋
  3. 调整平衡因子

 右左双旋对抽象节点进行分析与左右双旋类似,大家可自行尝试分析。

平衡因子调整规则为:

  • 当curR->_bf = 0时:调整为curR->_bf = 0,cur->_bf = 0,parent->_bf = 0
  • 当curR->_bf = -1时:调整为curR->_bf = 0,cur->_bf= 1,parent->_bf = 0
  • 当curR->_bf = 1时:调整为curR->_bf = 0,cur->_bf = 0,parent->_bf = -1
//先右再左void RotateRL(Node* parent){//对parent的right右旋,再对parent左旋Node* cur = parent->_right;Node* curL = cur->_left;int bf = curL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0)//curL是新增的{parent->_bf = cur->_bf = curL->_bf = 0;}else if (bf == -1)//curL的left是新增的{parent->_bf = 0;cur->_bf = 1;curL->_bf = 0;}else if (bf == 1)//curL的right是新增的{parent->_bf = -1;cur->_bf = 0;curL->_bf = 0;}else{assert(false);}}

3.4 AVL树的验证

如果要验证一个二叉搜索树是否为AVL 树,则需要检查每个节点的平衡因子是否符合 AVL 树的定义,即平衡因子为 -1、0 或 1,并且整棵树中的每个节点都满足 AVL 树的平衡性质。

验证 AVL 树的一般步骤如下

  1. 对树中的每个节点,计算其左子树的高度和右子树的高度;
  2. 计算每个节点的平衡因子(左子树高度 - 右子树高度);
  3. 检查每个节点的平衡因子是否为 -1、0 或 1,如果不是,则说明该节点不满足 AVL 树的平衡性质;
  4. 递归地对树中的每个节点进行上述检查,确保整棵树都符合 AVL 树的定义。

验证的实现:

	//判断是否平衡bool IsBalance(){return _IsBalance(_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(Node* root){if (root == nullptr){return true;}int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}return abs(rightHeight - leftHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}

 四、AVL树的完整代码

#pragma once
#include <assert.h>
#include<iostream>
using namespace std;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;//平衡因子balance factorAVLTreeNode(const pair<K,V> kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){Node* pnode = new Node(kv);if (_root == nullptr){_root = pnode;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < pnode->_kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > pnode->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}if (parent->_kv.first < pnode->_kv.first){parent->_right = pnode;pnode->_parent = parent;}else{parent->_left = pnode;pnode->_parent = parent;}//开始处理平衡因子cur = pnode;while (parent){if (cur == parent->_left){parent->_bf--;}else{parent->_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 if (parent->_bf == -2 && cur->_bf == 1){//先左旋再右旋RotateLR(parent);}else{assert(false);}break;}else{assert(false);}}return true;}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){return _IsBalance(_root);}
private://左旋void RotateL(Node* parent){Node* parentParent = parent->_parent;Node* cur = parent->_right;Node* curL = cur->_left;parent->_right = cur->_left;parent->_parent = cur;cur->_parent = parentParent;cur->_left = parent;if (curL){curL->_parent = parent;}if (_root == parent){_root = cur;//cur->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = cur;}else{parentParent->_right = cur;}}parent->_bf = cur->_bf = 0;}//右旋void RotateR(Node* parent){Node* cur = parent->_left;Node* curR = cur->_right;Node* parentParent = parent->_parent;parent->_left = curR;parent->_parent = cur;cur->_right = parent;cur->_parent = parentParent;if (curR){curR->_parent = parent;}if (_root == parent){_root = cur;}else{if (parentParent->_left == parent){parentParent->_left = cur;}else{parentParent->_right = cur;}}parent->_bf = cur->_bf = 0;}//先右再左void RotateRL(Node* parent){//对parent的right右旋,再对parent左旋Node* cur = parent->_right;Node* curL = cur->_left;int bf = curL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0)//curL是新增的{parent->_bf = cur->_bf = curL->_bf = 0;}else if (bf == -1)//curL的left是新增的{parent->_bf = 0;cur->_bf = 1;curL->_bf = 0;}else if (bf == 1)//curL的right是新增的{parent->_bf = -1;cur->_bf = 0;curL->_bf = 0;}else{assert(false);}}//先左再右void RotateLR(Node* parent){//对parent的left左旋,再对parent右旋Node* cur = parent->_left;Node* curR = cur->_right;int bf = curR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0)//curR是新增的{parent->_bf = cur->_bf = curR->_bf = 0;}else if (bf == -1)//curR的left是新增的{parent->_bf = 1;cur->_bf = 0;curR->_bf = 0;}else if (bf == 1)//curR的right是新增的{parent->_bf = 0;cur->_bf = -1;curR->_bf = 0;}else{assert(false);}}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;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}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->_kv.first << "平衡因子异常" << endl;return false;}return abs(rightHeight - leftHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}private:Node* _root = nullptr;
};

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

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

相关文章

js滚动table动画

requestAnimationFrame()告诉浏览器你希望执行一个动画&#xff0c;&#xff0c;并且要求浏览器在下次重绘之前调用指定的回调&#xff0c;&#xff0c;更新动画。。。 请求动画帧&#xff0c;&#xff0c;也称帧循环&#xff0c;&#xff0c;&#xff0c; 改api能以浏览器的显…

基于ssm旅社客房收费管理系统+vue

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

如何选择合适的幻兽帕鲁服务器CPU核心数和内存大小,避免丢包和延迟高?

根据幻兽帕鲁服务器的实际案例分析选择合适的CPU核心数和内存大小以避免丢包和延迟高&#xff0c;首先需要考虑的是服务器的性能需求。幻兽帕鲁服务器推荐使用4核CPU和16GB内存&#xff0c;建议使用32GB以上的内存才能稳定运行。这表明对于幻兽帕鲁这样的游戏服务器来说&#x…

珠宝店如何利用微信管理系统提高效率和竞争力?

珠宝店可以利用微信管理系统来提高自身的业务效率和客户满意度。以下是微信管理系统在珠宝店中的应用&#xff1a; 1. 客户管理&#xff1a;珠宝店可以将客户导入微信&#xff0c;通过微信管理系统对客户进行管理。可以对客户进行标签管理&#xff0c;了解客户的购买偏好和需求…

JS api基础初学

轮播图随机版 需求&#xff1a;当我们刷新页面&#xff0c;页面中的轮播图会显示不同图片以及样式 分析&#xff1a;①&#xff1a;准备一个数组对象&#xff0c;里面包含详细信息&#xff08;素材包含&#xff09; ②&#xff1a;随机选择一个数字&#xff0c;选出数组对应…

IGCSE-Physics-Chapter10-课堂总结(编辑中)

10.2-Specific heat capacity(比热容) Energy and temperature Internal energy(内能) includes both the kinetic energy of the particles and chemical potential energy of the bonds between them. Energy and temperature are not the same thing.The internal energy…

excel导入标准化

excel导入较导出还是复杂一些&#xff0c;一般分为三个步骤.市面上低代码平台可以将常用的操作固化&#xff0c;并且形成收益&#xff0c;这也是挺好的。我将我的一些总结分享到网上也是我自己乐意的。毕竟尊重技术的还是搞技术的自身&#xff0c;一般企业老板并不太关心技术代…

期货开户始终坚持自己的交易系统

做期货其实很简单&#xff0c;赚钱的技术重复做&#xff01;期货交易中最重要的是什么&#xff1f;就是坚定自己的交易理念。小编为大家介绍。 1、市场用最朴实无华的的现实告诉了我们颠扑不破的真理&#xff1a;投资市场永恒的规律是90%的人都亏损的规律&#xff0c;任何人都…

windows安装部署node.js以及搭建运行第一个Vue项目

一、官网下载安装包 官网地址&#xff1a;https://nodejs.org/zh-cn/download/ 二、安装程序 1、安装过程 如果有C/C编程的需求&#xff0c;勾选一下下图所示的部分&#xff0c;没有的话除了选择一下node.js安装路径&#xff0c;直接一路next 2、测试安装是否成功 【winR】…

Flutter中Future和Stream关系

Future和Stream类是Dart异步编程的核心。 Future 表示一个不会立即完成的计算过程。与普通函数直接返回结果不同的是异步函数返回一个将会包含结果的 Future。该 Future 会在结果准备好时通知调用者。 Stream 是一系列异步事件的序列。其类似于一个异步的 Iterable&#xff0c;…

应用稳定性优化1:ANR问题全面解析

闪退、崩溃、无响应、重启等是应用稳定性常见的问题现象&#xff0c;稳定性故障大体可归类为ANR/冻屏、Crash/Tombstone、资源泄露三大类。本文通过对三类故障的产生原因、故障现象、触发机制及如何定位等&#xff0c;展开深度解读。 本文将详解ANR类故障&#xff0c;并通过一…

将镜像上传到私有镜像仓库Harbor

首先你需要安装Harbor服务&#xff1a; https://blog.csdn.net/qq_50247813/article/details/136388229 客户端已经安装docker&#xff1a; https://docs.docker.com/engine/install/centos/ 在docker客户端登录 Harbor 我的Harbor 服务器地址&#xff1a; 192.168.44.161 账号…