数据结构——启发式合并
一般情况下,合并两个数据结构的时间复杂度大于等于
并查集的启发式合并
通常情况下,我们会这样写并查集的合并
void merge(int x, int y) {
x = find(x), y = find(y);
if(x == y) return;
f[x] = y;
}
在极端情况下,用这种写法实现的并查集会呈链状,时间复杂度将退化。这时我们有两种解决办法。
1、路径压缩
2、启发式合并
大多数情况下,路径压缩已经足够。
但是!
可持久化并查集。。。
无法使用路径压缩。。。
这时启发式合并便排上用场了
我们维护一个
void merge(int x, int y) {
x = find(x), y = find(y);
if(x == y) return;
if(dep[x] < dep[y]) f[x] = y;
else if(dep[x] > dep[y]) f[y] = x;
else f[x] = y, ++dep[y];
}
这样一来,
在合并其他数据结构时,常常会使用并查集来维护每个数据结构是否已经被合并,从而保证效率。
线性数据结构的启发式合并
如果要合并的不是两个集合,而是两个栈、队列或是链表呢?
同样可以使用启发式合并。
设
于是我们每次都贪心的选择更优的操作,保证
伪代码如下
void merge(int x, int y) {
if(已经合并) return;
if(size[x] > size[y]) 将y合并到x;
else 将x合并到y;
}
二叉堆的启发式合并
与上述线性数据结构的启发式合并几乎没有差异,而且Pairing Heap、Leftist Tree等可并堆编程复杂度并不比启发式合并高,效率却胜于启发式合并,所以二叉堆的启发式合并并不常见。
线段树的合并
线段树大体可分为两种,区间树和权值树。其中权值树可以用于维护一个有序可重集,因此也支持合并操作。
动态开点
普通线段树的空间复杂度为
但是在大多数问题中,每个集合最初只有一个元素,在线段树内占用的空间为
对于在普通的权值线段树插入一个元素,通常这样实现
void insert(int o, int l, int r, int x) { // 当前结点为o,当前区间为[l, r],插入的数为x
++s[o].size;
if(l == r) return;
int mid = (l + r) >> 1, lc = o << 1, rc = lc | 1;
if(x <= mid) insert(lc, l, mid, x);
else insert(rc, mid + 1, r, x);
}
因为我们默认
void insert(int &o, int l, int r, int x) { // 注意这里的o是引用
if(!o) o = new_node();
++s[o].size;
if(l == r) return;
int mid = (l + r) >> 1;
if(x <= mid) insert(s[o].lc, l, mid, x);
else insert(s[o].rc, mid + 1, r, x);
合并操作
设当前要把结点
若
若
代码
void merge(int &x, int y) { // 将y合并到x上
if (!x) { x = y; return; }
if (!y) return;
s[x].size += s[y].size;
merge(s[x].lc, s[y].lc);
merge(s[x].rc, s[y].rc);
}
时间复杂度
我们可以这样分析:
调用一次
注意:
合并两棵线段树的时间复杂度与其
平衡树的启发式合并
与线性数据结构的启发式合并类似,单次合并复杂度为
但是!
有一种平衡树叫做Splay。。。
还有一种平衡树叫做Treap。。。
于是便有了。。。
Splay的启发式合并
注:以下部分内容摘自国家侯选队论文
首先,要引入一个概念。
Finger Search
一般情况下,在数据结构中查找某个元素的时间复杂度会表示成关于
Finger Search指的就是在数据结构中从某些特定的节点(也就是“finger”)开始查找其他元素。假如被查找的元素与“finger”的排名距离很近,那么查询的效率就会有很大的提升。
例如在一个有序数组中,我们可以通过倍增来实现finger search,时间复杂度为
而在平衡树中,我们可以将根结点视为一个Finger。于是便有了一个神奇的定理
Dynamic Finger Theorem
在一棵
别问我怎么证明。。。
如何实现
众所周知,Splay通过splay操作来保证均摊
那么这样做的时间复杂度是多少呢?
设当前要合并的两棵Splay为
为了方便,我们可以设
{
即将序列
不难发现,这两序列中的个元素对合并的时间复杂度至多会产生
综上所述,Splay启发式合并的均摊复杂度为
Treap的启发式合并
还是有一个神奇的定理
两个元素 x, y 在 Treap 上的路径长度是期望
仍然不要问我怎么证明。。。
如何实现
在Treap上找到结点
参考资料
黄学长的博客
《浅谈Splay与Treap的性质及其应用》董炜隽