二叉搜索树
以下笔记为jjyaoao本人制作,欢迎借鉴学习和提出相关建议,转载需要标明出处www.jjyaoao.space
二叉搜索树
寻关键码访问
有序性
为了简单而言 节点 = 词条 = 关键码
单调性
在微观上处处满足顺序性,宏观上每一个的垂直投影,满足单调性
ADT
查找
可以看到,和二分查找过程相仿
1 | template <typename T> BinNodePosi<T> & BST<T>::search ( const T & e ) { //在BST中查找关键码e |
*两种表现形式*,下面一种更易理解
每运行一次,下降一层
hot会记下此前访问过的非空节点,总体而言,hot最终将会指向谁呢?
我们引入一个假想的哨兵(上图右边的e),它的位置在我们树的叶节点的下一位,我们可以看到,如果我们引入之后,这个BST仍然是合法的,更重要的是,它让我们的hot语义变得完整起来,也就是,无论如何,hot总是能指向命中节点的父亲。
插入
删除
if x 不存在 那么 退出删除 和插入正好对称,接下来的问题是removeAt可能需要处理哪几种情况,各种情况又当如何应对呢
单分支删除
实现
双分支删除
化繁为简!
必无左孩子(因为直接后继一定是右孩子的最左边的左子代
需要说明的是,hot需要在删除完节点后更新为刚刚被实际删除节点的父亲节点,并且向上遍历,因为有可能祖先的高度会发生变化(因为后代的删除)
实现
首先找到待删除节点的直接后继,令他和待删除节点交换,等效的将待删除的节点转移到新的位置,并且至多只有一个分支,当然,这个节点一定只有右孩子(或者没有孩子
我们现在只需要在他和祖父之间正确的完成一次双向连接
w为待删除数据,x为它的直接后继
后面那一句三元即为桥梁,将w在这个BST树里剔除掉
SUCC()
1 | template <typename T> BinNodePosi<T> BinNode<T>::succ() { //定位节点v的直接后继 |
removeAt ()
1 | template <typename T> |
复杂度
我们观察,BST的删除操作,所需要的时间主要来源于更新祖先高度,和找到直接后继,经过推理验证,不难得出,两者都不会超过O(h)的复杂度,因此,删除算法和插入一样,都是O(h)的复杂度,
那么,这个结论,是好还是不够好呢?
我们将进行进一步探讨(后续
平衡与等价
极端退化
BST = List + Vector
平均高度
n!和卡特兰数,谁的统计更为准确呢?我们认为,是Catalan数,因为n!统计会有重复,因为在前一统计口径所得到的估算值,在高度更小的时候,会出现更多的重复(比如2 1 3 ,2 3 1)这也使得,重复在高度低的位置充作统计对象,这也是它为什么会统计的平均高度更小的原因
从这一观点来看,logn过于乐观了,根号n也许更加可信
为了控制树高,我们也许应该做点什么,同时,我们也可以发现,我们需要采取一系列的技巧
理想+适度
而这种适度平衡的树,我们就把他叫做 BBST
歧义=等价
全局上任然是BST
在微观上可以改变
AVL
了解就好,已经过时
平衡因子
balfac()
一种适度的平衡标准
适度平衡
ADT
失衡与重平衡
插入
插入:单旋转
插入:双旋
插入:实现
删除
单旋转
双旋转
实现
3+4重构
以上删除插入,使用旋转,无非是帮助理解算法如何实现
而真正实施,大可不必这样,因为我们是追求的效率,而不是所谓的技巧
对于魔方工人而言,可以选择随便组装,然后再转到标准的魔方样子,更可以直接拆散,然后进行组装
算法
注意重新命名
实现
connect34()
1 | template <typename T> BinNodePosi<T> BST<T>::connect34 ( |
统一调整
rotateAt()
我们通过判断p和v到底是左孩子还是右孩子,就可以准确的区分选用zig-zig,zig-zag……..
而在每一种情况下,v,p,g究竟该如何命名为a,b,c,以及属下的四棵子树该如何命名,都是固定的,甚至可以绘制成一张表,这张表也就是rotateAt所作的事情
1 | /****************************************************************************************** |
评价AVL
如果说,前两个缺点是鸡蛋里挑骨头,那最后一个则是致命的(拓扑排序的变动)
insert和delete的操作是非常不对等的,就删除而言,拓扑排序变化量能保持在常数的范围,而删除则只能logn,对于更高级的数据结构而言,对变化量是有严格的要求的,而我们这里的logn是绝对不能满足要求的,我们希望将他们控制到更低