定义
左偏树(Leftist Tree)是一种可并堆的实现。左偏树是一棵二叉树,它的节点除了和二叉树的节点一样具有左右子树指针( left, right)外,还有两个属性,键值和距离(dist)。
先引入一个概念
外节点:一个左子树为空或者右子树为空即可在其子树并入新元素的节点
距离:父节点到外节点最少的经历的边数
所以对于外节点,dist(i)=0;
性质
左偏树的本质是一颗有序的二叉树,故满足堆性质
- 节点的键值小于或等于它的左右子节点的键值
左偏树,顾名思义,是一颗向左偏的树,因为左边节点多而右边稀少,那么顺着右子树查找外节点一定比顺着左子树查找经过的边数少,这又引出了左偏树的两条性质
-
节点的左子节点的距离不小于右子节点的距离
-
节点的左子节点右子节点也是一颗左偏树
因为一个节点的dist实质上就是这个节点一直顺着右边查找外节点的距离,那么
- 节点的距离等于它的右子节点的距离加1
即dist(rt)=dist(r)+1;又因为外节点的dist定为0,而它的右节点为空,根据此性质,故空节点的dist为-1
左偏树还有一些其他的性质和距离与节点的关系,可以参考 左偏树的特点及其应用——黄源河
合并
在合并两个堆的时候,左右子树会渐渐变的平衡,但要时刻保持左偏树的性质,即当发现左子树的dist小于右子树的dist时,swap
还是以小根堆为例
操作
首先定义一些东西
struct node
{
int l,r,key,dist;//左、右儿子,键值,距离
node(int d=-1) {dist=d;}
}t[N];
int fa[N];//用并查集记录每颗左偏树堆顶编号
合并
结合上方图示理解
int Merge(int x,int y)//x,y为两棵树堆顶的编号
{
//如果一颗树为空,返回另一棵树
if(!x) return y;
if(!y) return x;
if(t[x].key > t[y].key) swap(x,y);//选择x为新树,若其根节点键值大于y,则交换
t[x].r=Merge(t[x].r,y);//然后将y与x的右子树合并
fa[t[x].r]=x;//将右子树的父节点赋为x
if(t[t[x].l].dist < t[t[x].r].dist) swap(t[x].l,t[x].r);//为保证左子节点的距离不小于右子节点的距离,合并完后判断是否需要交换左右子树
t[x].dist=t[t[x].r].dist+1;//最后更新根节点的dist值
return x;
}
插入
可看做与只有单点的左偏树合并
void push(int x,int y)//将y插入到编号为x的左偏树中
{
Merge(x,y);
}
删除
可看做将当前节点的左右儿子合并
void pop(int x)//删除编号为x的节点所在左偏树的堆顶元素
{
x=findf(x);//先找到堆顶编号
int tmp=Merge(t[x].l,t[x].r);
fa[x]=fa[t[x].l]=fa[t[x].r]=tmp;//重置父亲
}
查询
int top(int x)//返回编号为x的节点所在左偏树的堆顶的键值
{
return t[findf(x)].key;
}
模板
注意当堆中有多个最小值时,删除原序列中靠前的
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define N 100005
using namespace std;
int n,m;
int fa[N];
struct node
{
int l,r,key,dist;
node(int d=-1) {dist=d;}
}t[N];
int findf(int x)
{
if(x==fa[x]) return x;
return fa[x]=findf(fa[x]);
}
void con(int x,int y)
{
x=findf(x),y=findf(y);
fa[x]=y;
}
int top(int x){return t[findf(x)].key;}
int Merge(int x,int y)
{
if(!x) return y;
if(!y) return x;
if(t[x].key > t[y].key||(t[x].key==t[y].key&&x>y)) swap(x,y);
t[x].r=Merge(t[x].r,y);fa[t[x].r]=x;
if(t[t[x].l].dist < t[t[x].r].dist) swap(t[x].l,t[x].r);
t[x].dist=t[t[x].r].dist+1;
return x;
}
void pop(int x)
{
x=findf(x);
int tmp=Merge(t[x].l,t[x].r);
fa[x]=fa[t[x].l]=fa[t[x].r]=tmp;
t[x].l=t[x].r=t[x].key=0;t[x].dist=-1;//因为数据有可能给到已经被删过的点,所以需要将被删除节点清空
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {scanf("%d",&t[i].key);t[i].dist=0;fa[i]=i;}
for(int i=1;i<=m;i++)
{
int opt,x,y;scanf("%d%d",&opt,&x);
if(opt==1)
{
scanf("%d",&y);
if(!t[x].key||!t[y].key) continue;//若这个数被删除,不合并
int fx=findf(x),fy=findf(y);
if(fx!=fy) Merge(fx,fy);//不在一个集合里才合并
}
else
{
if(!t[x].key) printf("-1
");
else {printf("%d
",top(x));pop(x);}
}
}
return 0;
}
参考来源
百度百科
https://baike.baidu.com/item/%E5%B7%A6%E5%81%8F%E6%A0%91/2181887?fr=aladdin#2
图解数据结构(9)——左偏树 http://www.cnblogs.com/yc_sunniwell/archive/2010/06/28/1766756.html
可并堆?左偏树? ——MaxMercer http://blog.csdn.net/maxmercer/article/details/75136683
左偏树(可并堆)——yew1eb http://blog.csdn.net/yew1eb/article/details/19349099