C++ AVL树(旋转)

我们之前学习了搜索二叉树,我们知道普通的搜索二叉树会有特殊情况出现使得二叉树的两枝极其不平衡形成我们通俗说的歪脖子树:

这样的树一定会使得我们的增删查的效率变低;为了避免这种极端的情况出现,在1962年有两位伟大的俄罗斯数学家Adelson-VelskyLandis发明的;它们通过旋转使得我们的树的任意节点的左右子树高度差不超过2;使得搜索二叉树总会是一颗平衡的搜索二叉树;

我们怎么知道我们的左右子树的高度差呢?

AVL树的节点

我们这个时候就需要在搜索二叉树的基础上对我们的节点进行修改了,给我们的节点增加一个平衡因子_balance factor(_bf);

_bf平衡因子

介绍_bf:

_bf是如何计算的呢?创作者规定 _bf=右子树的深度-左子树的深度; 也就是说,我们每次我们插入一个新节点到我们当前节点的左边的时候_bf需要减一,当新节点插入到我们当前节点的右边的时候_bf加一;

template<class K,class V>
struct AVLTreeNode
{typedef AVLTreeNode<K,V> node;node* _left;node* _right;node* _parent;pair<K,V> _data;int _bf;//balance factor平衡因子AVLTreeNode(const pair<K, V>& data):_left(nullptr), _right(nullptr),_parent(nullptr),_data(data),_bf(0){}
};

可以看到,我们的AVL树的成员增加了_bf平衡因子和_parent父亲节点的指针;

有了描述每个节点的条件,要开始组织起来这些节点了,我们需要插入一个个节点;

插入节点

一开始插入节点是与搜索二叉树相同,我们需要先找到节点插入的位置,然后再将新增节点连接到我们的原树的叶子节点的末端,这个时候插入就完成了;

不同的是插入完成后,我们需要更新我们父亲节点的_bf平衡因子;采用上方所说的插入位置在父亲节点左侧时,父亲节点_bf-1,插入在父亲节点右侧时当前节点_bf+1;更新完成父亲节点后向上不断遍历修改父亲的父亲节点;而这个遍历我们又需要分情况遍历;

插入操作(和搜索二叉树中操作相同)
if (_root == nullptr)
{_root = new node(data);return true;
}
node* cur = _root;
node* parent = _root;
while (cur)
{if (data.first < cur->_data.first){parent = cur;cur = cur->_left;}else if (data.first > cur->_data.first){parent = cur;cur = cur->_right;}else{return false;}
}
cur = new node(data);
cur->_parent = parent;
if (data.first < parent->_data.first)
{parent->_left = cur;
}
else
{parent->_right = cur;
}
以上是普通的插入操作,下面需要进行平衡因子更新

更新_bf

情况1:父亲节点的_bf更新为1或-1

如果父节点的_bf为1或者1时就代表我们的父节点的左边或者右边新插入了节点,我们我们当前父节点为根的子树高度增加了,从而我们又会影响到父亲节点的父亲节点;所以我们需要继续所以向上遍历让父亲节点成为新插入的节点,更新父亲节点的父亲节点成为parent(父亲),更新新的父亲的_bf;

 情况2:父亲节点的_bf更新为0

 此时说明我们的父亲节点为根节点的树原本是不平衡的,但是由于我们插入的新节点补齐了这颗树;使得父亲节点的_bf变为了0,这个时候由于父亲节点为根的树高度没有变化;所以父亲节点上层的节点自然也不需要更新_bf所以这个时候可以直接退出遍历了;

情况3:父亲节点的_bf绝对值大于1

这个时候,我们需要进行旋转来降低父节点为根的树高度了

循环向上更新_bf
while (parent)
{if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 1 || parent->_bf == -1)//-1or1时需要继续更新平衡因子{cur = parent;parent = parent->_parent;	}else if (parent->_bf == 0)//因子为0是将树补齐了不需要再向上更新了{break;}else if (parent->_bf == 2 || parent->_bf == -2)//2or-2时需要旋转减小树的高度{if (parent->_bf == -2 && parent->_left->_bf == -1)//左子树的左子树高右旋转{RotateR(parent);}else if (parent->_bf == 2 && parent->_right->_bf == 1)//右子树的右子树高左旋转{RotateL(parent);}else if (parent->_bf == -2 && parent->_left->_bf == 1)//左子树的右子树高,先右子树左旋再左子树右旋{RotateLR(parent);}else if (parent->_bf == 2 && parent->_right->_bf == -1)//右子树的左子树高,先左子树右旋再右子树左旋{RotateRL(parent);}else{assert(false);}break;}
}

 旋转

