AVL平衡搜索树
基本概念
特征
- 二叉树
- 每个结点只有两个孩子
- 孩子之间有区别 一般为左孩子和右孩子
- 二叉搜索树
- 任取二叉树的结点N,满足 K(N的左子树) < K(N) < K(N的右子树) (也就是左子树的值 < 根节点的值 < 右子树的值)
- 平衡树
- 任取树的结点 N,要求左子树的高度和右子树的高度之间的差的绝对值,不能超过1
H(N.left) - H(N.right) <= 1
- 任取树的结点 N,要求左子树的高度和右子树的高度之间的差的绝对值,不能超过1
- AVL树 (满足上面三种)
- 二叉树
- 搜索树
- 平衡树
AVL树结点中需要携带的信息
需要携带至少5中信息
伪代码表示
AVL 特点
相对于普通搜索树的优势:
由于平衡,所以一颗搜索树不会退化成单支树的情况,所以可以保护保证时间复杂度 ( O log n)是稳定的。
那么它的其他特点就可以参照搜索树来理解
AVL树的操作
AVL 仍然是一个装元素的容器
添加元素
插入前
按照普通搜索树的方式进行插入
- 元素已经在集合中,插入失败
- 插入成功
插入中
插入一个结点( cur,所以 cur.bf = 0),其父节点的平衡因子需要讨论cur.parent.bf = ?
一个AVL树中 ,任意一个结点,它的平衡因子的取值只可能是-1 0 1.
一个结点,如果插入到父节点的左边 父节点的bf +1 插入到父节点的右边 bf-1。
插入结点之后,首先要判断它的perent 的bf
- bf == 0 那么 H(parent) 也就是父节点的高度不变
- bf == 1/ bf == -1 H(parent) 也就是父节点的高度改变 (高度+1 )
为什么要讨论H(parent 是否变化) 重点就是
- 如果 H(parent) 不变,那么BF(parent.parent) 就不需要变化
- 如果 H(parent) 变化了,那么BF(parent.parent) 也需要跟着进行调整
插入后
插入的结点是cur ,插入后的父结点只可能有以下两种情况
-
插入后,满足AVL树的特征且高度(bf = 0)不发生变化,插入结束
插入过程满足搜索树特征,插入后,bf(parent)满足平衡树特征,同时H(parent) 没有发送变化,对树的影响到此为止,整体满足AVL树插入完成
-
插入后,局部满足AVL树,但因为H(parent)高度+1(bf = -1 /1),所以向当前父结点的父结点(根节点蔓延),停下来的条件
√ 蔓延过程中 parent .parent == null 说明parent是树的根,插入就结束了。√ 如果不是继续调整,调整如下:
-
蔓延过程中,遇到 df = 0,结束。
-
蔓延到根处就还是情况2-那就继续向上。再次判断是树的根吗?不是就再次调整
-
蔓延过程中,遇到了 (bf = -2 bf = 2) 树失衡结束。
-
失衡问题
结束判断的时候如果树处于失衡条件那么需要进行调整。即bf(parent) = 2/-2
这种情况可以分为以下的四大类
- 左 左 失衡
表示从失衡结点出发 一直向左走的失衡
直接对左左失衡的结点进行右旋
-
左 右 失衡
-
右 右 失衡
-
右 左 失衡
除了失衡结点之外,其他的部分不会出现失衡的情况。要始终记得一句话,就是失衡是慢慢往上蔓延的。
反推绿色:如果再绿色的部分(也就是过程中出现了失衡,那么是轮不到调整当前的失衡结点的,从下蔓延,再过程中就阻塞了,到不了失衡结点)
解决失衡问题必须满足的条件:
- Tree(失衡结点) 满足AVL树(搜索 + 平衡)
- 修复完之后的Tree (失衡的树) 高度和插入前保持不变
旋转操作
要想解决这个问题,必须先对树的旋转有了解。
搜索树的旋转
右旋
左旋
搜索树的左右旋转示意
AVL树的旋转
左左失衡
- 左左失衡解决办法:对失衡的结点进行右旋操作
那么是否在通用的场景下,这个办法仍然正确
证明左左失衡情况下,对失衡结点右旋后能够做到验证以下两点成立即可。
- 局部满足AVL树(搜索树 + 平衡)
- 失衡树的高度不变
讨论插入橙色的结点之后,H(甲)和H(乙) 的关系 (没有插入橙色的结点之前 这颗树一定是一个AVL树)
1.假设一:插入结点之后 H(甲) = H(乙)
反证法发现 没有插入结点之前不是AVL树,所以假设一错误
-
假设二:插入结点之后 H(甲)< H(乙)
反证法发现 没有插入结点之前不是AVL树,所以假设二错误
-
假设三:插入结点之后 H(甲) > H(乙)
反证法发现 没有插入结点之前是AVL树,所以假设三成立!
上述验证之后,各个的高度都知道了,经过右旋以后
左右失衡
首先对失衡的结点的左孩子进行左旋,再对失衡结点进行右旋。
注意是对左孩子进行变动失衡节点是不动的!
右右失衡
右左失衡
总结
失衡状态 | 调整策略 |
---|---|
左左失衡 | 对失衡结点右旋 |
左右失衡 | 对失衡结点的左孩子左旋 然后失衡结点右旋 |
右右失衡 | 对失衡结点左旋 |
右左失衡 | 对失衡结点的右孩子右旋 失衡结点进行左旋 |
查找元素
和普通搜索树的查找完全一致
- 判断是否存在 key == node.key 找到
- 获取管理数据 Value
key < node.key 往左找
key > node.key 往右找
如果最后走到null 就没有找到
删除元素
由于比较复杂,暂时不研究。
时间复杂度评估
查找 最坏是树的高度 (log n)
插入 (查找位置 + 插入+调整) ( log n)
删除(log n)