Bootstrap

树(二叉平衡树、红黑树、B树)

二叉平衡树(AVL)

  • 各个左右子树长度之差不超过1
  • 插入节点之后,如果出现不平衡,那么首先要找到最小不平衡子树
  • RR,插入E后,最小不平衡子树是CDE,不平衡出现在C的右节点、D的右节点,所以称为RR,左转即可

  • RR,插入E后,最小不平衡子树是BDE,不平衡出现在B的左节点、D的左节点,所以称为LL,右转即可

  • LR,插入F后,最小不平衡子树是ABCDEF,不平衡节点出现在A的左节点、B的右节点,所以称为LR
  • 根据小到大原则,从子树B开始处理,不平衡节点出现在B的右节点,那么左转
  • 然后处理A节点,不平衡节点出现A的左节点,那么右转
  • 所以处理上,是先左后右,也是LR

  • RL,插入F后,最小不平衡子树是ABCDEF,不平衡点出现在A的右节点、C的左节点,所以称为RL
  • 根据由小到大原则,处理C节点,不平衡点出现在C的左节点,那么右转
  • 然后处理A节点,不平衡点出现在A的右节点,那么左转
  • 处理上是先右后左,也是RL

  • 还有两种简单的LR和RL,如下
  • LR,最小不平衡树出现在ABC,在A的左、B的右,先左转处理BC(小子树),再右转处理ABC(大子树)

  • RL,最小不平衡树出现在ABC,在A的右、B的左,先右转处理BC(小子树),再左转处理ABC(大子树)

红黑树

