左偏树(可并堆)

入门

1. 基本要素

  1. 定义“外结点”表示左儿子右儿子为空的结点

  2. 定义 \(dis_u\) 表示结点 \(u\)最近的一个“外结点”的距离

2. 基本性质及结论

  1. 左偏树本质为小根堆,满足一个结点的权值大于等于其左右儿子的权值

  2. 左偏树满足左偏的性质,即对于任意一个结点 \(u\),满足 \(dis_{l_u}\ge dis_{r_u}\)

  3. 由于有 \(dis_{l_u}\ge dis_{r_u}\),因此一定有 \(dis_u=dis_{r_u} + 1\)

  4. 定义有 \(n\) 个结点的左偏树的根为 \(rt\),满足 \(dis_{rt}\)\(\log n\) 级别的

  5. 一个 \(dis_{rt}=n\) 的左偏树,其结点数至少为 \(2^{n + 1} - 1\)(此时为满二叉树)

3. 基本操作

① 合并 (merge)

左偏树最关键的就在它的合并操作上

定义 \(merge(x,y)\) 为将以 \(x\)\(y\) 为根的两颗左偏树进行合并

我们先来考虑一下普通的小根堆怎么合并

首先,如果有 \(val_x\le val_y\),那么显然合并后的根为 \(x\);为了方便,当有 \(val_x > val_y\) 时,交换 \(x, y\)

接着,我们将 \(y\)\(x\) 的左右儿子中选择一个进行合并,即 \(merge(y, son_x)\);用返回的根节点取代这个 \(x\) 的儿子的位置

重复上述操作,直到当 \(x\)\(y\) 出现一个空节点时,我们返回 \(x+y\)(根节点)

这样合并,最坏情况下是 \(O(n)\) 的,因为每次合并的复杂度是两颗树的深度和

因此,我们需要优化,一种是每次随机选择左右儿子;另一种就是采用左偏树,能够每次都稳定在 \(O(\log n)\)

因为 \(dis_{ls} \ge dis_{rs}\),我们每次合并都选择右儿子;而合并完后,为了保持左偏的性质,如果 \(dis_{ls}<dis_{rs}\),我们就交换左右儿子

② 新增结点

将这个结点当成树,进行合并操作即可

③ 求最小值并删除

堆顶就是最小值;删除即将左右儿子合并即可

记得删除后要将原来的堆顶结点清空!

④ 求结点所在的树的根节点

我们可以记录每个结点的父亲,然后一直向上跳。但显然,这样的复杂度是不对的

我们考虑用路径压缩,直接指向根节点

在合并时,我们将 \(rt_x=rt_y=merge(x,y)\)

再删除时,我们要使 \(rt_{l_u}=rt_{r_u}=rt_{u}=merge(l_u, r_u)\),因为之前有一些结点指向 \(u\),所以我们 \(rt_u\) 也要指向 \(merge(l_u, r_u)\)

代码

posted @ 2022-08-11 09:56  zuytong  阅读(51)  评论(0编辑  收藏  举报