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

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

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

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

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

    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. 第03组 Alpha冲刺

    队名:不等式方程组 组长博客 作业博客 团队项目进度 组员一:张逸杰(组长) 过去两天完成的任务: 文字/口头描述: 制定了初步的项目计划,并开始学习一些推荐.搜索类算法 GitHub签入纪录: 暂无 ...

  2. ansible-playbook--jia使用

    #cat jia.yml - hosts: test-gfs user: dev gather_facts: true vars: PORT: "3306" MESAGE: &qu ...

  3. FZU Monthly-201905 获奖名单

    FZU Monthly-201905 获奖名单 冠军: 郑学贵 S031702338 一等奖: 林闽沪 S131700309 罗继鸿 S031702524 二等奖: 苏锦程 S031802325 林鑫 ...

  4. Armbian编译以及定制

    Armbian项目地址 Github: https://github.com/armbian/build Armbian for TV Box 项目地址 Github: https://github. ...

  5. JAXB xml序列化应注意

    使用JAXB将对象序列化为XML时,发现有一个地方性能非常低,即 JAXBContext.newInstance(XXX.class); 建议将其提前创建好并作为变量保存,到时可直接使用其引用,而非重 ...

  6. BIO,NIO,AIO到NETTY

    NIO 近期接触了几个产品都触及NIO,要么应用,要么改造项目,听多了也有些了解,但仍然不能真正理解,工期比较赶,还是要潜心下来看看. NIO是什么呢,应该是NOT-BLOCKING IO的意思,不阻 ...

  7. Ubuntu 16.04 catkin_make 常见操作

    参考博客:https://answers.ros.org/question/54178/how-to-build-just-one-package-using-catkin_make/ 1. catk ...

  8. visual studio制作代码片段

    使用 Visual Studio 的代码片段功能,我们可以快速根据已有模板创建出大量常用的代码出来.ReSharper 已经自带了一份非常好用的代码片段工具,不过使用 ReSharper 创建出来的代 ...

  9. Java中遍历ConcurrentHashMap的四种方式

    //方式一:在for-each循环中使用entries来遍历 System.out.println("方式一:在for-each循环中使用entries来遍历"); for(Map ...

  10. 创建Observer

    观察者 观察者作用就是监听事件, 然后对这个事件做出响应, 或者说任何响应时间的行为都是观察者 1. 在subscribe()方法中创建监听者 创建观察者最直接的方法就是在Observable的sub ...