这个操作就是AVL最精华的部分了;我们知道是当树左右两边高度相差超高1的时候就需要旋转了;旋转主要是靠画图理解;

我们需要注意的是我们旋转的树是一般是子树(父节点为根时为整棵树);

而旋转又分为四种旋转情况:

为了方便描述我们将当前树的根节点同一叫根节点;

单旋

情况1:根节点_bf为-2根节点左节点_bf为-1时(右单旋)

当根节点_bf为-2时说明我们这棵树的左子树高度要比右子树高2个节点的高度,根左节点的_bf为-1又说明我们左子树的左子树比左子树的右子树高1个节点的高度,也就是如下图:

我们需要进行单旋,因为是左树高,所以需要进行右旋转,将根节点旋转为左节点的右节点

右单旋
void RotateR(node* parent)
{node* subl = parent->_left;node* sublr = subl->_right;parent->_left = sublr;if (sublr)sublr->_parent = parent;node* ppnode = parent->_parent;subl->_right = parent;parent->_parent = subl;if (ppnode == nullptr){_root = subl;_root->_parent = nullptr;}else{subl->_parent = ppnode;if (ppnode->_left == parent){ppnode->_left = subl;}else{ppnode->_right = subl;}}parent->_bf = subl->_bf = 0;return;
}

 情况2:根节点_bf为2根节点右节点_bf为1时(左单旋)

这种情况就是情况1的对立边,这次是右树的右子树高,我们需要进行左单旋;

左单旋
void RotateL(node* parent)
{node* subr = parent->_right;node* subrl = subr->_left;parent->_right = subrl;if (subrl)subrl->_parent = parent;node* ppnode = parent->_parent;subr->_left = parent;parent->_parent = subr;if (ppnode == nullptr){_root = subr;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subr;}else{ppnode->_right = subr;}subr->_parent = ppnode;}parent->_bf = subr->_bf = 0;
}

 单旋之后我们的parent和左节点的_pf都为0;

双旋

情况3:根节点_pf为-2根节点的左节点_pf为1(左右双旋)

这种情况就是我们的根节点左树高,但左数中的右树高,我们需要进行双旋:

如何左右双旋如图:

 

值得注意的是双旋我们的_bf是需要更新的;我们需要通过sublr的_bf来判断更新值

 代码实现

void RotateLR(node* parent)
{node* subl = parent->_left;node* sublr = subl->_right;int flag = sublr->_bf;RotateL(subl);//左旋右子树(树中树)RotateR(parent);//右旋左子树//更新平衡因子if (flag == 1)//插入位置为树中树的右树{parent->_bf = 0;subl->_bf = -1;sublr->_bf = 0;}else if (flag == -1){parent->_bf = 1;subl->_bf = 0;sublr->_bf = 0;}else if (flag == 0){parent->_bf = 0;subl->_bf = 0;sublr->_bf = 0;}else{assert(false);}
}

情况4:根节点_pf为2根节点的右节点_pf为-1(右左双旋)

此情况就是不同与单旋时的单一枝高,而是一枝中的另一枝高;

 

 同样的我们旋转之后更新我们的_bf;

代码实现

void RotateRL(node* parent)
{node* subr = parent->_right;node* subrl = subr->_left;int flag = subrl->_bf;RotateR(subr);//右旋左子树(树中树)RotateL(parent);//左旋右子树//更新平衡因子if (flag == 1)//插入位置为树中树的右树{parent->_bf = -1;subr->_bf = 0;subrl->_bf = 0;}else if (flag == -1){parent->_bf = 0;subr->_bf = 1;subrl->_bf = 0;}else if (flag == 0){parent->_bf = 0;subr->_bf = 0;subrl->_bf = 0;}else{assert(false);}
}

 当上面的旋转和更新_bf都完成的时候,这棵树我们插入就一定是我们的AVL树了;

为了验证我们的树是否真的成为了AVL树我们需要通过检查算法来比较每个节点的_bf是否正确

void _print(node* root)
{if (root == nullptr)return;_print(root->_left);cout << root->_data.first << " ";_print(root->_right);
}bool judgeBalance()
{return _judgeBalance(_root);
}bool _judgeBalance(node*root)
{if (root == nullptr)return true;int hl = getDeep(root->_left);//hightLeftint hr = getDeep(root->_right);//hightRightint judge = hr - hl;if (judge != root->_bf){cout << root->_data.first << "这个节点的_pf没处理好" << endl;return false;}return _judgeBalance(root->_left) && _judgeBalance(root->_right);
}void testAVLTree1()
{AVLTree<int, int>t;srand(time(0));for (int i = 0; i < 100; i++){int x = rand();t.insert(make_pair(x, x));}t.print();cout << t.judgeBalance() << endl;
}

