左偏树入门

一、左偏树能做什么?

左偏树(Leftist Tree)是一种维护可并堆(Mergeable Heap)的数据结构。

可并堆是一种抽象数据结构(Abstract Data Type, ADT),在普通的堆(Heap)的基础上,增添了 upd 操作,使得两个堆可以合并。这也是其名之来历。

二、左偏树的基本操作

左偏树的基本操作有三种:

  1. 一切操作的核心:merge 操作(合并)
  2. del 操作【删除操作,分为删除堆顶元素(入门)和删除任意元素(进阶)】
  3. add 操作(加点操作)

本文将以洛谷的左偏树模板 P3377 为纲,故不会介绍 del 操作中的删除任意元素和 add 操作,同时,以下的堆实质上都为小根堆。

三、详解左偏树操作

  • Part ZERO: 有关左偏树的定义与基本性质、定理

    左偏树是一颗二叉树(Binary Tree),它的节点除了和二叉树的节点一样具有左右子树(Left Subtree, ls; Right Subtree, rs)以外,还有两个属性:键值(Value, v)距离(Distance, dis)

    • Definition ONE: 外节点(External Node)

      节点 \(i\) 被称为外节点,当且仅当节点 \(i\) 的左子树或右子树为空。

    • Definition TWO: 距离

      节点 \(i\)距离是它到其后代中最近的外节点所经过的边数。

      特别地,如果节点 \(i\) 本身就是外节点,则其距离为 \(0\); 空节点的距离被规定为 \(-1\).

      通常称一棵左偏树的距离为其根节点的距离。

    • Property ONE: 任意节点的键值不大于它的左右子节点的键值(小根堆性质)。

    Property ONE 可得:

    • Theorem ONE: 左偏树的根节点的键值是所有节点中最小的。

    Theorem ONE, 我们可以用 \(O(1)\) 的时间复杂度得到左偏树中最小的节点。

    • Property TWO: 任意节点的左子节点的距离不小于其右子节点的距离(左偏性质)。

    小根堆性质与左偏性质对于每一个节点都成立。因此,显然可以得出,左偏树的左右子树皆为左偏树。同时,我们可以得出左偏树之定义:具有左偏性质的堆有序二叉树是左偏树

    Property TWO 可知:

    • Property THREE: 任意节点的距离等于其右子节点的距离加 \(1\).

    接下来,我们将要讨论左偏树的距离与节点数之间的关系。

    • Lemma ONE: 若一棵左偏树的距离为一定值,则在所有可能的左偏树当中,节点最少的是一棵完全二叉树。

      Proof: 由 Property TWO 可证。

    Lemma ONE 可知:

    • Theorem TWO: 若一棵左偏树的距离为 \(k\), 则这颗左偏树至少有 \((2^{k+1}-1)\) 个节点。

    由此推论可知:

    • Corollary ONE: 一棵 \(n\) 个节点的左偏树的距离最大值为 \(\log_2(n+1)-1\).
  • Part ONE: merge 操作

    merge 操作在代码中体现为 int merge(int x,int y) 函数。其中,令 \(x\)\(A\) 堆的堆顶,\(y\)\(B\) 堆的堆顶。merge 操作的任务即是将 \(B\) 堆与 \(A\) 堆的右子树合并,且维护左偏树的性质。

    merge 操作分为以下几个过程:

    Step ONE: 特判。如果两个合并的堆有任意一个为空,则返回另一个堆。

    Step TWO: 判断优先级。判断两堆堆顶的键值大小。如果 \(x\) 的键值大于 \(y\) 的键值,则说明如果仍然将当前的 \(B\) 堆与 \(A\) 堆的右子树合并,就无法维护左偏树的小根堆性质,故需要 swap(x,y), 即将 \(A\) 堆和 \(B\) 堆“交换”。

    Step THREE: 合并。将 \(B\) 堆与 \(A\) 堆的右子树合并。

    Step FOUR: 判断距离。如果 \(A\) 的右子树的距离比左子树大,则违背了左偏树的左偏性质。则进行交换。

    Step FIVE: 更新距离。将整棵左偏树的距离进行更新。

    代码如下:

    const int maxn=100005;
    struct Node{
    	int ls;
    	int rs;
    	int v;
    	int dis;
    	int id;
    }tr[maxn];
    
    int merge(int x,int y){
    	if(!x||!y)return x+y;//Step ONE
    	if(tr[x].v>tr[y].v)
    		swap(x,y);//Step TWO
    	tr[x].rs=merge(tr[x].rs,y);fa[tr[x].rs]=x;//Step THREE
    	if(tr[tr[x].rs].dis>tr[tr[x].ls].dis)
    		swap(tr[x].ls,tr[x].rs);//Step FOUR
    	tr[x].dis=tr[x].rs?(tr[tr[x].rs].dis+1):0;//Step FIVE
    	return x;
    }
    

