数据结构:AVLTree的插入和删除的实现

在这里插入图片描述

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》《C++》

文章目录

  • 前言
  • 一、AVLTree
  • 二、AVLTree的插入
    • 插入新增节点
    • 调整平衡因子
    • 旋转
      • 左单旋(新增节点位于较高右子树的右侧)
      • 右单旋(新增节点位于较高左子树的左侧)
      • 右左双旋(新增节点在较高右子树的左子树上)
      • 左右双旋(新增节点在较高左子树的右子树上)
  • 三、AVLTree的删除
    • 删除节点
    • 调节平衡因子
  • 四、检查AVLTree的正确性
  • 代码实现
  • 总结


前言

本篇博客作为AVL树的插入和删除的实现。如果代码实现有问题,还请大佬们指出。


一、AVLTree

二叉搜索树虽然可以缩短查找的效率,但如果数据有序或者接近有序,二叉搜索树将退化成单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两个俄罗斯的数学家G.M.Adelson-Velskil和E.M.Landis在1962年发明了一种解决上述问题的方法:当二叉搜索树中插入新节点后,如果能保证每个节点的左右子树高度之差的绝对值不超过1,即可降低树的高度,从而减少平均搜索长度。
也就是说,一颗AVL树或者空树是具有以下性质的二叉树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(平衡因子)的绝对值不超过1(-1 \ 0 \ 1)。[ 平衡因子 = 右子树高度 - 左子树高度 ]

如下图所示:
在这里插入图片描述

如果一颗搜索二叉树是高度平衡的,它就是AVL树,如果它有N个节点,其高度为logn,搜索的时间复杂度为logn。

其中AVL树节点的定义:

template<class T>
struct AVLTreeNode
{AVLTreeNode(const T data = T()):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_bf(0){}AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;T _data;int _bf;
};

其中_parent指针为指向父节点的指针,保证在插入和删除时,可以通过该指针进行回溯寻找父节点。(当然也可以不用该指针,转而在插入和删除的过程中,使用栈保存路径来进行回溯)

二、AVLTree的插入

AVL树的插入可以分为两步:

  1. 依据二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

插入新增节点

第一点很好实现,用两个指针变量cur,parent。cur来与新增节点比较,如果cur大于新增节点,cur就去左子树,如果cur小于新增节点,cur就去右子树,直到cur走的nullptr,表示新增节点要插入的位置。parent作为cur的父节点。需要注意的是,如果树为空,要进行特殊处理。

		// 树为空if (_root == nullptr) {_root = new Node(data);return true;}// 寻找新增节点要插入的位置Node* cur = _root;Node* parent = nullptr;while (cur) {parent = cur;if (cur->_data < data){cur = cur->_right;}else if (cur->_data > data){cur = cur->_left;}else{return false;}}// 插入新增节点cur = new Node(data);cur->_parent = parent;if (parent->_data > data){parent->_left = cur;}else{parent->_right = cur;}

调整平衡因子

第二点调整平衡因子就有些麻烦,因为对于插入节点的祖先节点而言,它们的平衡因子都可能改变甚至失衡。
如下图所示:
在这里插入图片描述

对于上图中新增节点的祖先节点的平衡因子都发生改变。那么我们要如何处理平衡因子呢?

(平衡因子 = 右子树高度 - 左子树高度)

  • 如果插入节点是父节点的左子树(左子树高度+1),那么父节点的平衡因子-1
  • 如果插入节点是父节点的右子树(右子树高度+1),那么父节点的平衡因子+1

经过上述调整,父节点的平衡因子就会分成三种情况 0(该子树高度不变),正负1(该子树的高度改变),正负2(该子树已经失衡)。
在这里插入图片描述
如上图所示,我们在s的左子树插入一个节点,s的平衡因子变为0,s作为r的右子树的高度不变,r的平衡因子不需要改变,所以当调整完,父节点的平衡因子为0,我们不需要向上回溯处理祖先节点的平衡因子。
在这里插入图片描述

