堆/左偏树

堆(二叉堆)

定义

是完全二叉树,每个点有一 \(val\) ,且满足,对于给定的比较函数我们有,对于任意一个点,其与其任意儿子间的关系都满足该关系。

例:大根堆的每个点都大于它的任意儿子,小根堆则每个点都小于它的任意儿子。

STL 中有 priority_queue<> 实现了该功能。

下面以小根堆为例。别的堆可以自定义比较函数得到。

功能

一个堆支持如下操作:

  1. 查询堆顶元素
  2. 删除堆顶元素
  3. 插入新的元素

实现

我们需要动态维护两个性质:完全二叉树和每个儿子大于其父亲。

1.查询堆顶元素

由于我们始终保持其堆的性质,所以根节点即为堆顶元素,返回即可。

2.删除堆顶元素

我们先 swap(root,lastnode) ,并将 lastnode 位置删除,这样我们就删除了指定元素,并保持了完全二叉树的性质。但是我们遇到另外一个问题,我们在这一操作中破坏了其父子节点大小关系,这就需要我们进行调整。注意到不合法的位置有且仅有我们刚刚换上去的根,于是我们可以在它的两个儿子中选一个较小的,并将该点与其交换。但是这样堆可能仍不合法,我们只需对该子树重复该操作即可。(当然,如果堆已合法,我们不需再调整)由于其为完全二叉树,我们最多调整 \(\log n\) 次。

3.插入新的元素

我们将新加的元素直接放在完全二叉树上的第一个合法位置。然后我们又遇到了不合法的问题,我们可以参考操作二中的调整,只不过这次是自底向上调整,复杂度相同,也为 \(O(\log n)\)

一个支持上述三种操作的示例
struct heap
{
int a[mn],tp;
heap()
{
tp=0;
}
void mvup(int pos)
{
while(pos!=1 && a[pos>>1]>a[pos])
{
swap(a[pos>>1],a[pos]);
pos=pos>>1;
}
}
void mvdn(int pos)
{
while((pos<<1)<=tp)
{
if((pos<<1)==tp)
{
if(a[pos<<1]<a[pos])swap(a[pos],a[pos<<1]);
break;
}
if(a[pos<<1]<a[pos<<1|1])
{
if(a[pos<<1]<a[pos])swap(a[pos],a[pos<<1]);
else break;
pos<<=1;
}
else
{
if(a[pos<<1|1]<a[pos])swap(a[pos],a[pos<<1|1]);
else break;
pos=pos<<1|1;
}
}
}
void push(int x)
{
a[++tp]=x;
mvup(tp);
}
void pop()
{
swap(a[1],a[tp]);
tp--;
mvdn(1);
}
int top()
{
return a[1];
}
}q;

左偏树

这是一种可并堆。下面给出 OI-wiki 上的定义。

对于一棵二叉树,我们定义 外节点 为子节点数小于两个的节点,定义一个节点的 \(dist\) 为其到子树中最近的外节点所经过的边的数量。空节点的 \(dist\)\(0\)
左偏树是一棵二叉树,它不仅具有堆的性质,并且是「左偏」的:每个节点左儿子的 \(dist\) 都大于等于右儿子的 \(dist\)

其核心操作为 join ,即将两个点及其子树合并。下面给出以小根堆为实例的代码及注释。

node* join(node* x,node *y)//返回指针方便父亲找儿子
{
if(!x || !y)return x?x:y;//如果有至少一个是空的,那就不用合并了,将非空的作为合并好的即可
node *res=NULL;
if(x->val>y->val || (x->val==y->val && x->id>y->id))swap(x,y);//优先选小的点作为根,这里还要比id是因为洛谷板子要求的
res=join(x->ch[1],y);//合并右子和非根的那一部分
x->ch[1]=res;//将合并的子树接在x的右儿子上
if(res)res->fa=x;
if(x->ch[1] && (!x->ch[0] || x->ch[1]->dist>x->ch[0]->dist))swap(x->ch[0],x->ch[1]);//判断dist是否满足要求,不满足则需要交换左右儿子
x->dist=(x->ch[1]?x->ch[1]->dist+1:0);//更新x的dist
x->fa=NULL;
return x;
}

然后别的操作我们可以以 join 为基础实现:

  1. 加入点:将新点看作一个堆,与原堆合并
  2. 删除点:合并该点的左右儿子
