跨境派

跨境派

跨境派,专注跨境行业新闻资讯、跨境电商知识分享!

当前位置:首页 > 卖家故事 > 【C++】AVL树的插入、旋转

【C++】AVL树的插入、旋转

时间:2024-03-27 15:00:45 来源:网络cs 作者:纳雷武 栏目:卖家故事 阅读:

标签: 旋转 
阅读本书更多章节>>>>

目录

一、AVL树介绍1.1 概念1.2 定义 二、AVL树的实现2.1 插入2.2 旋转2.2.1 左单旋2.2.2 右单旋2.2.3 左右双旋2.2.4 右左双旋

一、AVL树介绍

1.1 概念

AVL树是高度平衡的二叉搜索树,相比普通的二叉搜索树,它防止了变成单支树的情况。因为AVL树每插入一个新的节点,它都会调整使左右子树的高度差的绝对值不超过1,从而降低了树的高度,提高了搜索效率。

特点:

左右子树都是AVL树左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)搜索时间复杂度O( l o g 2 n log_2 n log2​n)有平衡因子控制高度差:右子树高度减去左子树高度

在这里插入图片描述

1.2 定义

AVL树是三叉链结构,即左孩子指针、右孩子指针和双亲指针,多了一个双亲指针方便找到上一个节点。定义它的数据域,为kv模型的类型。定义平衡因子,作用是记录当前节点的左右子树高度之差。

template<class K, class V>struct AVLTreeNode{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf;pair<K, V> _kv;AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _bf(0), _kv(kv){}};

二、AVL树的实现

2.1 插入

AVL树的插入过程与二叉搜索树的插入过程是一样的,只不过在此基础上增加了调整节点的平衡因子。所以总结为两个步骤:

按照二叉搜索树的方式插入新节点调整节点的平衡因子

没标数字的是新插入的节点

在这里插入图片描述
调整平衡因子,首先要从cur和parent入手,即新插入的节点和插入节点的上一个节点。如果插入节点cur是parent的右边,则parent的平衡因子+1;反之,则-1。整个调整过程是一个循环,因为刚开始改变的是下面的平衡因子,上面节点的平衡因子也可能会随之改变。循环条件为parent不为空,还要其他条件可以终止循环,下面细讲。

所以根据parent的平衡因子的值可以分为3步:

parent的平衡因子为0parent的平衡因子为1或者-1parent的平衡因子为2或者-2

这里说明下,没有parent的平衡因子可能为3/-3的情况,如果出现了说明之前的树有问题

1️⃣parent的平衡因子为0
此情况说明原来的parent的左边或者右边存在一个节点,新插入的节点在parent没有节点的一侧,parent两边都有节点,平衡因子为0,不影响上面的节点,调整结束,跳出循环。

在这里插入图片描述

2️⃣parent的平衡因子为1或者-1
如果parent的平衡因子为1或者-1,则会影响上面的节点,所以让parent节点转换为它的上一个节点,cur转换为parent,向上更新,以次类推。每次循环还是要经过cur是parent的左边还是右边,才能确定parent的平衡因子是+1还是-1。直到cur为根节点,parent为空时循环结束。
在这里插入图片描述

3️⃣parent的平衡因子为2或者-2
当parent向上更新,到某个节点时(有可能不是根节点)它的平衡因子为2或者-2,这时违背了AVL树的规则,因此要进行处理——旋转,来改变树的高度差,使其左右之树的高度差的绝对值不超过1

下图的树插入节点后要发生旋转
在这里插入图片描述

旋转后,高度差恢复平衡,跳出循环。

总结:
AVL树插入节点要进行调整平衡因子,调整结束有3种:
1.parent为空——已经调整到根节点了
2.parent的平衡因子为0——不影响上面节点
3.旋转后——树恢复平衡状态

代码:

//插入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 (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;//有重复不能插入}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//调整平衡因子while (parent){if (parent->_left == cur){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0)//不需要修改,直接跳出{break;}else if (parent->_bf == 1 || parent->_bf == -1)//向上更新{cur = cur->_parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2)//要旋转{//旋转有4种情况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{RotateRL(parent);//右左双旋-先右后左}break;//旋转后跳出}else{assert(false);//之前树就有问题}}return true;}

2.2 旋转

这里就开始详细分析AVL树的旋转了,旋转分为4种情况,有左单旋、右单旋、左右双旋和右左双旋。发生这4种旋转的条件不一样,下面逐个分析:

2.2.1 左单旋

整体思路:cur的左孩子变成parent的右孩子(cur的左孩子可能为空),parent变成cur的左孩子,cur连接原来parent的双亲(有可能原来的parent就是根节点,如果是,cur变成根)

先来简单的图示分析:
在这里插入图片描述
parent是平衡因子为2的节点,cur是平衡因子为1的节点,为调整成平衡,parent要变成cur的左孩子,而在此之前,cur的左孩子要先变成parent的右孩子(上面的图节点较少,所以cur没有左孩子),最终平衡。

通过上面的例子,可以基本了解左单旋的整个过程,下面来较多节点的例子:
在这里插入图片描述
定义两个临时变量,subR指向parent的右孩子,即cur的位置(后面的操作就可以不用cur),subRL为subR的左孩子。

按照上面的步骤,subRL变成parent的右孩子,parent变成subR的左孩子,定义一个ppnode节点为原来parent的双亲,如果parent是在ppnode的左边,则ppnode的左边连接subR,;反之,则右边连接subR。如果parent原来是根节点,那么根节点就变成subR。最后修改parent和subR的平衡因子,它们的平衡因子都变成了0
在这里插入图片描述