如上图所示,我们在c的左子树插入一个节点,c的平衡因子变为-1,c作为b的右子树的高度改变了,b的平衡因子也需要跟着改变,b的平衡因子变为1… 直到某一祖先节点的平衡因子变为0(如在插入一节点在s的左子树,那么其祖先节点r的平衡因子就为0),正负2(在t节点的左子树插入一节点,那么其祖先s节点的平衡因子变为2),回溯到根节点(上图所示)才能停止。
在这里插入图片描述
如上图所示,我们在t的右子树插入一个节点,t的平衡因子变为1,t作为s的右子树的高度改变了,s的平衡因子变为2,此时s的平衡因子失衡。此时我们就要对s这一子树进行旋转。

		while (parent){if (parent->_left == cur) // 新增节点是父节点的左子节点{parent->_bf--;}else if(parent->_right == cur) // 新增节点是父节点的左子节点{parent->_bf++;}if (parent->_bf == 0) // 调整平衡因子后,父节点的平衡因子为0{break;}else if (parent->_bf == 1 || parent->_bf == -1) // 调整平衡因子后,父节点的平衡因子为正负1{cur = parent;parent = cur->_parent;}else if(parent->_bf == 2 || parent->_bf == -2) // 调整平衡因子后,父节点的平衡因子为正负2{// 失衡处理break;}}

旋转

对于失衡节点的旋转,我们可以分为四种情况左单旋右单旋左右双旋右左双旋
对于下图s的平衡因子失衡,我们就可以对于左单旋,使其回复平衡。
在这里插入图片描述

左单旋(新增节点位于较高右子树的右侧)

如下图s节点而言,新增节点位于其较高右子树的右侧。我们就需要对s节点进行左单旋。

在这里插入图片描述
那么如何进行左单旋呢?
我们先将定义四个指针pparent(r节点,s节点父节点),parent(s节点),subR(t节点,也就是s节点的右子树的根节点),subRL(t节点的左子树的根节点,在这里为nullptr)。此时我们只需要使parent->_right = subRL,subR-> _left = parent,subRL-> _parent = parent,subR-> _parent = parent-> _parent, parent-> _parent = subR,pparent-> _left = subR,使subR作为新子数的根节点,parent作为t较低左子树的根节点,再连接父节点指针即可(要注意此时subRL有可能为空,当parent为根节点时,pparent为空),此时 t 和 s 的平衡因子为0。
在这里插入图片描述
这是对于这个s节点的左单旋,那有没有可以概括整个左单旋的模型呢?
在这里插入图片描述

在这个模型中,我们可能会疑惑为什么a,b,c的高度都为h呢?这是因为如果当b = c = h,a = h-1,那么此时
parent的平衡因子已经失衡,且无论我们怎么旋转都不能解决失衡问题,再如果b = h-1,c = a = h,此时如果新增节点在b上,parent的平衡因子不会改变,如果新增节点在c上,subR的平衡因子就会先失衡。所以a,b,c的高度都要相当。
这样我们依据左单旋的模型就可以清晰的写出左单旋的代码,但不要忘记我们的节点还有一个父指针,要连接父子指针。

void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;if (subRL != nullptr){subRL->_parent = parent;}Node* pparent = parent->_parent;if (pparent == nullptr) // 处理根节点{_root = subR;}else{if (pparent->_left == parent){pparent->_left = subR;}else if (pparent->_right == parent){pparent->_right = subR;}}subR->_parent = pparent;parent->_parent = subR;parent->_bf = 0;subR->_bf = 0;}

右单旋(新增节点位于较高左子树的左侧)

右单旋与左单旋类似,这里我们就直接上模型了。
在这里插入图片描述
如上图所示,我们只需要使parent-> _left = subLR,subL->_right = parent,subRL->_parent = parent,subL->_parent = parent->_parent,parent->_parent = subL再将parent原先的父节点与该子树新的根节点subL连接即可。

void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;if (subLR != nullptr){subLR->_parent = parent;}Node* pparent = parent->_parent;if (parent->_parent == nullptr){_root = subL;}else{if (pparent->_left == parent){pparent->_left = subL;}else if (pparent->_right == parent){pparent->_right = subL;}}subL->_parent = parent->_parent;parent->_parent = subL;subL->_bf = 0;parent->_bf = 0;}

