先说说二叉搜索树: 是有序的二叉树,根值>左节点值,右节点值>根值。

    如果要查找某个值,二叉搜索树和二分查找一样,每进行一次值比较,就会减少一半的遍历区间。

    但是,如果树插入的值一直递增/递减,就会出现这种情况:

    这样,二叉树性能就完全失去了,直接退化成了顺序表,查找效率低下。

    由此,引入了能保持性能最佳的二叉搜索树。

    AVL树: 具有高度平衡的二叉搜索树

    性质: 1.它的左右子树都是AVL树

        2.左右子树高度差(简称平衡因子)的绝对值不超过1

                  

      搜索的时间复杂度: O(log2(n))

       AVL树节点    

struct AVLTree{
pair<K,V> kv;
AVLTreeNode* left;
AVLTreeNode* right;
AVLTreeNode* parent; int bf; //balance factor
};

               与二叉搜索树不同的是引入了父节点(方便后面讲的旋转)和平衡因子(保持高度平衡的核心)。

      AVL树的插入:

      在插入节点后,需要调整平衡:

      1.bf更新规则:新增在左 父亲bf-1 ;  新增在右 父亲bf+1

      2.持续往祖先更新  如果 祖先bf==0,祖先肯定是从1或者-1变来的,那就说明祖先所在子树的高度不变,停止更新;

                        祖先|bf|==1,祖先肯定是从0变来的,说明祖先所在子树高度变了,继续往上更新;

                 祖先|bf|==2,祖先肯定是从1或者-1变来的,说明高度已经不平衡了,需要旋转调整;

      旋转:

      1.左单旋: 新结点插在较高右子树右侧   2 1  

                     

      2.右单旋: 新结点插在较高左子树左侧  -2 -1 

                    

      3.左右双旋(先左单旋parent,再右单旋g):  新结点插在较高左子树右侧  -2 1 -> -2 -2                                

      4.右左双旋:  新结点插在较高右子树左侧   2 -1 -> 2 2

    总结:

        1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
      当pSubR的平衡因子为1时,执行左单旋
      当pSubR的平衡因子为-1时,执行右左双旋
      2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
      当pSubL的平衡因子为-1是,执行右单旋
      当pSubL的平衡因子为1时,执行左右双旋
      旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。
 
      AVL树旋转代码:
//右单旋
void RotateR(Node* parent)
{
Node* subL = parnet->left;
Node* subLR = subL->right; //右旋下去,原根左孩子成为新根
parent->left = subLR;
if(subLR) //LR不为空,才连接其父亲
subLR->parent = parent; //更新新根与原根的关系
subL->right = parent; //记录原根的父
Node* ppNode = parent->parent;
//更新新根与原根的关系
parent->parent = subL; //下面都是因为双向链表带来的问题
//更新原根父与新根的关系
//新根就是根节点
if(ppNode==nullptr)
{
root = subL;
root->parent = nullptr;
}
//新根更新与原根父的关系
else
{
if(ppNode->left==parent)
ppNode->left = subL;
else
ppNode->right = subL; subL->parent = ppNode;
}
//根据上图,更新部分节点平衡因子
parent->bf = subL->bf = 0;
}
//左单旋同理
void RotateL(Node* parent); 
//右左双旋
void RotateRL(Node* parent)
{
Node* subR = parent->right;
Node* subRL = subR->left;
int bf = subRL->bf;
//右左双旋
RotateR(parent->right);
RotateL(parent); //更新双旋后的bf
//由于单旋会将bf置0,而双旋有三种情况,需要记录旋转前的新根的bf
//1.新结点插入后,subRL的bf是0 --- 插入的节点就是subRL
if(bf == 0)
{
parent->bf = subRL->bf = subR->bf = 0;
}
//2.新结点插入后,subRL的bf是1 --- 插入的节点在subRL右边
else if(bf == 1)
{
subR->bf = 0;
parent->bf = -1;
subRL->bf = 0;
}
//3.新结点插入后,subRL的bf是-1 --- 插入的节点在它subRL左边
else if(bf == -1)
{
parent->bf = 0;
subR->bf = 1;
subRL->bf = 0;
}
}
//左右双旋同理
void RotateLR(Node* parnet);

       AVL树插入代码

bool Insert(const pair<K,V>& kv)
{
//插入结点
if(root == nullptr)
{
root=new Node(kv);
root->bf = 0;
return true;
}
Node* parent = nullptr;
Node* cur = root;
while(cur)
{
if(cur->kv.first < kv.first) else if(cur->kv.first > kv.first) else
return false;
}
cur = new Node(kv);
//父节点连接插入的结点
if(parent->kv.first < kv.first)
{
parent->right = cur;
cur->parent = parent;
}
else
{
...
} //调平衡
//1.新增在左 父亲bf-1 新增在右 父亲bf+1
//2.持续往祖先更新 if 祖先bf == 0,则祖先所在子树高度不变,停止往上更新
// if 祖先|bf| == 1,则祖先所在子树高度变了,继续往上更新
// if 祖先|bf| == 2,则祖先所在树不平衡,则旋转调整
//1.更新平衡因子
while(parent)
{
if(cur == parent->right)
parent->bf++;
else
parent->bf--; //高度不变,更新完成
if(parent->bf == 0)
break;
//高度变了,继续更新
else if(abs(parent->bf) == 1)
{
cur = parent;
parent = parent->parent;
}
//不平衡,旋转调整
else if(abs(parent->bf) == 2)
{
//判断旋转方式
if(parent->bf==2)
{
if(cur->bf==1)
RotateL(parent);
else if(cur->bf==-1)
RotateRL(parent);
}
else if(parent->bf==-2)
{
if(cur->bf==-1)
RotateR(parent);
else if(cur->bf==1)
RotateLR(parent);
}
break;
}
else
assert(false);
}
}

      因为引入了bf和双向链表,所以有了很多坑,应该避免以下两点:

      1.单旋完成后,注意更新其新根与原根父节点的连接关系

      2.双旋完成后,注意根据双旋规律进行bf更新

    

