487 words
2 minutes
左偏树
简介
左偏树是一种可并堆,即可以快速合并的堆。
定义
对于一棵二叉树,定义 外节点 为子结点小于两个的节点。
定义一个节点的 为其到子树中最近的外节点所经过的边的数量,外节点的 为 。
左偏树是一棵二叉树,它不仅具有对的性质,并且每个节点左儿子的 都大于等于右儿子的 。
左偏树每个节点的 都等于其右儿子的 加一。
操作
为了方便,本文操作均讨论大根堆。
合并 merge
合并是左偏树的的核心操作。
合并两个堆时,由于要满足堆性质,先取值较大的那个根作为合并后堆的根节点。
然后将根的左儿子作为合并后堆的左儿子,递归合并其右儿子与另一个堆,作为合并后的堆的右儿子。
为了满足左偏树的性质,若合并完后左儿子的 小于右儿子的 ,就交换左右儿子。
void merge(int x,int y) {
if(!x||!y) return x+y;//若一个堆为空,返回另一个堆
if(w[x]<w[y]) swap(x,y);
rson[x]=merge(rson[x],y);
if(dist[lson[x]]<dist[rson[x]])
swap(lson[x],rson[x]);
dist[x]=dist[rson[x]]+1;//更新 dist
return x;
}
由于左偏性质,每递归一层,其中一个堆根节点的 就会减小 ,而一棵有 个节点的二叉树,根的 不超过 ,所以合并两个大小分别为 和 的堆复杂度是 。
插入节点 insert
单个节点也可以视为一个堆,合并即可。
删除根 pop
合并根的左右儿子即可。
删除节点 erase
先将左右儿子合并,然后自底向上更新 ,不满足左偏性质时交换左右儿子,当 无需更新时结束递归。
void pushup(int x) {
if(!x) return;
if(dist[x]!=dist[rson[x]]+1) {
dist[x]=dist[rson[x]]+1
pushup(fa[x]);
}
}
void erase(int x) {
int y=merge(lson[x],rson[x]);
fa[y]=fa[x];
if(lson[fa[x]]==x)
lson[fa[x]]=y;
else rson[fa[x]]=y;
pushup(fa[y]);
}