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

C++AVL树

目录

一.AVL树的性质

二.AVL树节点的定义

三.AVL树插入操作 

 四.AVL树的旋转操作

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

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

4.3左右双旋:新节点插入较高左子树的右侧

4.4右左双旋 :新节点插入较高右子树的左侧

五.AVL树验证 

六.AVL树的删除

七.AVL树性能


一.AVL树的性质

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整)

特点

  • AVL树可以是空树
  • 左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

注意:如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O(log_2 n),搜索时间复杂度O(log_2 n)

 

 

二.AVL树节点的定义

AVL树的左右子树的高度差不能超过1,那么我们如何让快速判断是不是AVL树呢?

我们可以引入平衡因子来解决这个问题。如果右子树比左子树高一层,则此时平衡因子为1;如果左子树比有子树高一层,则此时平衡因子为-1;如果右子树和左子树一样高,则此时平衡因子为0。(这里平衡因子计算是右子树-左子树,当然左子树-右子树也可以)

依据此规则,如果平衡因子为2/-2,则说明平衡被打破。

在调整不平衡的AVL树时,需要频繁访问父亲节点,所以在AVL树节点定义中,除了定义指向左右节点的指针以外,还需要一个指向父亲节点的指针。

AVL树节点定义如下:

template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data = T()): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;AVLTreeNode<T>* _pRight;AVLTreeNode<T>* _pParent;T _data;int _bf;// 节点的平衡因子
};

三.AVL树插入操作 

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么 AVL树的插入过程可以分为两步:

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

平衡因子调节:(pCur为新插入节点,pParent为pCur父亲节点)

pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent 的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:

  1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可  
  2.  如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可  

此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2:  

  • 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整 成0,此时满足 AVL树的性质,插入成功  
  • 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更 新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新  
  • 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进 行旋转处理

至此我们可以对AVL插入操作有了大致框架:

bool Insert(const T& data)
{Node* newnode = new Node(data);if (_pRoot == nullptr){_pRoot = newnode;return true;}Node* pcur = _pRoot;Node* parent = nullptr;while (pcur){parent = pcur;if (data < pcur->_data){pcur = pcur->_pLeft;}else if (data > pcur->_data){pcur = pcur->_pRight;}else{return false;}}if (parent->_data < data){parent->_pRight = newnode;}elseparent->_pLeft = newnode;//从下往上更新平衡因子pcur = newnode;while (parent){if (pcur == parent->_pLeft){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){//...}else if (parent->_bf == 1 || parent->_bf == -1){//...}else if (parent->_bf == 2 || parent->_bf == -2){//...break;//旋转后字数高度不变,不需要向上调整}else{return false;}}return true;
}

 四.AVL树的旋转操作

在插入操作时我们提出有2和-2的特使情况,这时候就需要进行旋转操作。

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

由图可以看出,在a插入一个节点则右边比较高,为了保持平衡我们需要向左旋转。

代码如下:

// 左单旋
void RotateL(Node* parent)
{Node* subr = parent->_pRight;Node* subrl = subr->_pLeft;parent->_pRight = subrl;if (subrl)subrl->_pParent = parent;Node* pparent = parent->_pParent;parent->_pParent = subr;if (parent==_pRoot){_pRoot = subr;subr->_pParent = nullptr;}else{if (pparent->_pLeft == parent){pparent->_pLeft=subr;}else{pparent->_pRight=subr;}subr->_pParent = pparent;}subr->_bf = parent->_bf = 0;
}

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

和左单旋类似,代码如下:

// 右单旋
void RotateR(Node* parent)
{Node* subl = parent->_pLeft;Node* sublr = subl->_pRight;parent->_pLeft = sublr;subl->_pRight = parent;if (sublr){sublr->_pParent = parent;}Node* pparent = parent->_pParent;parent->_pParent = subl;if (_pRoot == parent){_pRoot = subl;subl->_pParent = nullptr;}else{if (pparent->_pLeft == parent){pparent->_pLeft=subl;}else{pparent->_pRight=subl;}subl->_pParent = pparent;}subl->_bf = parent->_bf = 0;
}

4.3左右双旋:新节点插入较高左子树的右侧

分成两种情况:

这两种情况都属于在较高左子树的右侧插入,处理方式都是相同的,唯一的区别在于最后旋转完成后,更新平衡因子时的值不同。

我们对其中一种情况进行分析:

 

可以看到这两种情况中,如果在b下面插入新节点,那么旋转过后30和60的平衡因子更新成0,90的平衡因子更新成1;如果在c下面插入新节点,则是60和90的平衡因子更新成0,30的平衡因子更新成-1

