左偏树(可并堆)

前言

关于我想去学环状树却学了左偏树这件事

正文

前置芝士

并查集

性质

根据算法名称就可以知道,这是一个森林中所包含的所有树都向左偏的森林(也是因为他向左偏,并且可以进行合并,所以他也是可并堆)。

算法函数

建树: 很简单,并查集实现(就不放代码了)。

合并: 也就是把两棵树合并先放代码:

int merge(int x,int y/*要进行合并的两棵树的根*/) {
	if (!x or !y) {
		return x+y;
	}
	if (val[x]>val[y] or (val[x]==val[y] and x>y)/*可以根据大小跟堆调整大小余号*/) {
		swap(x,y);//维护左偏
	}
	r[x]=merge(r[x],y);//递归寻找右子树
	fa[l[x]]=fa[r[x]]=fa[x]=x;
	if (dis[l[x]]<dis[r[x]]) {
		swap(l[x],r[x]);//依旧维护
	}
	dis[x]=dis[r[x]]+1;//更新根节点到叶子节点的最短路径
	return x;
}

按照代码从上往下说,首先来看这句:

if (!x or !y) {
	return x+y;
}

什么意思呢,很简单我们可以把它和 r[x]=merge(r[x],y); 结合食用,也就是说再找右子树时,如果到了叶子节点或 \(y\) 变为 \(0\) 了,就把以 \(y\) 为根的树变为右子树或返回右子树当前节点的值(因为 \(y\) 等于零的情况肯定是前几次循环,\(x\) 与 \(y\) 交换了值)。

其他就不用说了,都是维护左偏树。

删除: 删除分两种,一种是把根节点删除(也就是删除堆中的最大或最小元素),第二种是删除任意元素。

先上代码:

//删除堆顶
void Delete(int x) {
	vis[x]=1;
	fa[l[x]]=l[x],fa[r[x]]=r[x];
	fa[x]=merge(l[x],r[x]);
}
//任意
void Delete(int x) {
	int L=l[x],R=r[x];
	fa[L]=L,fa[R]=R;
	l[x]=r[x]=dis[x]=0;
	merge(merge(L,R),find(x));
}

其实这两种差不了多少,都是先把左右子树合并,再和自己父节点所在的子树合并(可以想一想,很简单)。

嗯对就没了,一共就这两种操作,上一下例题 p3377 的代码(无注释):

#include<bits/stdc++.h>

using namespace std;

int fa[100001];
int  val[100001]; 
int dis[1000010];
int ls[1000010],rs[1000010];
bool vis[1000010];

int n,m;

int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
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);
    rs[x] = merge(rs[x],y);
    fa[ls[x]] = fa[rs[x]] = fa[x] = x;
    if(dis[ls[x]] < dis[rs[x]]) swap(ls[x],rs[x]);
    dis[x] = dis[rs[x]] + 1;
    return x;
}
void Union(int x,int y)
{
    int xx = find(x),yy = find(y);
    if(vis[x] || vis[y] || xx == yy) return;
    fa[xx] = fa[yy] = merge(xx,yy);
}
void Delete(int x)
{
    vis[x] = 1;
    fa[ls[x]] = ls[x]; fa[rs[x]] = rs[x];
    fa[x] = merge(ls[x],rs[x]);
}
int get_ans(int x)
{
    if(vis[x]) return -1;
    int xx = find(x);
    Delete(xx);
    return val[xx];
}
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0' or ch>'9')
    {
        if(ch=='-')
        {
            f=-1;ch=getchar();
        }
    }
    while(ch>='0' and ch<='9')
    {
        x=x*10+ch-'0';ch=getchar();
    }
    return x*f;
}

void work()
{
    cin>>n>>m; 
    for(int i = 1;i <= n;i ++) cin>>val[i];
    for(int i = 1;i <= n;i ++) fa[i] = i;
    for(int i = 1,opt,x,y;i <= m;i ++)
    {
        cin>>opt>>x;
        if(opt == 1) {cin>>y; Union(x,y);}
        else printf("%d\n",get_ans(x));
    }
}
int main() {return work(),0;}

例题

p2713

和板子题一模一样。

p4971

需要删去任意节点,另外注意判断。

p1456

每次合并要除以 \(2\),以及要寻找堆顶。

finally

没了。

上一篇:HNSW近邻搜索算法


下一篇:庖丁解牛-图解MySQL 8.0优化器查询解析篇