有个问题,旋转有4种情况,怎么知道用哪种呢?其实上面的两种图示已经显示出左单旋的条件。当parent的平衡因子为2,并且cur的平衡因子为1时,要进行的旋转为左单旋。

代码:

//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;//不为空if (subRL){subRL->_parent = parent;}subR->_left = parent;Node* ppnode = parent->_parent;parent->_parent = subR;//处理parent如果为根if (parent == _root){_root = subR;subR->_parent = nullptr;}//不为根,处理与ppnode的连接else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}//修改平衡因子parent->_bf = 0;subR->_bf = 0;}

2.2.2 右单旋

整体思路:cur的右孩子变成parent的左孩子,parent变成cur的右孩子,cur连接原来parent的双亲。

简单图示:
在这里插入图片描述
parent是平衡因子为-2的节点,cur是平衡因子为-1的节点,为调整成平衡,cur的右孩子要先变成parent的右孩子,parent变成cur的右孩子,然后修改平衡因子,最终平衡。

用较多节点的例子来分析:
在这里插入图片描述
定义两个临时变量,subL指向parent的左孩子,subLR指向subL的右孩子。

subLR变成parent的左孩子,parent变成subL的右孩子,定义一个ppnode节点为原来parent的双亲,步骤同左单旋。最后修改parent和subL的平衡因子,它们的平衡因子都变成了0
在这里插入图片描述
通过例子可知,发生右单旋的条件是parent的平衡因子为-2,cur的平衡因子为-1

代码:

//右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;//不为空if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* ppnode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}//修改平衡因子parent->_bf = 0;subL->_bf = 0;}

2.2.3 左右双旋

左右双旋是先左单旋,再右单旋。左单旋的是parent的左子树,右单旋的是包括parent的当前树。既然已经有单旋了,为什么还要双旋呢?如图:
在这里插入图片描述
通过上面例子可知,如果插入的位置与前面(左单旋或者右单旋插入时的位置)不同,只有左或者右旋转不能使树变平衡,因此要进行双旋。

左右双旋的情况主要分为以下3类:
在这里插入图片描述
当插入的节点是subLR时,subLR的平衡因子为0;插入的节点在subLR的左边,subLR的平衡因子为-1,插入的节点在subLR的右边,subLR的平衡因子为1;通过图示可知,发生左右双旋的条件是parent的平衡因子为-2,cur的平衡因子为1。这3种情况经过左右双旋后,subLR的平衡因子都变成0,但是parent和subL的平衡因子是有区别的。

下面来看看3种情况的旋转:
1️⃣插入的新节点是subLR
在这里插入图片描述

2️⃣插入的新节点在subLR的左边
在这里插入图片描述

3️⃣插入的新节点在subLR的右边
在这里插入图片描述

通过上面的图发现,当插入的新节点是subLR,subLR的平衡因子为0,旋转后,subL和parent的平衡因子也为0;插入的新节点在subLR的左边,subLR的平衡因子为-1,旋转后,subL的平衡因子为0,parent的平衡因子为1;插入的新节点在subLR的右边,subLR的平衡因子为1,旋转后,subL的平衡因子为-1,parent的平衡因子为0。

代码:

//左右双旋-先左后右void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;//提前保存原来的bfRotateL(subL);RotateR(parent);//旋转后,不同情况最后的bf会不一样subLR->_bf = 0;//确定的if (bf == -1){subL->_bf = 0;parent->_bf = 1;}else if (bf == 1){subL->_bf = -1;parent->_bf = 0;}else if (bf == 0){subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}

2.2.4 右左双旋

右左双旋是先右单旋,再左单旋。右单旋的是parent的右子树,左单旋的是包括parent的当前树。

右左双旋也分为3类:
在这里插入图片描述
当插入的节点是subRL时,subRL的平衡因子为0;插入的节点在subRL的左边,subRL的平衡因子为-1;插入的节点在subRL的右边,subRL的平衡因子为1;通过图示可知,发生右左双旋的条件是parent的平衡因子为2,cur的平衡因子为-1。这3种情况经过右左双旋后,subRL的平衡因子固定都变成了0,但是parent和subL的平衡因子有区别。

下面是3种情况的旋转:
1️⃣插入的新节点是subRL
在这里插入图片描述

2️⃣插入的新节点在subRL的左边
在这里插入图片描述

3️⃣插入的新节点在subRL的右边
在这里插入图片描述

插入的新节点是subRL,subRL的平衡因子为0,旋转后,subR和parent的平衡因子也为0;插入的新节点在subRL的左边,subRL的平衡因子为-1,旋转后,subR的平衡因子为1,parent的平衡因子为0;插入的新节点在subRL的右边,subRL的平衡因子为1,旋转后,subR的平衡因子为0,parent的平衡因子为-1。

代码:

//右左双旋-先右后左void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);subRL->_bf = 0;if (bf == -1){subR->_bf = 1;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == 0){subR->_bf = 0;parent->_bf = 0;}else{assert(false);}}
阅读本书更多章节>>>>

本文链接:https://www.kjpai.cn/gushi/2024-03-27/149531.html,文章来源:网络cs,作者:纳雷武,版权归作者所有,如需转载请注明来源和作者,否则将追究法律责任!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。

文章评论