【模板】左偏树
一、左偏树的性质
左偏树,又称可并堆,所以他有堆的性质。
定义几个量:\(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