左偏树
作者:互联网
左偏树
左偏树是一种具有堆的性质,支持在\(\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