左偏树(可并堆)学习笔记

引入:堆是一种很常用的数据结构,可以在 $ O_{log(n)} $的复杂度内进行插入,以维护最大/最小值。但是,如果我们将两个堆合并,即使是启发式合并,时间复杂度也高达 $ O_{(size_a \times log(size_b))} $,而这是我们所不想看到的。那么,有没有一种更好的解决方案呢?

下面来引入一种新的数据结构——左偏树。顾名思义,这是一颗树,但是向左偏。首先定义一个 \(dis_u\),表示 \(u\) 到最近的非满节点(即没有左儿子或右儿子的节点)的距离。

而左偏的性质,若设左儿子右儿子分别为 \(ls\)\(rs\),则在这棵树上,对于每个结点,都有 \(dis_{ls} \geq dis_{rs}\)。显然,当 \(dis\) 为定值时,如果令这棵子树结点最多,那么这棵子树是一棵满二叉树。所以,对于一个大小为 \(n\) 的子树 \(u\),有 \(2^{dis_u+1}-1 \ge n\)

利用这一性质,我们可以进行合并操作。合并时,只需要维护堆性质和左偏树性质即可。

int fa[N], ls[N], rs[N], val[N], dis[N];
int find(int x){
	if(fa[x] ^ x){
		fa[x] = find(fa[x]);	
	}
	return fa[x];
}
int merge(int x, int y){
	if(!x || !y){
		return x+y;
	}
	if(val[x] > val[y]){
		swap(x, y);
	}
	rs[x] = merge(rs[x], y), fa[rs[x]] = x;
	if(dis[ls[x]]<dis[rs[x]]) swap(ls[x], rs[x]);
	dis[x] = dis[rs[x]]+1;
	return x;
}

因为每次往下走一层,\(dis\) 会减少 \(1\),而根据性质,\(dis\) 不会超过 \(log(size+1)\),因此,最坏情况(每次都交换)的复杂度为 \(O_{(log(m) + log(n))}\),其中 \(m\)\(n\)为合并的两棵子树的大小。

如何删除堆顶元素呢?只需要合并左右儿子。

void del(int x){
	val[x] = -1 ,fa[ls[x]] = ls[x], fa[rs[x]] = rs[x];
	fa[x] = merge(ls[x], rs[x]);
}
posted @ 2023-06-07 10:45  霜木_Atomic  阅读(15)  评论(0编辑  收藏  举报