右左双旋(新增节点在较高右子树的左子树上)

这里我们先上用例,再上模型。
如下图,如果我们在i,k,m,o节点下新增一个节点,都会发生右左双旋。
在这里插入图片描述
我们新增节点为m节点的左子节点,m的平衡因子变为-1,n的平衡因子变为-1…p的平衡因子变为-1,h的平衡因子变为2。此时h的平衡因子失衡(新增节点位于h节点较高右子树中,新增节点又位于p节点的左子树中)。我们需要先对p节点右单旋,再对h节点左单旋。
在这里插入图片描述

这样我们就可以完成右左双旋的操作。如果我们将新增节点加在i节点的左子节点上,那么虽然该树依据需要进行右左双旋的操作,h 和 p节点的平衡因子会发生改变。

在这里插入图片描述

因此,我们不仅需要两个指针parent(指向h节点,左单旋),subR(指向p节点,右单旋),还需要一个变量记录l节点的平衡因子。当l的平衡因子为1时,h的平衡因子为-1,p的平衡因子为0。当l的平衡因子为-1时,h的平衡因子为0,p的平衡因子为1。

在这里插入图片描述

这样我们就可以清楚的依据右左双旋模型来实现代码,但不要忘记将新子树的根节点与旧父节点相连接。

void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);if (bf == 1){parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;}}

左右双旋(新增节点在较高左子树的右子树上)

左右双旋与右左双旋类似,这里就直接上模型了。
在这里插入图片描述
我们先对subL节点左单旋,再对parent节点右单旋,最后依据subLR节点的平衡因子来修改parent节点,subL节点的平衡因子

void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);if (bf == -1){parent->_bf = 1;}else if(bf == 1){subL->_bf = -1;}}

至此四种旋转就结束了,那么什么时候使用那种旋转呢?
在这里插入图片描述
依据上图判断parent节点与cur节点的平衡因子即可知晓使用那种平衡。

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 if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}// 旋转后,该子树就不需要再向上回溯break;

三、AVLTree的删除

AVRTree的删除与二叉搜索树类似,也可以分成三种情况来看(实际情况一与情况二在实现上可以合并)

  • 情况一:要删除节点为叶子节点,则直接删除,调节平衡因子
  • 情况二:要删除节点有一个子节点,将该节点的父节点原来指向该节点的指针,直接指向该节点的子节点,删除该节点,调节平衡因子
  • 情况三:要删除节点有两个子节点,找该节点在中序遍历中的直接前驱节点,将该节点与前驱节点直接替换,该前驱节点必定只有一个右子节点或没有子节点,按照情况二删除前驱节点,调整平衡因子。

删除节点

我们将情况一和情况而合并为同一种情况处理。这种情况删除节点很好处理,我们只需找到该节点让该节点的父节点原指向该节点的指针指向该节点的子节点即可。但情况三有一个问题,当我们找到该节点后,如何找该节点在中序遍历中的直接前驱节点?
如下图所示:该图中序遍历结果就是字母a到字母t的顺序
在这里插入图片描述
如果我们要删除h节点,那h节点在中序遍历中的直接前驱就是g节点,如果删除的是p节点,那么前驱节点就是o节点,如果删除d节点,前驱节点就是c节点。那么有什么发现吗?一个节点的“前驱节点”就是该节点左子树的最右节点,毕竟中序遍历是左 根 右,左子树的最后一个节点就是该节点的“前驱节点”。

		Node* cur = _root;    // 要删除的节点Node* prev = nullptr; // 要删除节点的父节点Node* sub = nullptr;  // 要删除节点的子节点int flag = 0; // flag = 1表示删除节点是右子节点   flag = -1表示删除节点是左节点while (cur != nullptr && cur->_data != data){if (cur->_data > data){cur = cur->_left;}else if (cur->_data < data){cur = cur->_right;}}if (cur == nullptr){return false;}prev = cur->_parent;// 处理有两个子节点if (cur->_left != nullptr && cur->_right != nullptr){// 将cur 与 中序次序的直接前序替换sub = cur->_left;while (sub->_right != nullptr){sub = sub->_right;}prev = sub->_parent;cur->_data = sub->_data;cur = sub;// prev->_right = sub->_left;}// 处理有单个子节点if (cur->_left != nullptr){sub = cur->_left;}else{sub = cur->_right;}if (prev == nullptr) // 删除的是根节点{delete cur;cur = nullptr;_root = sub;if(sub != nullptr)sub->_parent = nullptr;}else{if (sub != nullptr){sub->_parent = prev;}if (prev->_left == cur){flag = -1;prev->_left = sub;}else{flag = 1;prev->_right = sub;}delete cur;cur = nullptr;// 调节平衡因子}

