目录

一.AVL树的概念与性质

1.1概念

1.2性质

二.AVL树操作

2.1AVL树节点定义

2.2AVL树插入

1.插入节点

2.保持树的平衡

3.更新parent平衡因子

三.AVL树的性能


我们在使用map/multimap/set/multiset这些容器时,有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

一.AVL树的概念与性质

1.1概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

1.2性质

它具有以下性质:

  • 他的左右子树都是AVL树
  • 左右子树高度差(又称平衡因子)的绝对值不操作1
  • 如果一颗二叉搜索树是高度平衡的,他就是AVL树,如果他有n个节点,其高度可保持在O(log2n),搜索时间复杂度O(log2n)。

二.AVL树操作

AVL树的创建,进行插入与删除节点,同时进行平衡判断。(我在分析时,会将代码拆开来,而且只会写核心的部分,最终代码我会附上链接(C++实现链接),需要的直接去拿。)

2.1AVL树节点定义

struct AVLTreeNode{AVLTreeNode* left;AVLTreeNode* right;AVLTreeNode* parent;T data;int bf;//VAL树中的平衡因子AVLTreeNode(const T& x):left(nullptr), right(nullptr), parent(nullptr), data(x), bf(0){}
};

2.2AVL树插入

1.插入节点

我们在对AVL树进行插入的时候,其实就是二叉搜索时的插入。

bool insert(const T& data){//先判断是否为空树if(nullptr == root){root = new Node(data);return true;}Node* cur = root;Node* parent = nullptr;while(cur){parent = cur;if(data < cur->data){cur =cur->left;}else if (data > cur->data){cur = cur ->right;}else{return false;}}//当找到要插入的位置时,进行插入cur = new Node(data);//如果此时插入的节点是大于父节点的,将其插入父节点的左边if(parent->data > data) parent->left = cur;else parent->right = cur;return true;
}

2.保持树的平衡

插入节点,如果此时的树满足平衡,那么就不用进行调节,但是如果此时树不再保持平衡,就要对树进行旋转调整,分为四种情况。(我在分析时,会将代码拆开来,而且只会写核心的部分,最终代码我会附上链接(C++实现),需要的直接去拿。)

(1)右单旋

发生的情况:当一个平衡二叉树满足具有较高左子树时(如下图A),且要插入的节点插入到了较高左子树的左侧(此时注意是左子树,并非左孩子,因此当插入节点20为30的有孩子,其操作是相同的),此时进行右单旋。

我们根据上图进行分析,在进行旋转时,第一步先将parent->left指向curR节点,此时并要对curR的parent进行更新,但是必须注意的是此时curR是否为空,如果此时curR为空再对他进行操作可能代码会崩溃,如下代码是对curR进行更新。

parent->left = curR;
//因为我们使用的是孩子双亲表示法,因此更新curR节点的parent
//此时curR不为空时,才能对其进行更新
if(curR) curR->parent = parent; 

第一步操作结束,下来让cur->right指向parent节点,此时我们并不知道parent节点是否为之前树的根节点,因此要先将其父节点进行保存,后面要进行判断,再对插入的节点旋转结束后,对其旋转因子再进行更新。下面是第二步代码:

cur->right = parent;
Node* pparent = parent->parent;
//现在我们先更新parent节点的父节点
parent->parent = cur;
//再更新cur的父节点
cur->parent = pparent;
//此时我们要注意,判断pparent是否为空,如果为空,那么root直接就是cur,如果不为空在进行判断他是父节点的左子树还是右子树
if(pparent == nullptr)//说明此时更新节点后,cur应该为根节点root = cur;
else if(pparent->left == parent)//说明之前是根节点的左子树pparent->left = cur;
else//说明之前是右子树pparent->right = cur;
parent->bf = 0;
cur->bf = 0;

(2)左单旋

发生的情况:当一个平衡二叉树满足具有较高右子树时(如下图A),且要插入的节点插入到了较高右子树的右侧(此时注意是右子树,并非有孩子,因此,其操作是相同的),此时进行左单旋。

我们根据上图进行分析,当开始进行左单旋时,我们先将parent节点进行处理,如下代码:

parent->right = curL;
//因为我们使用的是孩子双亲表示法,因此更新curL节点的parent
//此时curL不为空时,才能对其进行更新
if(curL) curL->parent = parent;

当处理完第一步之后,开始对cur节点进行处理,此时我们依旧要注意parent节点在左单旋之前是否是由父节点的,如下代码:

