替罪羊树详解
刚开始学习平衡树。可是我太弱了弄不懂有旋转操作的treap和splay,这时候学习可以不用旋转操作——替罪羊树的平衡树就很适合。这个名字取得比较玄乎,一眼看上去并不知道有什么卵用,但是, 如果你是刚学平衡树的新手,那么从替罪羊树开始学一定是个绝佳的选择,因为它是个很优雅的平衡树,什么叫优雅?暴力即是优雅!
如果在一棵平衡的二叉搜索树内进行查询等操作,时间就可以稳定在log(n),但是每一次的插入节点和删除节点,都可能会使得这棵树不平衡,最坏情况就是退化成一条链,显然我们不想要这种树,于是各种维护的方法出现了,大部分的平衡树都是通过旋转来维护平衡的,但替罪羊树就很厉害了,一旦发现不平衡的子树,立马拍扁重建,这就是替罪羊树的核心:暴力重建
先来说说我的替罪羊树上的每个节点包含些什么:
(1) zuo,you:记录该节点的左右儿子
(2)x:该节点的值
(3)tot:有多少个值为x的数
(4) size,trsize,whsize:size表示以该节点为根的子树内有多少个节点,trsize表示有多少个有效节点(这个后面再讲啦),whsize表示有多少个数(也就是子树内所有节点的tot的和)
(5)fa:该点的父亲
(6)tf:该点是否有删除标记(这个也会在后面讲啦)
用处
替罪羊树是一种平衡树,支持插入,删除,查找第k小元素,查找元素的排名等操作
1、关于α
在替罪羊树中,定义了一个平衡因子α,α的范围因题而异,一般取0.5~1.0之间,若题目没有特殊说明,一般就取个中0.75就好了。那这个α有啥用呢?替罪羊树判断一棵子树是否平衡的方法是:如果 x的左(右)子树的节点数量 > 以x为根的子树的节点数量α ,那么,以x为根的这棵子树就是不平衡的。显然的,如果有一棵子树的大小超过了 以x为根的子树的节点数量α,那么这种节点一边倒的情况对于查询来说肯定就很慢,所以,这个时候我们就将它重建。
不知各位有木有想过,α的值究竟与效率有什么关系呢?仔细想想,当α的值越小,那么替罪羊树就越容易重构,那么树也就越平衡,查询的效率也就越高,自然修改(加点和删点)的效率也就低了,所以,如果查询操作比较多的话,就可以将α的值设小一点,反之,假如修改操作多,自然α的值就要大一点了。
还有,α不能等于1 or 0.5,假如它等于0.5,那么当一棵树被重构之后如果因为节点数问题,不能完全重构成一个完全二叉树,那么显然,对于这棵树的根,他的 | 左子树节点数量 - 右子树节点数量 | 很可能会等于1,那么如果往多的那棵子树上加一个节点,那么这棵树又得重构一次,最坏情况时间会变成n^2,显然死定了。。那么等于1会怎么样?还能怎么样!你觉得会有一棵子树的大小大于整棵树的大小吗?!
2、关于时间复杂度
除了重构操作,其他操作的时间复杂度显然都是log(n)的,那么下面看一下重构的时间复杂度。
虽然重构一次的时间复杂度是O(n)的,但是,均摊下来其实只是O(logn)。
考虑极端情况,每次都把整棵树重构。
那么我们就需要每次都往根的一棵子树内加点,假设一开始是平衡的,那么左右子树各有50%的节点,那么要使一棵子树内含有超过75%的节点,那么这棵子树就需要在原来的基础上增加2倍的节点数。也就是说,当最差情况时,整棵替罪羊树的节点数要翻个倍,才会重构。那么最差情况时也就是在4,8,16,32……个节点时才会重构,于是重构的总的时间复杂度也就是O(nlogn)了,加上一些杂七杂八的重构,也不过就是加上一个很小的常数,可以省略不计。所以,替罪羊树的时间复杂度依然是O(nlogn)的。
引用博客:一、结构体指针
二、树状数组
三、带图更清晰