Part TWO: del 操作

del 操作相对来说非常容易理解,它所要做的就是删除一个堆的堆顶,并重新整理其他的节点使其重新成为一个堆。

删除堆顶很容易,我们只需要将堆顶节点的信息全部清空即可。

重新整理呢?我们换个角度想想。其实,我们只需要将原堆顶节点的两个子树使用 merge 操作合并!

代码如下:

int del(int x){
	int fx=tr[x].ls;int fy=tr[x].rs;
	tr[x].ls=0;tr[x].rs=0;
	tr[x].dis=0;
	fa[fx]=fx;fa[fy]=fy;
	int tmp=merge(fx,fy);
	return fa[x]=tmp;
}

Part THREE: 全部代码

下面的代码是洛谷 P3377 【模板】左偏树(可并堆)的 AC 代码。由于题目的要求,可能部分代码与上面的解释会有所不同,但不影响理解。

#include<bits/stdc++.h>

using namespace std;

void rd(int &x){
	x=0;int f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
	x*=f;
}

const int maxn=100005;
struct Node{
	int ls;
	int rs;
	int v;
	int dis;
	int id;
}tr[maxn];
int fa[maxn];
bool exist[maxn];
int n,m;

int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}

int merge(int x,int y){
	if(!x||!y)return x+y;
	if(tr[x].v>tr[y].v||(tr[x].v==tr[y].v&&tr[x].id>tr[y].id))
		swap(x,y);
	tr[x].rs=merge(tr[x].rs,y);fa[tr[x].rs]=x;
	if(tr[tr[x].rs].dis>tr[tr[x].ls].dis)
		swap(tr[x].ls,tr[x].rs);
	tr[x].dis=tr[x].rs?(tr[tr[x].rs].dis+1):0;
	return x;
}

int del(int x){
	int fx=tr[x].ls;int fy=tr[x].rs;
	tr[x].ls=0;tr[x].rs=0;
	tr[x].dis=0;
	fa[fx]=fx;fa[fy]=fy;
	int tmp=merge(fx,fy);
	return fa[x]=tmp;
}

int main(){
	rd(n);rd(m);
	fill(exist+1,exist+n+1,true);
	for(int i=1;i<=n;i++){
		rd(tr[i].v);
		tr[i].id=fa[i]=i;
	}
	for(int i=1;i<=m;i++){
		int op,x,y;
		rd(op);rd(x);
		if(op==1){
			rd(y);
			if(!exist[x]||!exist[y])continue;
			int fx=find(x);int fy=find(y);
			if(fx!=fy)merge(fx,fy);
		}else if(op==2){
			if(!exist[x]){cout<<-1<<endl;continue;}
			x=find(x);
			cout<<tr[x].v<<endl;
			del(x);
			exist[tr[x].id]=false;
		}
	}
  return 0;
}
posted @ 2021-03-14 15:39  Aehnuwx  阅读(110)  评论(0编辑  收藏  举报