左偏树学习笔记

今天刚刚学习了左偏树,这里记录一下,加深理解

首先,我们用左偏树可以做到\(O(log_n)\)来合并两个堆,\(O(log_n)\)来删除等一些\(O(log_n)\)的操作

\(dis\)为这个节点到它的子树中最近的一个叶子的距离,对于一个小根堆它有这么几条性质

1.一个节点的值小与左、右儿子的值
2.一个节点的左儿子的\(dis\)不小于右儿子的\(dis\)
3.一个节点的\(dis\)始终等于右儿子+1
4.节点数为n的左偏树,距离最大为\(log_{(n+1)}−1\)

然后来写一点它的基本的操作

\(Merge\)合并堆

每次递归的\(x\)\(y\)就是两个堆的根节点,首先我们要让\(y\)的值更大来方便后续的操作,判一下是不是\(y\)更大,不是就\(swap\)一下,然后我们就可以考虑让\(y\)所在的堆来与\(x\)的右子树合并,然后一直递归,最后把\(y\)接到\(x\)的右子树上

那么怎么维护左偏呢?每次判一下\(x\)的左右两个儿子,如果右儿子的\(dis\)大于左儿子的,那么交换这两个儿子

int merge(int xx,int yy)
{
    if(xx==0||yy==0)
        return max(xx,yy);
    if(a[xx].num>a[yy].num||(a[xx].num==a[yy].num&&xx>yy))
        swap(xx,yy);
    a[xx].r1=merge(a[xx].r1,yy);
    if(a[a[xx].l1].dis

Pop删除所在堆中最小结点

这个就只接把堆顶的左右两个子树\(Merge\)一下即可

但是有一个问题让我在这里想了很久,代码调了好久才发现是这里的问题,这也是我写这篇博客的理由QWQ

a[xx].fa=merge(a[xx].l1,a[xx].r1);

如果你是写的路径压缩的并查集,那么就要把删去的点的父亲设为他的两个子树合并后的堆顶

左偏树学习笔记_第1张图片

那么这是为什么呢?如上图所示,我们删去了1号节点,而由于路径压缩的原因,下面的2、3、4、5号节点的父亲仍为1号节点,如果不把1号节点的父亲设为2号节点,那么在查找下面节点的堆顶的时候就会找到1号节点这个已经被删掉的节点

void pop(int xx)
{
    a[xx].num=-1,a[a[xx].l1].fa=a[xx].l1,a[a[xx].r1].fa=a[xx].r1;
    a[xx].fa=merge(a[xx].l1,a[xx].r1);
}

还有一些其他的操作,要注意一下维护左偏树的性质,具体就不在这写了

洛谷模板的代码

#include
using namespace std;
struct node
{
    int num,l1,r1,fa,dis;
}a[100003];
int n,m,typ,x,y;
int get(int x1)
{
    while(a[x1].fa!=x1)
        a[x1].fa=a[a[x1].fa].fa,x1=a[x1].fa;
    return x1;
}
int merge(int xx,int yy)
{
    if(xx==0||yy==0)
        return max(xx,yy);
    if(a[xx].num>a[yy].num||(a[xx].num==a[yy].num&&xx>yy))
        swap(xx,yy);
    a[xx].r1=merge(a[xx].r1,yy);
    if(a[a[xx].l1].dis

你可能感兴趣的:(左偏树学习笔记)