而新节点究竟插入到了b下面还是在c下面,我们可以通过插入节点后60的平衡因子来判断。

 

// 左右双旋
void RotateLR(Node* pParent)
{Node* subl = pParent->_pLeft;Node* sublr = subl->_pRight;int bf = sublr->_bf;RotateL(pParent->_pLeft);RotateR(pParent);if (bf == 0){subl->_bf = sublr->_bf = pParent->_bf = 0;}else if (bf == -1){subl->_bf = sublr->_bf = 0;pParent->_bf = 1;}else if (bf == 1){pParent->_bf =sublr->_bf = 0;subl->_bf = -1;}
}

4.4右左双旋 :新节点插入较高右子树的左侧

// 右左双旋
void RotateRL(Node* pParent)
{Node* subr = pParent->_pRight;Node* subrl = subr->_pLeft;int bf = subrl->_bf;RotateR(pParent->_pRight);RotateL(pParent); if (bf == 0){subr->_bf = subrl->_bf = pParent->_bf = 0;}else if (bf == 1){subrl->_bf =subr->_bf= 0;pParent->_bf = -1;}else if (bf == -1){pParent->_bf = subrl->_bf = 0;subr->_bf = 1;}
}

总结: 假如以pParent为根的子树不平衡,即pParent的平衡因子为2或-2,分以下情况考虑

1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR

  • 当pSubR的平衡因子为1时,执行左单旋
  • 当pSubR的平衡因子为-1时,执行右左双旋

2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL

  • 当pSubL的平衡因子为-1是,执行右单旋
  • 当pSubL的平衡因子为1时,执行左右双旋

旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。  

五.AVL树验证 

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

1. 验证其为二叉搜索树

    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

2. 验证其为平衡树

  • 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
  • 节点的平衡因子是否计算正确
// 根据AVL树的概念验证pRoot是否为有效的AVL树
bool _IsAVLTree(Node* pRoot)
{if (pRoot == nullptr){return true;}int leftheight = _Height(pRoot->_pLeft);int rightheight = _Height(pRoot->_pRight);if (rightheight - leftheight !=pRoot->_bf){cout << "平衡因子异常" << endl;return false;}return abs(rightheight - leftheight) <= 1 && _IsAVLTree(pRoot->_pLeft)&& _IsAVLTree(pRoot->_pRight);
}
size_t _Height(Node* pRoot)
{if (pRoot == nullptr){return 0;}size_t lefth=_Height(pRoot->_pLeft);size_t righth = _Height(pRoot->_pRight);return max(lefth, righth)+1;
}

六.AVL树的删除

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不 错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。

七.AVL树性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这 样可以保证查询时高效的时间复杂度,即log_2 (N)。但是如果要对AVL树做一些结构修改的操 作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数 据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

完整代码:

