常用二叉可并堆——左偏树

SuperJvRuo

2018-07-24 06:38:23

Personal

要求:了解什么是左偏树。不必熟练掌握左偏树的写法。 ## 一、左偏树的结构与性质 左偏树也是一棵二叉树,也满足堆的性质。以小根堆为例,每个非根结点的值都比它父亲大。因此在整棵左偏树中,根的值最小。 即左偏树的性质一(以小根堆为例): $$val_p\leq val_{lson}$$ $$val_p\leq val_{rson}$$ 左偏树的每个节点记录四个值:左儿子,右儿子,权值和距离。 ``` struct Node { int ch[2],val,height; } ``` 所谓距离,是指一个节点到其子树内最近叶节点的距离。叶节点的距离是$0$。 为了体现名字中的“左偏”,我们钦定每个节点左儿子的距离大于等于右儿子的距离。整个树就是一种左边重右边轻的状态。 即左偏树性质二: $$height_{lson}\leq height_{rson}$$ 所以,距离父节点最近的叶节点一定在右子树中,可得左偏树性质三: $$height_p=height_{rson}+1$$ 节点数一定的情况下,要使根节点的距离最大,就要让这棵树形成完全二叉树。这样可以使得离根节点最近的叶节点尽可能远。 设此时$height_{root}=k$,则有节点数$n=2^{k+1}-1$。即得所有情况下$n\geq 2^{k+1}-1$。易见左偏树性质四: $$height_{root}\leq \log^{n+1}_2-1$$ ## 二、push和merge 同样地,把push视为merge来处理。 当我们要合并两棵左偏树a和b时,不妨设$val_{roota}<val_{rootb}$。大部分操作和斜堆相同,不同的是要保证左偏性质,还要维护节点的距离。那么只需在合并后检查两个儿子的距离大小关系,当左儿子距离小于右儿子时交换即可。在返回新得到的树根前,修改根节点的距离,使其满足性质三。 合并前 ![](https://cdn.luogu.com.cn/upload/pic/24931.png) 合并后 ![](https://cdn.luogu.com.cn/upload/pic/24936.png) ``` int merge(int root1,int root2) { if(!root1||!root2) return root1+root2; if(node[root1].val>node[root2].val) std::swap(root1,root2); node[root1].ch[1]=merge(node[root1].ch[1],root2); if(node[node[root1].ch[0]].height>node[node[root1].ch[1]].height) std::swap(node[root1].ch[0],node[root1].ch[1]); node[root1].height=node[node[root1].ch[1]].height+1; return root1; } ``` ## 三、pop 参考斜堆。 ## 四、时间复杂度分析 左偏树的左偏性保证了它不会退化成链,保证了它最坏情况下的效率。由于两棵被合并的左偏树a和b的根节点距离分别不大于$\log^{size_a+1}_2-1$和$\log^{size_b+1}_2-1$,合并的复杂度为$\log^{n^2}_2$。 然而可以从~~我写的很丑的~~代码看出,左偏树的常数并不小,在实际应用中,面对随机数据,反而比斜堆要慢。 ## 五、例题 [P3377 【模板】左偏树(可并堆)](https://www.luogu.org/problemnew/show/P3377) [P3261 JLOI2015 城池攻占](https://www.luogu.org/problemnew/show/P3261)