调节平衡因子

依据平衡因子 = 右子树高度 - 左子树高度

  • 如果删除的节点是父节点的左子节点,那么父节点的平衡因子+1
  • 如果删除的节点是父节点的右子节点,那么父节点的平衡因子-1

那么修改后,父节点的平衡因子会有三种情况。

  1. 父节点的平衡因子本身是0,修改后变成正负1,此时该子树高度不变,不需要向上回溯。
    在这里插入图片描述
    如上图删除o节点,对于以n为根节点的子树而言高度不变,不需要向上回溯调整平衡因子。
  2. 父节点的平衡因子本身不为0,删除较高子树的节点,修改后变成0,此时该子树高度发生改变,需要向上回溯调节祖先节点的平衡因子
    在这里插入图片描述
    如上图删除t节点,s节点的平衡因子变为0,对于以s节点为根节点的子树而言,其高度减少,需要向上回溯,p节点的平衡因子变为-1,对于以p节点为根节点的子树而言,其高度不变,不需要向上回溯。
  3. 父节点的平衡因子本身不为0,删除较低子树的节点,修改后平衡因子变成正负2,此时该子树失衡,需要旋转。我们现将parent(父节点)的较高子节点记录为sub。此时我们又可以分成三种情况。
  1. sub的平衡因子为0,此时我们只需要单旋处理即可,不需要向上调整平衡因子。
    在这里插入图片描述
    如果我们是连续删除e,g,f节点,那么b节点作为d节点中较高节点(左子节点)的平衡因子为0,需要右单旋来完成平衡。
    2.sub的平衡因子不为0,prev(父节点)的平衡因子与sub(父节点的子节点中较高子节点)平衡因子同号,只需要一个单旋来完成平衡,但该子树的高度发生改变,需要向上回溯,调节祖先节点的平衡因子。
    在这里插入图片描述
    如上图我们删除节点q,此时r节点的平衡因子为2,s节点的平衡因子为1,我们需要左单旋来完成平衡,但新的以s节点为根的子树高度变低了,我们需要向上回溯,调整p节点的平衡因子,此时p的平衡因子为-1。对于模型中sub的左子树部分,可能有人会疑惑为什么高度为h-1,而不是h?如果sub左子树部分高度为h,该模型不就是情况1的模型。
    3.sub的平衡因子不为0,prev的平衡因子与sub的平衡因子异号,此时需要执行双旋来完成平衡,且完成双旋后高度降低,需要向上回溯,调节祖先节点的平衡因子。
    在这里插入图片描述
    如上图我们连续删除a,c,e,g,t节点,此时h节点的平衡因子为2,p节点的平衡因子为-1,我们需要右左双旋来完成平衡,先对sub右旋,再对prev左旋,旋转后,此时以l为根节点的新子树高度减小,需要向上调整,但l节点恰好是整个树的根节点。不用向上调整。