此时我们的树就是一颗完全正确的AVL树 ;

我们只实现了AVL树的插入算法,删除算法还没有学习;

这是我的完整的AVL树代码:

C++代码/AVL树 · future/my_road - 码云 - 开源中国 (gitee.com)

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

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

相关文章

xshell7连接ubuntu18.04

&#x1f3a1;导航小助手&#x1f3a1; 1.查看ubuntu IP2.开启openssh-server3.静态IP设置4.Xshell连接 1.查看ubuntu IP 输入下面命令查看IP ifconfig -a可以看到网卡是ens33&#xff0c;IP为192.168.3.180。 2.开启openssh-server 1、执行下句&#xff0c;下载SSH服务 s…

零基础入门转录组数据分析——DESeq2差异分析

零基础入门转录组数据分析——DESeq2差异分析 目录 零基础入门转录组数据分析——DESeq2差异分析1. 转录组分析基础知识2. DESeq2差异分析&#xff08;Rstudio&#xff09;3. 结语 1. 转录组分析基础知识 1.1 什么是转录组&#xff1f; 转录组&#xff08;transcriptome&#…

【详细教程制作】用户列表

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

【Vue3源码学习】— CH2.8 Vue 3 响应式系统小结

Vue 3 响应式系统小结 1.核心概念1.1 Proxy和Reflect1.2 响应式API1.3 依赖收集与更新触发1.4 触发更新&#xff08;Triggering Updates&#xff09;&#xff1a;1.5 副作用函数&#xff08;Effect&#xff09;1.6 计算属性和观察者1.7 EffectScope1.8 性能优化&#xff1a; 2.…

登录系统演进、便捷登录设计与实现

作者 | 百度APP技术中台吧 导读 随着互联网、物联网和移动终端等技术的迅猛发展&#xff0c;登录认证面临着新的挑战和需求。虽然登录认证在信息系统中是传统且古老的组成部分&#xff0c;但未来的发展前景依然广阔。不论是用户登录、PC端、移动端还是智能设备的访问&#xff0…

14种建模语言(UML)图形

前言 UML 中有四种关系&#xff1a;依赖、关联、泛化和实现。这四种关系是 UML 模型中可以包含的基本关系事物。这里介绍14种UML图形:类图&#xff0c;对象图&#xff0c;包图&#xff0c;构件图&#xff0c;组合结构图&#xff0c;部署图&#xff0c;制品图&#xff0c;用例图…

DC电源模块的市场发展趋势分析

BOSHIDA DC电源模块的市场发展趋势分析 DC电源模块是一种将交流电转换为直流电的模块&#xff0c;广泛应用于各种电子设备中。随着科技的不断发展和电子产品的普及&#xff0c;DC电源模块市场也在不断扩大。本文将对DC电源模块的市场发展趋势进行分析。 第一&#xff0c;随着电…

【二叉树】Leetcode 437. 路径总和 III【中等】

路径总和 III 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只能从父节…

DOTS:Burst

目录 一&#xff1a;简介 1.1 Getting started 1.2 C# language support 1.2.1 HPC# overview 1.2.1.1 Exception expressions 1.2.1.2 Foreach and While 1.2.1.3 Unsupported C# features in HPC# 1.2.2 Static read-only fields and static constructor support 1.…

Mysql故障和优化

一、MySQL故障 二、MySQL优化 1.硬件优化&#xff1a; 2.数据库设计与规划 1.提前估计数据量&#xff0c;使用什么存储引擎 2.数据库服务器专机专用&#xff0c;避免额外的服务可能导致的性能下降和不稳定性 3.增加多台服务器&#xff0c;以达到稳定、高效的效果。主从同步、…

PPP+VPN综合实验

一、实验拓扑 二、实验划分 三、实验需求 四、实验结果 1.配置各端口和pc的IP&#xff1a; pc1&#xff1a; pc2&#xff1a; pc3&#xff1a; pc4&#xff1a; R1: [r1]inter g0/0/0 [r1-GigabitEthernet0/0/0]ip ad 192.168.1.2 24 [r1-GigabitEthernet0/0/0]int s4/0/0…

网站HTTPS证书是什么?有用吗?

什么是HTTPS证书&#xff1f; HTTPS证书&#xff0c;全称为安全套接层证书或传输层安全证书&#xff0c;是数字证书的一种。它由受信任的证书颁发机构签发&#xff0c;用于证明网站的身份&#xff0c;并为网站启用HTTPS&#xff08;超文本传输安全协议&#xff09;。 HTTPS证书…