DS AVL树详解的更多相关文章

  1. 数据结构图文解析之:AVL树详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  2. AVL树详解

    AVL树 参考了:http://www.cppblog.com/cxiaojia/archive/2012/08/20/187776.html 修改了其中的错误,代码实现并亲自验证过. 平衡二叉树(B ...

  3. trie字典树详解及应用

    原文链接    http://www.cnblogs.com/freewater/archive/2012/09/11/2680480.html Trie树详解及其应用   一.知识简介        ...

  4. Linux DTS(Device Tree Source)设备树详解之二(dts匹配及发挥作用的流程篇)【转】

    转自:https://blog.csdn.net/radianceblau/article/details/74722395 版权声明:本文为博主原创文章,未经博主允许不得转载.如本文对您有帮助,欢迎 ...

  5. JavaScript---Dom树详解,节点查找方式(直接(id,class,tag),间接(父子,兄弟)),节点操作(增删改查,赋值节点,替换节点,),节点属性操作(增删改查),节点文本的操作(增删改查),事件

    JavaScript---Dom树详解,节点查找方式(直接(id,class,tag),间接(父子,兄弟)),节点操作(增删改查,赋值节点,替换节点,),节点属性操作(增删改查),节点文本的操作(增删 ...

  6. 线段树详解 (原理,实现与应用)(转载自:http://blog.csdn.net/zearot/article/details/48299459)

    原文地址:http://blog.csdn.net/zearot/article/details/48299459(如有侵权,请联系博主,立即删除.) 线段树详解    By 岩之痕 目录: 一:综述 ...

  7. Linux dts 设备树详解(二) 动手编写设备树dts

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...

  8. Linux dts 设备树详解(一) 基础知识

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 1 前言 2 概念 2.1 什么是设备树 dts(device tree)? 2. ...

  9. trie树--详解

    文章作者:yx_th000 文章来源:Cherish_yimi (http://www.cnblogs.com/cherish_yimi/) 转载请注明,谢谢合作.关键词:trie trie树 数据结 ...

随机推荐

  1. 12-网页,网站,微信公众号基础入门(编写后台PHP程序,实现Airkiss配网)

    https://www.cnblogs.com/yangfengwu/p/11067590.html 首先说一下,这两个地方需要配置一样 网站根目录建个文件夹 airkiss的文件夹 里面放上 ind ...

  2. nginx 反向代理之 proxy_set_header

    proxy_set_header用来设定被代理服务器接收到的header信息. 语法:proxy_set_header field value; field :为要更改的项目,也可以理解为变量的名字, ...

  3. shell 给文件每一行都添加指定字符串

    [admin@localhost file]$ cat file hello hello hello hello hello [admin@localhost file]$ cat test.sh # ...

  4. pathlib.Path 类的使用

    from pathlib import Path 参考 https://www.jb51.net/article/148789.htm

  5. ImportError: DLL load failed while importing win32api: 找不到指定的模块。

    这个是用pip install pywin32安装报的一个错误 据说直接使用pip install pypiwin32安装就不会有报错 但是遇到错误还是要尝试解决一下的 pip install pyw ...

  6. [技术博客] 数据库1+N查询问题

    目录 问题简述 问题解决 group的方法简化查询 改正后的代码 作者:庄廓然 问题简述 本次开发过程中我们用到了rails的orm框架,使用orm框架可以很方便地进行对象的关联和查询,例如查询一个用 ...

  7. Django实现自动发布(2视图-任务接收)

    上一篇服务版本的新增,是通过触发 gitlab 任务来实现的,那么如何得到任务的最终状态呢? 好在 gitlab 为我们提供了webhook,也就是消息钩子,可以发送pipeline消息到我们指定的地 ...

  8. Unity2D音游案例-节奏大师教程+源码+素材

    Unity2D音游案例-节奏大师(Unity2017.2 ,基于Koreographer),本案例涉及到一些音乐音频音波的一点点内容. 首先,我们会把音游的核心功能实现,之后几个重要内容会出给思路并带 ...

  9. Java NIO Buffer详解

    一.ByteBuffer类型化的put与get方法 /** * ByteBuffer类型化的put与get方法 */ public class NioTest5 { public static voi ...

  10. R3300L按reset键无法进入USB Burning模式的问题分析

    最开始并没有注意到这个问题, 因为从设备拿到手, 用USB Burning Tool刷入潜龙版的安卓4.4.2, 再到运行EmuELEC, Armbian, 再到给Kernel 5.3的Armbian ...