详解AVL树(平衡二叉树)
目录
一.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树(平衡二叉树)相关推荐
- AVL树(平衡二叉树)讲解,入门篇,适合新手观看
1.1 概念 平衡二叉树就是为了让二叉搜索树的平均查找长度更短,时间复杂度更靠近logN,如果一个二叉搜索树不平衡了就会出现图1情况,完全变成一个数组,时间复杂度也变为了O(N). 平衡因子:平衡因子 ...
- SQL索引概念(详解B+树)
Sql索引(index) 定义 分类 复合索引特性 复合索引最左特性(原则) 原理 索引及其扫描类型 索引的优缺点 扩展:索引工作原理 BTree+索引 怎么判断是否创建索引? 为什么Mysql用B+ ...
- 详解B树、B+树、B*树
B树.B+树.B*树 1.前言: 动态查找树主要有:二叉查找树(Binary Search Tree),平衡二叉查找树(Balanced Binary Search Tree),红黑树(Red-Bla ...
- AVL树的实现(图文详解)
AVL树的实现 AVL树定义 AVL树其实就是一棵特殊的二叉树,为什么会出现AVL树,AVL树比普通二叉树优势在什么地方呢? 我们知道,一棵普通的二叉搜索树,以其特殊的性质(左<根<右), ...
- mysql联合索引B 树_B+树和Mysql索引详解
B+树总结 根据以下几篇文章总结的自己的心得,便于自己理解 B+树内部平衡详解 B+树存储原理 B+树存储 MySQL索引-B+树(看完你就明白了) 从B树.B+树.B*树谈到R 树 我们一般看到的B ...
- 平衡二叉树的调整(详解 LL、RR、LR、RL)
浙江大学讲解视频 平衡二叉树(AVL)的定义: 任一结点的左右子树高度差的绝对值小于等于1,绝对值就是平衡因子 任一结点的左右子树均为AVL树 平衡二叉树也可以是一个空树 平均查找长度(ASL)(查找 ...
- 字符串处理【字典树】 - 原理 字典树详解
字符串处理[字典树] - 原理 字典树详解 字典树,又称Trie树.单词查找树,是一种树形结构,也是哈希树的一种变种,主要用于统计.排序和存储大量的字符串(但不限于字符串),所以经常被搜索引擎系统用于 ...
- 蒙特卡洛树搜索(MCTS)详解
蒙特卡洛树搜索(MCTS)详解 蒙特卡洛树搜索是一种经典的树搜索算法,名镇一时的 AlphaGo 的技术背景就是结合蒙特卡洛树搜索和深度策略价值网络,因此击败了当时的围棋世界冠军.它对于求解这种大规模 ...
- Linux设备树详解
Linux设备树详解 设备树小故事 设备树文件 使用设备树 修改设备树文件 编译设备树 异常处理 编写驱动文件 参考资料 设备树小故事 设备树(Device Tree),将这个词分开就是"设 ...
- 双数组trie树详解
目录 双数组trie树的构建 构建base array 构建check array 双数组trie树的查询 双数组trie树的构建 NLP中trie树常用于做快速查询,但普通的trie树由于要保存大量 ...
最新文章
- 浅析flex中的焦点focus
- 51CTO采访Cisco专家何凌:实现整体虚拟化
- qtp查询mysql_QTP中测试数据库连接
- poj2096_概率dp
- 红管2不显示服务器,红色管弦乐队2怎么建立服务器 | 手游网游页游攻略大全
- json传输二进制的方案(python版)
- 7种可能会导致内存泄漏的场景!
- 应用机器学习视频教程,哥伦比亚大学 2020版
- 连通子图什么意思_为什么海洋科学家说:地球是“漏”的?
- 领域驱动设计的个人理解
- 解决“ValueError: Stop argument for islice() must be None or an integer: 0 <= x <= sys.maxsize.”
- 易经入门(体系最完整,推荐书目最完备,易经周易入门必收藏)
- VS2019+DCMTK3.6.6环境配置
- 基于Java实现udp编程
- 清除微信或者企业微信的缓存或cookie
- Substance Painter 画高度贴图
- 图扑数字孪生青岛城轨,赋能智慧交通低碳发展
- ReclyclerView刷新数据
- 让机器耳濡目染:MIT提出跨模态机器学习模型
- 达索Catia许可证优化管理方案