//在这里先创建parent的父节点,待会进行判断
Node* pparent = parent->parent;
cur->left = parent;
//更改parent的父节点
parent->parent = cur;
cur->parent = pparent;
//此时上面的代码将parent与curL两个节点的父节点都处理完毕,
//此时我们就要考虑cur此时是否有父节点,且是父节点的那个子树
if(pparent == nullptr)//此时parent的之前的父节点为空,那么此时cur直接就是根节点root = cur;
else if(pparent->left == parent)pparent->left = cur;
elseppanret->right = cur;
cur->bf = 0;
parent->bf = 0;

(3)左右双旋与右左双旋

左右双旋发生的情况:当原本的平衡二叉树是满足具有较高的左子树时,此时带插入的节点插入到了较高左子树的右侧(内侧),如下图,此时就要使用左右双旋。

我们在这里发现,他与要进行右单旋的情况有些像,但是在向AVL树中进行插入时,左子树的内侧,此时我们先试一试右单旋,观察他是否可以解决平衡问题,如下图。

我们观察到上面的图,发现虽然这种情况下与右单旋的情况有些相似,但是不能够达到平衡的情况,因此我们就要重新考虑如何旋转使之平衡。如下图所示:

我们观察上图,可以先利用左单旋将parent的左子树进行调整,将其转化成结构B,此时的二叉树的结构符合右单旋的情况,因此再进行右单旋,最终满足平衡的情况,因此此时需要先进行左单旋,再进行右单旋。我们再看图D到图E的平衡因子的变化。因为,我们在前两次的左单旋与右单旋之后都对他当前的parent与cur的平衡因子进行的更改,在最后在curR上插入新节点的位置会引起不同的平衡因子的更新。此时的代码就是复用上面的代码。因此在这里就不再重复。

右左双旋发生的情况:当原本的平衡二叉树是满足具有较高的右子树时,此时带插入的节点插入到了较右子树的左侧(外侧),此时就要使用右左双旋。他的思路与左右双旋的思路相同,但是右左双旋实现将parent节点的右子树通过左单旋进行调整,使其满足左单旋的情况,再进行左单旋。此时二叉树就会满足平衡。

3.更新parent平衡因子

在插入之前,平衡因子是满足情况的,当插入了cur节点之后,我们要从cur插入的位置向上更新parent的平衡因子,在更新时,我们只需要判断cur被插入到了parent的那棵子树,插入到左子树时,对parent->buf--;插入到右子树时,对parent->buf++。代码如下:

while(parent){//当parent不为空,那就相当于要遍历到二叉树的根节点即可if(parent->right == cur)//说明此时插入到了右子树的位置parent->bf++;else//说明此时插入到了左子树的位置parent->bf--;if( parent->bf == 0)//这里就是以parent为根的二叉树高度没有改变,对上层不会用影响break;else if(1 == parent->bf || -1 == parent->bf){//以parent为根的二叉树的高度增建了,需要继续往上层更新cur = parent;parent = parent->parent;}else{//此时已经违反了AVL树的特性if(parent->bf == -2){  if(cur->bf == -1){//此时进行右单旋}else{//此时进行左右双旋}else{if(cur->bf == -1){//此时进行右左双旋}else{//此时进行左单旋}break;}}
}

4.AVL树验证

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

  • 验证其为二叉搜索树 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  • 验证其为平衡树 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子) 节点的平衡因子是否计算正确
//求AVL树的深度(高度)int get_hight(node* root){if (root == nullptr) return 0;int hightleft = get_hight(root->left);int hightright = get_hight(root->right);int hight = hightleft > hightright ? hightleft + 1 : hightright + 1;return hight;}
//判断是否是AVLTreebool isAVLTree(node* root){if (nullptr == root) return true;int hightleft = get_hight(root->left);int hightright = get_hight(root->right);int bf = hightright - hightleft;if (abs(root->bf) > 1 || bf != root->bf){//当平衡因子的绝对值大于等于2,或者在更新平衡因子时出错,都返回falsecout << root->data << ":" << bf << "--" << root->bf << endl;return false;}return isAVLTree(root->left) && isAVLTree(root->right);//在进行检测他的左右子树}

三.AVL树的性能

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

