可并堆——左偏树

定义

左偏树(Leftist Tree)是一种可并堆的实现。左偏树是一棵二叉树,它的节点除了和二叉树的节点一样具有左右子树指针( left, right)外,还有两个属性,键值和距离(dist)。

先引入一个概念

外节点:一个左子树为空或者右子树为空即可在其子树并入新元素的节点

距离:父节点到外节点最少的经历的边数

所以对于外节点,dist(i)=0;

性质

左偏树的本质是一颗有序的二叉树,故满足堆性质

  • 节点的键值小于或等于它的左右子节点的键值

左偏树,顾名思义,是一颗向左偏的树,因为左边节点多而右边稀少,那么顺着右子树查找外节点一定比顺着左子树查找经过的边数少,这又引出了左偏树的两条性质

  • 节点的左子节点的距离不小于右子节点的距离

  • 节点的左子节点右子节点也是一颗左偏树

因为一个节点的dist实质上就是这个节点一直顺着右边查找外节点的距离,那么

  • 节点的距离等于它的右子节点的距离加1

即dist(rt)=dist(r)+1;又因为外节点的dist定为0,而它的右节点为空,根据此性质,故空节点的dist为-1

左偏树还有一些其他的性质和距离与节点的关系,可以参考 左偏树的特点及其应用——黄源河

合并

在合并两个堆的时候,左右子树会渐渐变的平衡,但要时刻保持左偏树的性质,即当发现左子树的dist小于右子树的dist时,swap

还是以小根堆为例
image

image

image

操作

首先定义一些东西

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;
}

模板

luogu3377

注意当堆中有多个最小值时,删除原序列中靠前的

#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\n");
			else {printf("%d\n",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

posted @ 2017-08-16 09:56  XYZinc  阅读(652)  评论(6编辑  收藏  举报