至此删除节点后调节平衡因子的所以情况都以说完。

			while (prev != nullptr) // 调节平衡因子{if (flag == -1) // 如果子节点删除完成后,其父节点的左右子节点都为nullptr,此时直接判断perv->_left == cur则无法区分。{prev->_bf++; }else if (flag == 1){prev->_bf--;}if (prev->_bf == -1 || prev->_bf == 1){break; // 该子树的高度不变}else if (prev->_bf != 0) // prev->_bf == 2 || prev->_bf == -2{// 使 sub 表示最高子树if (prev->_bf > 0){sub = prev->_right;}else{sub = prev->_left;}if (sub->_bf == 0) // 该子树平衡后高度不变{if (prev->_left == sub){RotateR(prev);sub->_bf = 1;prev->_bf = -1;}else{RotateL(prev);sub->_bf = -1;prev->_bf = 1;}break;}else if ((prev->_bf) * (sub->_bf) > 0) // 同号  {if (sub == prev->_right){RotateL(prev);}else if (sub == prev->_left){RotateR(prev);}prev = sub->_parent;}else if ((prev->_bf) * (sub->_bf) < 0) // 异号  {if (sub == prev->_right){RotateRL(prev);}else if (sub == prev->_left){RotateLR(prev);}sub = sub->_parent;prev = sub->_parent;}}else if (prev->_bf == 0){sub = prev;prev = prev->_parent;}//  更新 flag 的值if (prev != nullptr){if (prev->_left == sub){flag = -1;}else if (prev->_right == sub){flag = 1;}}}

四、检查AVLTree的正确性

验证AVLTree的正确分为两步:

  • 验证其是否是二叉搜索树,打印中序遍历结果,看结果序列是否是升序序列
  • 验证其是否是平衡数,每个子节点的高度差不大于1,节点的平衡因子是否正确。

这两步都非常简单,我们只需递归比较每个节点的左右子树的高度,看是否符合定义即可。

	bool isAVLTEree(){return _isAVLTree(_root);}bool _isAVLTree(Node* root){if (root == nullptr)return true;int leftHeight = _Hight(root->_left);int rightHeight = _Hight(root->_right);if(rightHeight - leftHeight != root->_bf){cout << root->_data << "平衡因子失衡" << endl;}return abs(leftHeight - rightHeight) >= 2 ? false : true;}size_t _Hight(Node* root){if (root == nullptr){return 0;}return max(_Hight(root->_left), _Hight(root->_right)) + 1;}

代码实现

AVLTree.h文件存放AVLTree插入删除的实现。
test.cpp文件存放的是测试用例。