#pragma once
#include<iostream>
using namespace std;
template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data = T()): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;AVLTreeNode<T>* _pRight;AVLTreeNode<T>* _pParent;T _data;int _bf;// 节点的平衡因子
};// AVL: 二叉搜索树 + 平衡因子的限制
template<class T>
class AVLTree
{typedef AVLTreeNode<T> Node;
public:AVLTree(): _pRoot(nullptr){}// 在AVL树中插入值为data的节点bool Insert(const T& data){Node* newnode = new Node(data);if (_pRoot == nullptr){_pRoot = newnode;return true;}Node* pcur = _pRoot;Node* parent = nullptr;while (pcur){parent = pcur;if (data < pcur->_data){pcur = pcur->_pLeft;}else if (data > pcur->_data){pcur = pcur->_pRight;}else{return false;}}if (parent->_data < data){parent->_pRight = newnode;}elseparent->_pLeft = newnode;//从下往上更新平衡因子pcur = newnode;while (parent){if (pcur == parent->_pLeft){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){pcur = parent;parent = pcur->_pParent;}else if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == 2 && pcur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && pcur->_bf == -1){RotateR(parent);}else if (parent->_bf == -2 && pcur->_bf == 1){RotateLR(parent);}else if (parent->_bf == 2 && pcur->_bf == -1){RotateRL(parent);}break;//旋转后字数高度不变,不需要向上调整}else{return false;}}return true;}// AVL树的验证bool IsAVLTree(){return _IsAVLTree(_pRoot);}void _Inorder(Node* Root){if (Root == nullptr){return ;}_Inorder(Root->_pLeft);cout << Root->_data << " ";_Inorder(Root->_pRight);}void Inorder(){_Inorder(_pRoot);}
private:// 根据AVL树的概念验证pRoot是否为有效的AVL树bool _IsAVLTree(Node* pRoot){if (pRoot == nullptr){return true;}int leftheight = _Height(pRoot->_pLeft);int rightheight = _Height(pRoot->_pRight);if (rightheight - leftheight !=pRoot->_bf){cout << "平衡因子异常" << endl;return false;}return abs(rightheight - leftheight) <= 1 && _IsAVLTree(pRoot->_pLeft)&& _IsAVLTree(pRoot->_pRight);}int _Height(Node* pRoot){if (pRoot == nullptr){return 0;}int lefth=_Height(pRoot->_pLeft);int righth = _Height(pRoot->_pRight);return max(lefth, righth)+1;}// 右单旋void RotateR(Node* parent){Node* subl = parent->_pLeft;Node* sublr = subl->_pRight;parent->_pLeft = sublr;subl->_pRight = parent;if (sublr){sublr->_pParent = parent;}Node* pparent = parent->_pParent;parent->_pParent = subl;if (_pRoot == parent){_pRoot = subl;subl->_pParent = nullptr;}else{if (pparent->_pLeft == parent){pparent->_pLeft=subl;}else{pparent->_pRight=subl;}subl->_pParent = pparent;}subl->_bf = parent->_bf = 0;}// 左单旋void RotateL(Node* parent){Node* subr = parent->_pRight;Node* subrl = subr->_pLeft;parent->_pRight = subrl;if (subrl)subrl->_pParent = parent;Node* pparent = parent->_pParent;parent->_pParent = subr;if (parent==_pRoot){_pRoot = subr;subr->_pParent = nullptr;}else{if (pparent->_pLeft == parent){pparent->_pLeft=subr;}else{pparent->_pRight=subr;}subr->_pParent = pparent;}subr->_bf = parent->_bf = 0;}// 右左双旋void RotateRL(Node* pParent){Node* subr = pParent->_pRight;Node* subrl = subr->_pLeft;int bf = subrl->_bf;RotateR(pParent->_pRight);RotateL(pParent); if (bf == 0){subr->_bf = subrl->_bf = pParent->_bf = 0;}else if (bf == 1){subrl->_bf =subr->_bf= 0;pParent->_bf = -1;}else if (bf == -1){pParent->_bf = subrl->_bf = 0;subr->_bf = 1;}}// 左右双旋void RotateLR(Node* pParent){Node* subl = pParent->_pLeft;Node* sublr = subl->_pRight;int bf = sublr->_bf;RotateL(pParent->_pLeft);RotateR(pParent);if (bf == 0){subl->_bf = sublr->_bf = pParent->_bf = 0;}else if (bf == -1){subl->_bf = sublr->_bf = 0;pParent->_bf = 1;}else if (bf == 1){pParent->_bf =sublr->_bf = 0;subl->_bf = -1;}}private:Node* _pRoot;
};

 

 

 

相关文章:

  • 访问网页的全过程(分步骤的详细解析)
  • 通过Linux系统服务管理IoTDB集群的高效方法
  • C++ -- string
  • C++:求分数序列和
  • 强化学习PPO算法学习记录
  • 【Pandas】pandas DataFrame clip
  • GET请求如何传复杂数组参数
  • 使用oracle goldengate同步postgresql到postgresql
  • Eclipse SWT 1 等比缩放
  • Web端项目系统访问页面很慢,后台数据返回很快,网络也没问题,是什么导致的呢?
  • 文件包含2
  • OpenCV的 ccalib 模块用于自定义标定板的检测和处理类cv::ccalib::CustomPattern()----函数calibrate
  • 火山引擎火山云主推产品
  • wpf UserControl 更换 自定义基类
  • PX4开始之旅(一)自动调参
  • Windows10 本地部署 IPFS(go-ipfs)
  • NX884NX891美光固态闪存NX895NX907
  • 汽车租赁|基于Java+vue的汽车租赁系统(源码+数据库+文档)
  • 【日撸 Java 三百行】Day 7(Java的数组与矩阵元素相加)
  • RT-Thread 深入系列 Part 1:RT-Thread 全景总览
  • 中国科协发声:屡禁不止的奇葩论文再次敲响学风建设警钟
  • 中国金茂新任命三名副总裁,撤销区域公司
  • 巴基斯坦称成功拦截印度导弹,空军所有资产安全
  • 上汽享道出行完成13亿元C轮融资,已启动港股IPO计划
  • 央行:中国政府债务扩张仍有可持续性
  • 铲屎官花5万带猫狗旅行,宠旅生意有多赚?