所谓的左偏树,是一种可并堆的实现。
这种数据结构能够支持高效的堆合并,但是不支持查询节点等操作,因此不同于平衡树,它的结构是不平衡的。
左偏树满足如下两条基本性质:
1. 堆的性质
这也就是说左偏树每个节点的值都大于/小于它父节点的值。
2. 对于任意节点,其左儿子距离不小于右儿子距离(左偏性质)
这里需要先引入距离的概念。
一个节点的距离,指它到后代中最近的外节点(儿子数量少于2)所经过的边数。
有了上面两条性质,我们不难证明下面这条性质:
3. 对于任意节点,其距离等于其右儿子距离+1
其正确性是显然的,因为左儿子距离大于等于右儿子距离,所以最近的外节点必然在右儿子里。
有了这些性质,我们就可以着手与其操作了。
合并操作
合并操作将两个左偏树合并在一起。
假如两棵树中有空树,那么返回另一颗即可。
否则取根节点更大/小的那一颗,然后将另一颗并到他的右儿子上去。
由于并完之后右儿子距离可能比左儿子大,所以我们需要特判是否交换儿子。
最后还要更新一下根节点的距离。
int merge (int x,int y) {
if (!x||!y) return x+y;
if (val[x]>val[y]) swap(x,y);
heap[x].rs=merge(heap[x].rs,y);
if (heap[heap[x].rs].size>heap[heap[x].ls].size) swap(heap[x].ls,heap[x].rs);
heap[x].dis=heap[heap[x].rs].dis+1;
return x;
}
删除操作
没删么好说的,直接将左右儿子并起来就可以了。
int erase (int x) {
x=merge(heap[x].ls,heap[x].rs);
return x;
}
复杂度证明
最后是左偏树合并操作的复杂度证明。
由于我们每一次递归都合并右子树,而一颗树的距离取决于其右子树。
所以最后一颗树被分解的次数不会超过其距离。
也就是说,不会超过\(log(n+1)-1\)次。
那么在最坏情况下,合并复杂度为\(O(log(n_a+1)+log(n_b+1)-2)\)即\(O(log(a)+log(b))\)也就是\(O(log(ab))\)