#pragma once
#include <iostream>
#include <assert.h>
#include <vector>
#include <stdlib.h>
#include <time.h>
#include <string>using namespace std;template<class T>
struct AVLTreeNode
{AVLTreeNode(const T data = T()):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_bf(0){}AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;T _data;int _bf;
};template<class T>
class AVLTree
{
public:typedef AVLTreeNode<T> Node;AVLTree():_root(nullptr){}bool insert(const T data){if (_root == nullptr){_root = new Node(data);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){parent = cur;if (cur->_data < data){cur = cur->_right;}else if (cur->_data > data){cur = cur->_left;}else{return false;}}cur = new Node(data);cur->_parent = parent;if (parent->_data > data){parent->_left = cur;}else{parent->_right = cur;}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 = parent;parent = cur->_parent;}else{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 if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}break;}}return true;}bool Erase(const T data){Node* cur = _root;Node* prev = nullptr;Node* sub = nullptr;int flag = 0; // flag = 1表示删除节点是右子节点   flag = -1表示删除节点是左节点while (cur != nullptr && cur->_data != data){if (cur->_data > data){cur = cur->_left;}else if (cur->_data < data){cur = cur->_right;}}if (cur == nullptr){return false;}prev = cur->_parent;// 处理有两个子节点if (cur->_left != nullptr && cur->_right != nullptr){// 将cur 与 中序次序的直接前序替换sub = cur->_left;while (sub->_right != nullptr){sub = sub->_right;}prev = sub->_parent;cur->_data = sub->_data;cur = sub;// prev->_right = sub->_left;}// 处理有单个子节点if (cur->_left != nullptr){sub = cur->_left;}else{sub = cur->_right;}if (prev == nullptr) // 删除的是根节点{delete cur;cur = nullptr;_root = sub;if(sub != nullptr)sub->_parent = nullptr;}else{if (sub != nullptr){sub->_parent = prev;}if (prev->_left == cur){flag = -1;prev->_left = sub;}else{flag = 1;prev->_right = sub;}delete cur;cur = nullptr;while (prev != nullptr) // 调节平衡因子{if (flag == -1) // 如果子节点删除完成后,其父节点的左右子节点都为nullptr,此时直接判断perv->_left == cur则无法区分。{prev->_bf++; }else if (flag == 1){prev->_bf--;}if (prev->_bf == -1 || prev->_bf == 1){break; // 该子树的高度不变}else if (prev->_bf != 0) // prev->_bf == 2 || prev->_bf == -2{// 使 sub 表示最高子树if (prev->_bf > 0){sub = prev->_right;}else{sub = prev->_left;}if (sub->_bf == 0) // 该子树平衡后高度不变{if (prev->_left == sub){RotateR(prev);sub->_bf = 1;prev->_bf = -1;}else{RotateL(prev);sub->_bf = -1;prev->_bf = 1;}break;}else if ((prev->_bf) * (sub->_bf) > 0) // 同号  {if (sub == prev->_right){RotateL(prev);}else if (sub == prev->_left){RotateR(prev);}prev = sub->_parent;}else if ((prev->_bf) * (sub->_bf) < 0) // 异号  {if (sub == prev->_right){RotateRL(prev);}else if (sub == prev->_left){RotateLR(prev);}sub = sub->_parent;prev = sub->_parent;}}else if (prev->_bf == 0){sub = prev;prev = prev->_parent;}//  更新 flag 的值if (prev != nullptr){if (prev->_left == sub){flag = -1;}else if (prev->_right == sub){flag = 1;}}}}return true;}void InOrder(){_InOrder(_root);}bool isAVLTEree(){return _isAVLTree(_root);}size_t Hight(){return _Hight(_root);}
private:bool _isAVLTree(Node* root){if (root == nullptr)return true;int leftHeight = _Hight(root->_left);int rightHeight = _Hight(root->_right);if(rightHeight - leftHeight != root->_bf){cout << root->_data << "平衡因子失衡" << endl;}return abs(leftHeight - rightHeight) >= 2 ? false : true;}size_t _Hight(Node* root){if (root == nullptr){return 0;}return max(_Hight(root->_left), _Hight(root->_right)) + 1;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_data << " ";_InOrder(root->_right);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;if (subRL != nullptr){subRL->_parent = parent;}Node* pparent = parent->_parent;if (pparent == nullptr){_root = subR;}else{if (pparent->_left == parent){pparent->_left = subR;}else if (pparent->_right == parent){pparent->_right = subR;}}subR->_parent = pparent;parent->_parent = subR;parent->_bf = 0;subR->_bf = 0;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;if (subLR != nullptr){subLR->_parent = parent;}Node* pparent = parent->_parent;if (parent->_parent == nullptr){_root = subL;}else{if (pparent->_left == parent){pparent->_left = subL;}else if (pparent->_right == parent){pparent->_right = subL;}}subL->_parent = parent->_parent;parent->_parent = subL;subL->_bf = 0;parent->_bf = 0;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);if (bf == -1){parent->_bf = 1;}else if(bf == 1){subL->_bf = -1;}}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);if (bf == 1){parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;}}Node* _root;
};