参考:https://www.jianshu.com/p/e136ec79235c

  • 自平衡二叉查找树,不像二叉平衡树那样要求严格,所以插入的时候调整的频率没有二叉平衡树高,所以插入效率较快;但是由于红黑树平衡度要求不高,所以查询速度稍逊于平衡二叉树
  • 红黑树适合 插入频繁,但是查找不那么频繁的
  • 平衡二叉树适合 查找频繁,但是插入不那么频繁的
  • 红黑树定义:
  1. 每个节点要么是黑色,要么是红色
  2. 根节点要求是黑色(这里的叶子节点和普通的二叉树中的叶子节点不一样,这里的叶子节点指的是原二叉树叶子节点的左右空指针NIL
  3. 叶子节点要求是黑色
  4. 红色节点的孩子节点是黑色的,也就是上下不能同时出现两个红色节点
  5. 任何一个节点,从其开始到任何一个叶子结点的路径,各个路径包含的黑色节点的数量是一致的,也就是黑色高度(brack height,bh)一样
  • 红黑树的高度,不超过2\cdot log(n+1),所以查询效率虽然没有平衡二叉好,但还是在一个数量级
  • 定义4确保了,最短的路径上只有黑色节点,最长的路径上黑色和红色节点交替出现
  • 定义5确保了,每条路径黑色节点数量一致,那么最长的长度,就是红黑交替,那么最多也就两倍黑色长度那么长,所以是2\cdot log(n+1)
  • 红黑树自平衡三种操作:左旋、右旋、变色
  • 刚插入的时候,都是先红色,如果不满足定义的要求(平衡要求),就需要旋转、变色调整了

  • 插入1:红黑树为空,直接插入点作为根节点,然后调整颜色为黑色
  • 插入2:插入节点已存在(value一样),那么更新值
  • 插入3:插入节点的父节点是黑色,那么直接插入即可,不需要自平衡
  • 插入4:插入节点的父节点为红色,那么该插入节点一定存在祖父节点,因为红色节点不能作为根节点
  • 插入4.1:插入节点的叔叔节点存在且为红色,那么需要如下操作:
  1. 将祖父节点g设置为红色
  2. 父亲p和叔叔u节点设置为黑色
  3. 如果祖父节点g的父节点为黑色,那么搞定,不用处理了;如果祖父节点g的父节点是红色,那么将其当做新插入点,重新执行4.1知道满足条件(肯定会满足,因为根节点是黑色)

  • 插入4.2:插入节点不存在叔叔节点或者叔叔节点时黑色,并且插入结点的父亲结点是祖父结点的左子结点
  • 插入4.2.1:插入节点是其父节点的左子节点,那么需要如下操作:
  1. 祖父节点g变红
  2. 父节点p变黑
  3. 右旋转

  • 4.2.2:插入节点是其父节点的右子节点,那么需要如下操作:
  1. 父节点p和插入节点new左旋
  2. 将父节点p和插入节点new互换身份,这样子就变成4.2.1的情况啦
  3. 按照4.2.1处理就好啦(4.2中已经默认了要么叔叔不存在,要目叔叔是黑色节点,当叔叔不存在的时候,就是下面这样情况;当叔叔存在,叔叔是黑色的,还是当祖父g的子节点也满足呀)

  • 4.3:插入节点不存在叔叔节点或者叔叔节点时黑色(该条件和4.2一致),并且插入节点的父节点是祖父节点的右节点(和4.2不同的就是父节点是祖父节点的左节点,这个和4.2对称)
  • 4.3.1:插入节点是其父节点的右子节点,那么需要如下操作:
  1. 将父节点p变为黑色
  2. 将祖父节点g变为红色
  3. 对父节点p和插入节点new进行左旋操作

  • 4.3.2:插入节点时其父节点的左节点,那么需要如下操作:
  1. 将p和new进行右旋
  2. 将new和p对换角色,这样子就变成4.3.1的情况了
  3. 按照4.3.1的情况来处理就好了

  • 删除后需要自平衡,分为以下情况:

2-3树

  • 每个节点都有2个或者3个孩子
  • 一个2节点包含1个元素和2个孩子(或者没有孩子)
  • 一个3节点包含2个元素和3个孩子(或者没有孩子)
  • 2-3树所有的叶子节点都在同一层
  • 插入分为三种情况:
  • 插入情况1:对于空树,直接插入即可,变为2节点
  • 插入情况2:插入到2节点中,直接变成3节点
  • 插入情况3:插入到3节点中,需要将3节点拆除,然后将3节点中的两个元素或者新插入的元素向上挪一层
  • 插入情况3-1:插入的3节点的父节点是2节点:插入的6、7节点是3节点,其父节点4是2节点
  1. 插入5,将6、7节点拆分,然后将6放到4中,5和7个作为一个2节点

  • 插入情况3-2:插入的3节点的父节点是3节点:插入11,插入的9、10是3节点,其父节点12、14也是3节点,其曾祖节点8是2节点,所以想到其曾祖节点可以操作
  1. 将9、10节点拆分,将9、10、11中的10往上放;
  2. 将12、14节点拆分,将10、12、14中的12往上放

  • 其实插入,总结来看就是,不断的将不满足的节点拆分,然后将多出的元素往上挪,那这样会有一个情况,就是挪到最后根节点也是3节点,那么这个时候只能拆掉根节点,整个树增加一层,高度+1了,比如下面的例子:
  1. 先拆1、3节点,将1、2、3中的2往上挪
  2. 然后拆4、6节点,将2、4、6中的4往上挪
  3. 最后到根节点了,拥有4、8、12元素,那么这个时候不能往上挪了,只能拆分根节点然后往下增加高度了

2-3-4树

  • 4节点,要么有4个孩子,要么没有
  • 3节点,要么有3个孩子,要么没有
  • 2节点,要么有2个孩子,要么没有

B树(多路查找树)

  • 2-3树和2-3-4树是B树的特例
  • B树中最大的孩子数目称为B树的阶,m阶B树具有如下属性:
  1. 如果根节点不是叶节点,那么至少有两颗子树(至少是2节点)
  2. 每个非根的分支节点都有k-1个元素 和k个孩子,其中\left \lceil m/2 \right \rceil \leqslant k \leq m,每个叶子节点都有k-1个元素,其中\left \lceil m/2 \right \rceil \leqslant k \leq m
  3. 叶子节点都在同一层
  4. 需要满足大小大小关系(类似二叉排序树)

B+树

  • B树有一个缺点,就是当需要遍历整颗B树,那么就要在硬盘之间多次访问,比如下图要访问 页面1 -> 页面2 -> 页面1 -> 页面3 -> 页面1 -> 页面4 -> 页面1 -> 页面5
  • 这样的话,就需要多次访问父页面,这样需要多次在磁盘间访问

  • 为了解决这个问题,在B+树中,出现在分支节点中的元素会在其中序遍历的前序叶子节点中出现。除此之外,每个叶子节点都会保存一个指向后一个叶子节点的指针。如下图:

  • B+树和B树的区别:
  1. 叶子节点保存所有关键字信息,叶子节点按照关键字大小顺序链接
  2. 所有分支节点可以看成是索引,节点中仅含有其子树的最大、最小值
  • 遍历B+树,从首个叶子节点开始,遍历链表即可
;