支持合并,查堆顶,删堆顶,加点的堆的左偏树实现
struct letree
{
struct node
{
node *fa,*ch[2];
int dist,val,id;
// int sz;
node(int x,int nid)
{
dist=0,val=x;
id=nid;
// sz=1;
fa=ch[0]=ch[1]=NULL;
}
}*root;
// int sz;
letree()
{
// sz=0;
root=NULL;
}
node* join(node* x,node *y)
{
if(!x || !y)return x?x:y;
node *res=NULL;
if(x->val>y->val || (x->val==y->val && x->id>y->id))swap(x,y);
res=join(x->ch[1],y);
x->ch[1]=res;
if(res)res->fa=x;
if(x->ch[1] && (!x->ch[0] || x->ch[1]->dist>x->ch[0]->dist))swap(x->ch[0],x->ch[1]);
x->dist=(x->ch[1]?x->ch[1]->dist+1:0);
x->fa=NULL;
return x;
}
void join(letree &x)
{
root=join(root,x.root);
x.root=NULL;
}
bool empty()
{
return root==NULL;
}
void push(int x,int id)
{
node *o=new node(x,id);
root=join(root,o);
}
int top()
{
return empty()?-1:root->val;
}
void pop()
{
deld[root->id]=1;
root=join(root->ch[0],root->ch[1]);
}
}q[mn];
支持合并,查堆顶,删堆顶,加点,删除指定元素,调整某一元素大小的更为强大的左偏树实现
struct node
{
node *fa,*ch[2];
int dist,val;
// int sz;
node(int x)
{
dist=0,val=x;
// sz=1;
fa=ch[0]=ch[1]=NULL;
}
bool isr()
{
return fa->ch[1]==this;
}
}*now[mn];//用于存储各元素位置,方便erase
int nodecnt=0;
struct letree
{
node *root;
letree()
{
root=NULL;
}
node* join(node* x,node *y)// 核心
{
if(!x || !y)return x?x:y;
if(x->val>y->val)swap(x,y);
x->ch[1]=join(x->ch[1],y);
if(x->ch[1])x->ch[1]->fa=x;
if(x->ch[1] && (!x->ch[0] || x->ch[1]->dist>x->ch[0]->dist))swap(x->ch[0],x->ch[1]);
x->dist=(x->ch[1]?x->ch[1]->dist+1:0);
x->fa=NULL;
return x;
}
void modup(node *x)//用于在erase后向上调整以保持左偏性质
{
while(x)
{
if(x->ch[1] && (!x->ch[0] || x->ch[1]->dist>x->ch[0]->dist))
{
swap(x->ch[0],x->ch[1]);
x->dist=(x->ch[1]?x->ch[1]->dist+1:0);
x=x->fa;
}
else break;
}
}
void join(letree &x)//合并
{
root=join(root,x.root);
x.root=NULL;
}
bool empty()//还有元素吗?
{
return root==NULL;
}
int top()//堆顶是什么?
{
return empty()?-1:root->val;
}
node* pop()// 删堆顶,有返回值是方便回收
{
node* res=root;
root=join(root->ch[0],root->ch[1]);
return res;
}
void push(int x)//加点
{
now[++nodecnt]=new node(x);
root=join(root,now[nodecnt]);
}
/*
也可以定义
void push(int x,int id)
{
now[id]=new node(x);
root=join(root,now[id]);
}
以指定id,但注意不能与上面那种混用,否则会出现两个元素编号相同
*/
node* erase(int x)// 删除编号为x的元素,有返回值是方便回收
{
if(now[x]==root)return pop();//特判没有父亲的情况
bool k=now[x]->isr();
now[x]->fa->ch[k]=join(now[x]->ch[0],now[x]->ch[1]);
if(now[x]->fa->ch[k])now[x]->fa->ch[k]->fa=now[x]->fa;
modup(now[x]->fa);
return now[x];
}
void dkey(int x,int y)//调整编号为x元素值为y
{
now[x]=erase(x);// 回收!!
now[x]->val=y;
now[x]->dist=0;
now[x]->fa=now[x]->ch[0]=now[x]->ch[1]=NULL;
root=join(root,now[x]);
}
};

本文作者:ikusiad

本文链接:https://www.cnblogs.com/ikusiad/p/18730041

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @ 2025-02-21 19:51  ikusiad  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起