好久不见!
就差这个坑就补齐了!
估计高三也不会太更博客了,不知道是不是NOIP前的最后一篇?
在博客园的新博客欢迎关注!
发现自己平衡树学了跟没学一样,退役了简单学了一发……
平衡树有很多,比如AVL树,红黑树(RBTree)什么的,AVL被魔改成SBT?RBTree是系统中的平衡树。但是不常用不介绍了。
平衡树的本质是一棵二叉搜索树(Binary Search Tree,即BST),规定一个二叉树,其左儿子的值都小于它,其右儿子的值都大于它,也就是说把二叉搜索树按中序遍历即可对这些数进行排序。但是这个二叉搜索树不是唯一的。
我们可以用BST解决一些维护集合问题,比如插入一个数,查询某数排名(第几小)和查询第
k
k
k大数。直接按着定义模拟,把BST搜一遍即可完成操作。
可是这种操作很爆炸,当按照从小到大的顺序插入数据的时候复杂度会很大(此时BST为一条链),而且删除操作很困难。
那么就需要一些操作维护这棵BST了。
我们发现,有两个操作:左旋和右旋可以在不破坏BST性质的情况下维护这棵BST不会退化成链的形式。
下面给出一棵BST的一部分
右旋的意思即为:对于
k
k
k节点,把父亲节点
x
x
x作为自己的右儿子,自己的右儿子作为旋转后
x
x
x的左儿子。
分步的操作如下:
->
因为
l
c
<
k
<
r
c
<
x
<
R
C
lc<k<rc<x<RC
lc<k<rc<x<RC,旋转后仍符合。
那么左旋就很好理解了:对于
k
k
k节点,把父亲节点
x
x
x作为自己的左儿子,自己的左儿子作为旋转后
x
x
x的右儿子。
->
这样旋转有什么用呢?
于是就有这几种维护方式了!
1、Splay
Splay可以说是一种经典的维护方式了,通过维护
x
x
x-父亲-祖父的关系实现平衡,也就是判断这三个点是否在一条直线上实现平衡。
记录操作
Splay
(
x
)
\text{Splay}(x)
Splay(x)为把
x
x
x节点转到根上。这样很容易就可以快速查找了。
那么如何旋转呢?
于是我们开始了大段的讨论:(其实就三种……)
1、
x
x
x的父亲就是根……
转一次就行了……
2、
x
x
x-父亲-祖父在一条直线上:
这种情况如图:
现在
f
f
f是
x
x
x的左儿子,
g
g
g是
f
f
f的左儿子(右儿子同理,只不过操作相反),那么我们将
g
g
g右旋,再将
f
f
f右旋即可(如果右儿子即为左旋),这个是Zig-Zig(Zag-Zag)操作。
如图所示……
->
这样
x
x
x就是根了!
3、
x
x
x-父亲-祖父不在一条直线上:
就是拧着的呗……
现在
f
f
f是
g
g
g的右儿子,
x
x
x是
f
f
f的左儿子(反着的同理),我们反向操作,对
f
f
f右旋,再对
g
g
g左旋即可把
x
x
x转到根上。这个是Zig-Zag(Zag-Zig)操作。
如图所示……
->
于是我们可以把任意一个节点都转到根上了!
那么复杂度呢……
根据势能分析,可知
n
n
n个点,
m
m
m次操作的Splay树的时间复杂度为
O
(
(
n
+
m
)
log
2
n
)
O((n+m)\log_2 n)
O((n+m)log2n)。
这里的势能可以理解为物理学势能,网上证明应该很多吧……这里不贴了……
Splay应用广泛,可以支持动态序列操作(如普通的平衡树),还可以支持区间变化的操作,比如区间反转,区间移动等。
如何在Splay上提取区间呢?
记需要提取
[
l
,
r
]
[l,r]
[l,r],那么把
l
−
1
l-1
l−1提到根,将
r
+
1
r+1
r+1提到根的右子树,那么根的右子树的左子树(即
r
+
1
r+1
r+1的左子树)即为
[
l
,
r
]
[l,r]
[l,r]区间的所有元素,就可以根节点打标记来维护了。
但是当
l
=
1
l=1
l=1时没有
0
0
0元素,当
r
=
n
r=n
r=n时没有
n
+
1
n+1
n+1元素,这样就需要手动添加
0
0
0和
n
+
1
n+1
n+1元素了。
Splay还可以用在Link-Cut Tree上(即动态树),就可以维护森林中每棵树的形态了。这玩意很玄学就不补板子了……
Splay在遇到有序数据的时候还会被卡……
2、Treap
Treap是个很玄学的数据结构,它是BST和Heap的合体版本。
堆是个二叉树,其每个节点的优先级比儿子们的优先级都要小,这就是priority_queue的实现方式。
因为BST是因为一些有序数据使得其退化,然而随机数据可以认为是无序的。那么把随机数当做Heap的优先级维护Tree就可以达到平衡BST的作用。所以,我们利用左旋和右旋操作保持Heap的性质,即可达到BST的平衡。
Treap的树高期望是
O
(
log
2
n
)
O(\log_2 n)
O(log2n)的,所以它的复杂度是期望
O
(
log
2
n
)
O(\log_2 n)
O(log2n)的。
Treap还有无旋维护的,是利用把树分成两块再合并的操作维护。
Treap还可以演变为替罪羊树,主要思想是暴力重构……是一种重量平衡树。
可以用Windows卡Treap(Windows下的rand范围比较小,再乘一个rand就可以防卡了……)
3、SBT
SBT是2006年在冬令营上首次出现的,可是被诸神犇喷不知道为什么……据说是换了一种方式维护AVL。
主要是用Maintain操作维护平衡,而且这个Maintain操作是均摊
O
(
1
)
O(1)
O(1)的非常快(据说比AVL,Treap,Splay快很多)。
在知乎上陈启峰本人已经给出了资料包,可以参考。
貌似SBT只能刷裸题+刷时间榜……
下面是喜闻乐见的板子时间……
Splay
Treap
SBT