其他分享
首页 > 其他分享> > 左偏树

左偏树

作者:互联网

左偏树

左偏树是一种具有堆的性质,支持在\(\log^2n\)时间内合并的数据结构

左偏树拥有两个属性:键值距离

距离

定义

我们定义外节点为左孩子或右孩子为空的节点

外节点的距离为0

非外节点的距离是该节点到最近的外节点的距离,空节点的距离为-1

性质

一颗有n个节点的二叉树,根的距离不超过\(\left\lceil\log(n + 1)\right\rceil\),因为一颗根的距离为x的二叉树至少有x-1层是满的,那么就至少有\(2^x-1\)个节点。

左偏树的定义和性质

左偏树是一颗二叉树,它的键值满足堆的性质。

它的距离满足左偏:每个节点的左孩子的距离都大于等于右孩子的距离。

因此,左偏树中的每个节点的距离都等于右孩子的距离+1。

合并操作(merge)

合并操作是左偏树的核心操作,以小根堆为例,合并x和y

具体步骤:

1.先将键值较小的节点作为合并后的根节点(这里假设x的键值更小)

2.然后将x的右孩子与y合并,将其根节点作为x新的右孩子

3.合并完成后可能会破坏x的左偏性质,所以合并后若x的孩子不满足左偏则交换x的两个孩子

4.最后再更新x的距离,为x的右孩子的距离+1

参考代码:

int merge(int x,int y)
{
	if (!x || !y) return x + y;//如果有一个为空直接返回剩下的那个 
	if (tr[y].v < tr[x].v || (tr[y].v == tr[x].v && x > y)) swap(x,y);//保证x的值小于等于y 
	tr[x].rp = merge(tr[x].rp,y);//合并x的右孩子和y 
	if (tr[tr[x].lp].dis < tr[tr[x].rp].dis) swap(tr[x].lp,tr[x].rp);//保证x的左孩子的距离大于等于右孩子 
	tr[x].dis = tr[tr[x].rp].dis + 1;//更新距离 
	return x;//返回根 
}

复杂度

由于左偏性质,每递归一层,其中一个堆的根节点的距离就会-1,而因为上面说过,“一颗有n个节点的二叉树,根的距离不超过\(\left\lceil\log(n + 1)\right\rceil\)”,所以合并两个大小分别为n和m的堆的复杂度是\(O(\log n + \log m)\)。

例题

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

这里贴上ac代码

#include<bits/stdc++.h>

using namespace std;

int read()
{
	int s = 0,f = 1; char x = getchar();
	while(x < '0' || '9' < x) f = (x == '-') ? -1 : 1 , x = getchar();
	while('0' <= x && x <= '9') s = s * 10 + x - '0' , x = getchar();
	return s * f;
}

const int N = 1e5 + 10;

int n,m,i;

struct tree
{
	int dis,v,lp,rp,rt;//dis记录距离,v记录数值,lp,rp记录左右孩子,rt记录根 
}tr[N];

int find(int x)//寻找根节点的同时路径压缩 
{
	while (tr[x].rt != x) x = tr[x].rt = find(tr[x].rt);
	return tr[x].rt;
}

int merge(int x,int y)
{
	if (!x || !y) return x + y;//如果有一个为空直接返回剩下的那个 
	if (tr[y].v < tr[x].v || (tr[y].v == tr[x].v && x > y)) swap(x,y);//保证x的值小于等于y 
	tr[x].rp = merge(tr[x].rp,y);//合并x的右孩子和y 
	if (tr[tr[x].lp].dis < tr[tr[x].rp].dis) swap(tr[x].lp,tr[x].rp);//保证x的左孩子的距离大于等于右孩子 
	tr[x].dis = tr[tr[x].rp].dis + 1;//更新距离 
	return x;//返回根 
}

void pop(int x)//删除堆顶 
{
	tr[tr[x].lp].rt = tr[tr[x].rp].rt = tr[x].rt = merge(tr[x].lp,tr[x].rp);//合并x的两个儿子 
	tr[x].dis = -1;//标记已被删除 
	return;
}

int main()
{
	n = read() , m = read();
	
	tr[0].dis = -1;
	
	for (i = 1;i <= n;i ++) tr[i].v = read() , tr[i].rt = i;
	
	int o,x,y;
	
	for (i = 1;i <= m;i ++)
	{
		o = read();
		if (o & 1)
		{
			x = read();
			y = read();
			if (tr[x].dis == -1 || tr[y].dis == -1) continue;//有一个节点已经被删除了
			int f1 = find(x),f2 = find(y);//找到两个节点的祖先 
			if (f1 != f2) tr[f1].rt = tr[f2].rt = merge(f1,f2);//如果两个节点不在同一个堆内就合并 
		}
		else
		{
			x = read();
			if (tr[x].dis == -1) puts("-1");//此节点已被删除 
			else cout << tr[find(x)].v << endl , pop(find(x));//输出该节点的堆顶的值,并且删除堆顶 
		}
	}
	return 0;
}

标签:rp,tr,距离,左偏,节点,dis
来源: https://www.cnblogs.com/9981day/p/16690073.html