详解AVL树(平衡二叉树)相关推荐

  1. AVL树(平衡二叉树)讲解,入门篇,适合新手观看

    1.1 概念 平衡二叉树就是为了让二叉搜索树的平均查找长度更短,时间复杂度更靠近logN,如果一个二叉搜索树不平衡了就会出现图1情况,完全变成一个数组,时间复杂度也变为了O(N). 平衡因子:平衡因子 ...

  2. SQL索引概念(详解B+树)

    Sql索引(index) 定义 分类 复合索引特性 复合索引最左特性(原则) 原理 索引及其扫描类型 索引的优缺点 扩展:索引工作原理 BTree+索引 怎么判断是否创建索引? 为什么Mysql用B+ ...

  3. 详解B树、B+树、B*树

    B树.B+树.B*树 1.前言: 动态查找树主要有:二叉查找树(Binary Search Tree),平衡二叉查找树(Balanced Binary Search Tree),红黑树(Red-Bla ...

  4. AVL树的实现(图文详解)

    AVL树的实现 AVL树定义 AVL树其实就是一棵特殊的二叉树,为什么会出现AVL树,AVL树比普通二叉树优势在什么地方呢? 我们知道,一棵普通的二叉搜索树,以其特殊的性质(左<根<右), ...

  5. mysql联合索引B 树_B+树和Mysql索引详解

    B+树总结 根据以下几篇文章总结的自己的心得,便于自己理解 B+树内部平衡详解 B+树存储原理 B+树存储 MySQL索引-B+树(看完你就明白了) 从B树.B+树.B*树谈到R 树 我们一般看到的B ...

  6. 平衡二叉树的调整(详解 LL、RR、LR、RL)

    浙江大学讲解视频 平衡二叉树(AVL)的定义: 任一结点的左右子树高度差的绝对值小于等于1,绝对值就是平衡因子 任一结点的左右子树均为AVL树 平衡二叉树也可以是一个空树 平均查找长度(ASL)(查找 ...

  7. 字符串处理【字典树】 - 原理 字典树详解

    字符串处理[字典树] - 原理 字典树详解 字典树,又称Trie树.单词查找树,是一种树形结构,也是哈希树的一种变种,主要用于统计.排序和存储大量的字符串(但不限于字符串),所以经常被搜索引擎系统用于 ...

  8. 蒙特卡洛树搜索(MCTS)详解

    蒙特卡洛树搜索(MCTS)详解 蒙特卡洛树搜索是一种经典的树搜索算法,名镇一时的 AlphaGo 的技术背景就是结合蒙特卡洛树搜索和深度策略价值网络,因此击败了当时的围棋世界冠军.它对于求解这种大规模 ...

  9. Linux设备树详解

    Linux设备树详解 设备树小故事 设备树文件 使用设备树 修改设备树文件 编译设备树 异常处理 编写驱动文件 参考资料 设备树小故事 设备树(Device Tree),将这个词分开就是"设 ...

  10. 双数组trie树详解

    目录 双数组trie树的构建 构建base array 构建check array 双数组trie树的查询 双数组trie树的构建 NLP中trie树常用于做快速查询,但普通的trie树由于要保存大量 ...

最新文章

  1. 浅析flex中的焦点focus
  2. 51CTO采访Cisco专家何凌:实现整体虚拟化
  3. qtp查询mysql_QTP中测试数据库连接
  4. poj2096_概率dp
  5. 红管2不显示服务器,红色管弦乐队2怎么建立服务器 | 手游网游页游攻略大全
  6. json传输二进制的方案(python版)
  7. 7种可能会导致内存泄漏的场景!
  8. 应用机器学习视频教程,哥伦比亚大学 2020版
  9. 连通子图什么意思_为什么海洋科学家说:地球是“漏”的?
  10. 领域驱动设计的个人理解
  11. 解决“ValueError: Stop argument for islice() must be None or an integer: 0 <= x <= sys.maxsize.”
  12. 易经入门(体系最完整,推荐书目最完备,易经周易入门必收藏)
  13. VS2019+DCMTK3.6.6环境配置
  14. 基于Java实现udp编程
  15. 清除微信或者企业微信的缓存或cookie
  16. Substance Painter 画高度贴图
  17. 图扑数字孪生青岛城轨,赋能智慧交通低碳发展
  18. ReclyclerView刷新数据
  19. 让机器耳濡目染:MIT提出跨模态机器学习模型
  20. 达索Catia许可证优化管理方案

热门文章

  1. 图像处理库Pillow的使用
  2. 国产矢量绘图软件--百绘大师
  3. 03 在CentOS7中安装oracle11g
  4. python监听网络请求_Python实现一个服务器监听多个客户端请求
  5. 创客使用Fusion 360 - 制作模型
  6. 洛谷 P1957 口算练习题
  7. 微博分享代码怎么显示自定义来源
  8. Arduino 通过双路L298N电机驱动模块控制麦克纳姆轮运动
  9. mysql如何更新视图,MySQL更新视图
  10. bugzilla使用规范分享