【模板】左偏树

一、左偏树的性质

左偏树,又称可并堆,所以他有堆的性质。

定义几个量:\(val\)表示该节点的值,\(fa\)表示该节点的父亲,\(ch[2]\)表示该节点的两个儿子(因为他是二叉树),\(dis\)表示这个节点到离他最近的叶子节点的距离。

性质一:该节点的val不大于该节点左右儿子的val

证明:堆。

性质二:该节点左儿子的dis不小于该节点右儿子的dis

证明:左偏树的定义。为了更快的进行合并、查询、删除(快速提取最小值)。

性质三:该节点的dis等于该节点右儿子的dis。

证明:构造的。

性质四:一棵n个节点的左偏树的节点的距离k最多为\(log(n+1)-1\)

证明:因为左偏树是一棵二叉树,当他节点最少的时候他是一棵完全二叉树,所以\(n>=2^(k+1)-1\),那么\(k<=log(n+1)-1\)。(这个结论是用来证明左偏树的时间复杂度的)

二、左偏树的主要操作

左偏树的主要实现就是他的merge,我们删除,插入一个节点就是利用左偏树的merge函数进行操作的。

merge函数的实现方法:

首先,我们建出来的左偏树是一定要符合以上四个性质的,所以我们的merge就是为了在合并后,左偏树仍有这些性质。

1.我们要保证性质一

所以:

if(val[x]>val[y]||(val[x]==val[y]&&x>y))//性质一
{
	swap(x,y);
}

2.我们要开始合并,我们将以节点y形成的左偏树和节点x的右子树合并,在让合并后所形成的的左偏树满足性质。

ch[x][1]=merge(ch[x][1],y);//合并右子树和y

3.合并后的左偏树要满足性质三

dis[x]=dis[ch[x][1]]+1;//性质三

4.我们要更新节点的fa

fa[x]=fa[ch[x][0]]=fa[ch[x][1]]=x;//更新fa

5.为了操作4,我们要return 根节点

return x;

综上,merge函数就是:

int merge(int x,int y)
{
	if(!x||!y)
	{
		return x+y;
	}
	if(val[x]>val[y]||(val[x]==val[y]&&x>y))//性质一
	{
		swap(x,y);
	}
	ch[x][1]=merge(ch[x][1],y);//合并右子树和y
	if(dis[ch[x][0]]<dis[ch[x][1]])//性质二
	{
		swap(ch[x][0],ch[x][1]);
	}
	fa[x]=fa[ch[x][0]]=fa[ch[x][1]]=x;//更新fa
	dis[x]=dis[ch[x][1]]+1;//性质三
	return x;
}

三、基于merge函数的操作

删除某一节点(pop)

我们只要合并这个节点的左右儿子即可。

void pop(int x)
{
	val[x]=-1;
	fa[ch[x][0]]=ch[x][0];
	fa[ch[x][1]]=ch[x][1];
	fa[x]=merge(ch[x][0],ch[x][1]);
}

求某一节点所在堆得最小值,并删除此节点

xx=getfa(x);
printf("%d\n",val[xx]);
pop(xx);

另:

有关getfa:

一定要路径压缩,不然原来\(O(logn)\)的查询,就会被一条链卡成\(O(n)\)

四、例题:

1.罗马游戏
2.Joint Stacks

posted @ 2019-08-19 15:21  ezoi_ly  阅读(194)  评论(0编辑  收藏  举报