// test.cpp 文件
#include "AVLTree.h"void test1()
{//vector<int> nums({ 16,3,7,11,9,26,18,14,15 });vector<int> nums({ 4,2,6,1,3,5,15,7,16,14 });AVLTree<int> tree;for (auto val : nums){tree.insert(val);}tree.InOrder();cout << endl;cout << tree.isAVLTEree() << endl;cout << tree.Hight() << endl;
}void test2()
{const int N = 1000;vector<int> v(N);srand(time(0));for (int i = 0; i < N; i++){v[i] = rand();cout << v[i] << endl;}AVLTree<int> tree;for (auto val : v){tree.insert(val);//cout << "insert:" << val << "->" << tree.isAVLTEree() << endl;}cout << tree.Hight() << endl;cout << tree.isAVLTEree() << endl;int i = 0;for (auto val : v){tree.Erase(val);cout << "Erase:" << val << "->" << i++ << endl;}cout << tree.isAVLTEree() << endl;}void test3()
{string str("abcdefghijklmnopqrst");AVLTree<char> t;for (auto data : str){t.insert(data);}for (int i = 0; i < str.size(); i++){t.Erase(str[i]);}t.InOrder();cout << t.isAVLTEree() << endl;
}void test4()
{AVLTree<int> t;t.insert(1);t.insert(3);t.insert(2);t.insert(4);for (int i = 0; i < 5; i++){t.Erase(i);}cout << t.isAVLTEree() << endl;
}
int main()
{test2();return 0;
}

2000个数据测试结果。
在这里插入图片描述

20000个数据结果
在这里插入图片描述
200000个测试结果

在这里插入图片描述
2000000个数据测试结果。

在这里插入图片描述


总结

以上就是我对于AVLTree插入和删除的理解。感谢支持!!!
在这里插入图片描述

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

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

相关文章

Unity中Shader雾效的实现方法三

文章目录 前言一、声明雾效所需要的内置变体二、在 v2f 中&#xff0c;定义 float4 worldPos &#xff1a;TEXCOORD1三、在顶点着色器中添加&#xff0c;会自动取o.worldPos.w裁剪空间下的坐标z值.在这里插入图片描述 四、在片元着色器&#xff0c;添加以下两句话1、UNITY_EXTR…

三江城115m²3室2厅2卫,现代简约不单是居所更是对生活的向往。福州中宅装饰,福州装修

【前言】 简洁有力&#xff0c;静默无声。 以简约精致的方式&#xff0c;展现现代都市生活&#xff1b; 经典不因潮流褪色&#xff0c;不为悦人只为悦己。 项目信息 项目名称 | 三江城 设计地址 | 福建福州 项目面积 | 115㎡ 项目户型 | 3室2厅 设计风格 | 现代简约 全…

LeetCode(23)找出字符串中第一个匹配项的下标【数组/字符串】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 找出字符串中第一个匹配项的下标 1.题目 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 hays…

黑马程序员微服务第四天课程 分布式搜索引擎1

分布式搜索引擎01 – elasticsearch基础 0.学习目标 1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; …

2016Outlook显示正在启动无法进入Outlook

2016Outlook显示正在启动无法进入Outlook 故障现象&#xff1a; 因上次非正常关闭&#xff0c;导致Outlook启动时&#xff0c;一直处于启动界面&#xff0c;无法进入主界面正常工作 故障截图&#xff1a; 故障原因&#xff1a; 数据文件异常导致 解决方案&#xff1a; 1、关…

YOLO改进系列之注意力机制(CoTAttention模型介绍)

简介 CoTAttention网络是一种用于多模态场景下的视觉问答&#xff08;Visual Question Answering&#xff0c;VQA&#xff09;任务的神经网络模型。它是在经典的注意力机制&#xff08;Attention Mechanism&#xff09;上进行了改进&#xff0c;能够自适应地对不同的视觉和语言…

趣学python编程 (一、计算机基础知识科普)

未来是高度科技化和智能化的时代。过去不识字的叫“文盲”&#xff0c;如今不懂点计算机知识&#xff0c;则可能是新时代的“文盲”。不论从事什么行业&#xff0c;了解下计算机和编程都是有益的。Python 连续多年占据最受欢迎的编程语言榜首&#xff0c;未来Python有机会成为像…

wps、office插入的复选框无法设置字体及大小?教你一招

插入的表单无法设置字体及大小 脑瓜子嗡嗡的吧&#xff1f;&#xff01;&#xff01; 如果没有强制要求&#xff0c;建议就换成开发工具下的复选框吧 如果一定要用上面这种&#xff0c;就自己做一个吧&#xff0c;设置方法如下 制作方法&#xff1a;插入选项卡插入窗体的复选框…

kickstarter数据采集

搜索界面字段如下&#xff1a; 详情界面字段如下&#xff1a; 评论页面采集&#xff1a; 需要的可以找我沟通

【云原生-Kurbernets篇】Kurbernets集群的调度策略

调度 一、Kurbernetes的list-watch机制1.1 list-watch机制简介1.2 创建pod的流程&#xff08;结合list-watch机制&#xff09; 二、Scheduler的调度策略2.1 简介2.2 预选策略&#xff08;predicate&#xff09;2.3 优选策略&#xff08;priorities&#xff09; 三、标签管理3.1…

基于Gin+Gorm框架搭建MVC模式的Go语言企业级后端系统

文/朱季谦 环境准备&#xff1a;安装Gin与Gorm 本文搭建准备环境&#xff1a;GinGormMySql。 Gin是Go语言的一套WEB框架&#xff0c;在学习一种陌生语言的陌生框架&#xff0c;最好的方式&#xff0c;就是用我们熟悉的思维去学。作为一名后端Java开发&#xff0c;在最初入门…

【AI视野·今日NLP 自然语言处理论文速览 第六十五期】Mon, 30 Oct 2023

AI视野今日CS.NLP 自然语言处理论文速览 Mon, 30 Oct 2023 Totally 67 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers An Approach to Automatically generating Riddles aiding Concept Attainment Authors Niharika Sri Parasa,…

为什么esp8266刷入了固件,无法接受AT指令

我遇到的解决方法是&#xff1a;是串口调试助手出了问题。所以需要更换一个串口调试助手软件。 上面这个就是我换了的软件 在开发的时候&#xff0c;经常会遇到软件故障&#xff0c;导致正确的方法&#xff0c;但是没有效果&#xff0c;好比以前用盗版的8.7版本的Proteus模拟…

idea中git 移除对某个文件的跟踪

应用场景如下 某个log 文件&#xff0c;被同事用git 提交到了服务器&#xff0c;本地拉去之后我们的跟踪也会受影响 取消跟踪的方法如下&#xff1a; 删除本地缓存 git rm --cached "logs/test.log" 提交无效的log git commit -m "ignore log" 再将lo…

时间序列预测实战(十六)PyTorch实现GRU-FCN模型长期预测并可视化结果

往期回顾&#xff1a;时间序列预测专栏——包含上百种时间序列模型带你从入门到精通时间序列预测 一、本文介绍 本文讲解的实战内容是GRU-FCN(门控循环单元-全卷积网络)&#xff0c;这是一种结合了GRU&#xff08;用于处理时间序列数据&#xff09;和FCN&#xff08;全卷积网络…

【数据结构】单链表 | 详细讲解

线性表顺序存储结构的优缺点 顺序表优点 无须为了表示中间的元素之间的逻辑关系而增加额外的存储空间&#xff1b;因为以数组形式存储&#xff0c;可以快速地存取表中任一位置的元素。 顺序表缺点 插入和删除操作需要移动大量元素&#xff0c;时间复杂度为O(N)&#xff1b;…

Android图形系统之X11、Weston、Wayland、Mesa3D、ANGLE、SwiftShader介绍(十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

redis运维(七)基础通用命令

一 基础通用命令 备注&#xff1a; 与具体数据类型无关Tab键 自动补全补充&#xff1a; redis 命令是不区分大小写 通用不到 10 个提升逼格的 redis 命令 后续&#xff1a; slowlog、rename-command、monitor、set ① help command 需求&#xff1a; 显示有关redis命令的…

就近值 reduce用法 时间戳与时间点对比循环查找

后台接口返回的13为时间戳 需要与数据data的time做对比&#xff0c;查找出最近的值 data的数据结构如下&#xff1a; 将&#xff1a;改为空格&#xff0c;变成数字之间的对比 //查找最近的时间getNearestTime(timestamp, data) {let date new Date(timestamp)let h date.ge…

openGauss学习笔记-122 openGauss 数据库管理-设置密态等值查询-密态支持函数/存储过程

文章目录 openGauss学习笔记-122 openGauss 数据库管理-设置密态等值查询-密态支持函数/存储过程122.1 创建并执行涉及加密列的函数/存储过程 openGauss学习笔记-122 openGauss 数据库管理-设置密态等值查询-密态支持函数/存储过程 密态支持函数/存储过程当前版本只支持sql和P…