左偏树

左偏树

定义

左偏树(英语:leftist treeleftist heap),也可称为左偏堆、左倾堆,是计算机科学中的一种树,是一种优先队列实现方式,属于可并堆,在信息学中十分常见,在统计问题、最值问题、模拟问题和贪心问题等等类型的题目中,左偏树都有着广泛的应用。斜堆是比左偏树更为一般的数据结构。


---------------百度百科

​ 在这里我们引入一个外节点的概念:左子树或右子树为空的节点。

​ 在引入一个距离的概念:一个节点到它最近的外节点所经过的距离。

性质

​ 1.左偏树是一个堆,堆顶元素小于(大于)它左右儿子的元素;

​ 2.左偏树左儿子的距离大于等于右儿子的距离;

​ 3.节点的距离等于它右儿子的距离+1;

​ 4.一颗有\(n\)个节点的左偏树,它的深度不超过\(\lfloor log_{2}(n+1) \rfloor - 1\)

​ 证明一下性质4,当左偏树是一颗满二叉树时,它的节点数有\(2^{k + 1} - 1\)个,所以\(n <= 2^{k + 1} - 1\),转化一下,\(k <= \lfloor log_2(n + 1) \rfloor - 1\)

操作

合并

​ 合并是左偏树最基础的操作,其他操作都要依赖合并操作。

​ 先结合图片理解一下合并的过程:

​ 我们将两个堆合并,将堆顶元素的大的堆(A)合并在堆顶元素小的堆(B)的下边(根据不同题意),如果A < B,交换就好了。我们每次将B与A的右子树合并,合并完如果发现左子树的距离小于右子树的距离,那么就交换左右子树,然后利用性质3更新B的距离。

​ 根据性质4,合并的最差复杂度是\(O(log(sizeA) +log(sizeB))\)

int merge(int x, int y) {
    if(x == 0 || y == 0) return x + y; //如果有一棵树为空,直接返回那个不为空的树
    if(vis[x] > vis[y] || (vis[x] == vis[y] || x > y)) swap(x, y); //保证堆顶最小,字典序小
    ch[x][1] = merge(ch[x][1], y);
    f[ch[x][1]] = x;
    if(dis[ch[x][0]] < dis[ch[x][1]]) swap(ch[x][0], ch[x][1]); //保证左子树距离大于右子树距离
    dis[x] = dis[ch[x][1]] + 1; //性质3
    return x;
}

插入

​ 就是将一个节点与一个堆合并。

删除

​ 找到\(x\)的堆顶,将堆顶的左右子树合并。

int find(int x) {
	if(fa[x]) x = fa[x]; return x;
}

void pop(int x) {
    x = find(x);
    f[ch[x][1]] = f[ch[x][0]] = 0;
    merge(ch[x][1], ch[x][0]);
}

模板

P3377 【模板】左偏树(可并堆)

​ 这个题注意找堆顶时要写路径压缩,不然最后一个点过不去。因为\(find\)函数是\(O(n)\)的。

​ 那怎么写路径压缩呢?路径压缩可能会改变左偏树的性质,我们在删点操作时,把删除的点连在合并完的堆下面,之后路径压缩找父亲时就不会断掉。

#include <cstdio>
#define R register

using namespace std;

inline int read() {
	int s = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
	return s * f;
}

const int N = 1e5 + 5;
int n, m;
int vis[N], ch[N][2], f[N], dis[N], fa[N];

inline int find(R int x) {
	while(f[x] == 0) return x;
	return f[x] = find(f[x]);
}

void swap(int &x, int &y) { int t = x; x = y; y = t; }

int merge(R int x, R int y) {
	if(x == 0 || y == 0) return x + y;
	if(vis[x] > vis[y] || (vis[x] == vis[y] && x > y)) swap(x, y);
	ch[x][1] = merge(ch[x][1], y);
	f[ch[x][1]] = x;
	if(dis[ch[x][0]] < dis[ch[x][1]]) swap(ch[x][1], ch[x][0]);
	dis[x] = dis[ch[x][1]] + 1;
	return x;
}

void div(R int x) {
	vis[x] = -1;
	f[ch[x][0]] = 0; f[ch[x][1]] = 0;
	f[x] = merge(ch[x][1], ch[x][0]);
}

int main() {
	
	n = read(); m = read(); dis[0] = -1;
	for(int i = 1;i <= n; i++) vis[i] = read(), fa[i] = 0;
	for(int i = 1;i <= m; i++) {
		int opt = read();
		if(opt == 1) {
			int x = read(), y = read();
			if(vis[x] == -1 || vis[y] == -1 || x == y) continue;
			x = find(x), y = find(y);
			x = merge(x, y);
		}
		else {
			int x = read();
			if(vis[x] == -1) printf("-1\n");
			else {
				x = find(x); printf("%d\n", vis[x]); div(x);
			}
		}
	}
	
	return 0;
}
posted @ 2020-07-29 11:12  C锥  阅读(260)  评论(1编辑  收藏  举报