左偏树
左偏树
一般的堆,仅能支持一些基本操作。
当我们要合并两个堆时,二叉堆要枚举其中一个堆的所有值并插入另一个堆,时间复杂度高达惊人的 \(O(n\log n)\)。
由于二叉堆维护的信息过少,导致合并时间复杂度过高。
而左偏树在二叉堆的基础上多维护了一个 \(dis\) 数组,使合并两个堆的复杂度是优秀的 \(O(\log n+\log m)\)。
1.左偏树的定义
左偏树是一种具有 左偏性质 的,满足堆性质 的二叉树,左偏树不一定是完全二叉树 。
左偏树每个节点包括左右子节点编号 \(lc[x],rc[x]\)、本身的值 \(v[x]\) 及 距离 \(dis[x]\) 。
一些定义
-
外节点 :左儿子 或 右儿子为空的节点。
-
距离 :该节点与最近的外节点经过的边数。外节点的距离为 \(0\) ,空节点的距离为 \(-1\) ,空节点指不存在的节点。
一些性质
对于任何节点:
-
有 \(v[x]\ge v[lc[x]],v[rc[x]]\) (大根堆)。
-
有 \(dis[lc[x]]\ge dis[rc[x]]\)(左偏)。
-
有 \(dis[x]=dis[rc[x]]+1\) 。
对于根节点:
有 \(dis[root]\le \log(n+1)-1\) 。
2.左偏树的实现
左偏树最重要的操作是 合并 ,流程大致如下:
-
维护堆的性质,选取值较小的根作为合并后的堆顶,然后递归合并其右儿子与另一个堆,作为合并后的堆的右儿子。
-
维护左偏性质,合并后若左儿子的 \(dis<\) 右儿子的 \(dis\) ,就交换两个儿子。
int Merge(int x,int y){
if(!x||!y) return x+y;//一堆为空
if(v[x]<v[y]) swap(x,y);//选择较大值作为堆顶
rc[x]=Merge(rc[x],y);//递归合并右儿子
if(dis[lc[x]]<dis[rc[x]]) swap(lc[x],rc[x]);//维护左偏性质
dis[x]=dis[rc[x]]+1;//更新局部根节点dis值
return x;//返回根节点编号
}
插入操作:将新插入节点当做一个堆,合并即可。
这里设插入节点 \(y\) 所在堆:
void Insert(int x,int y){
v[++n]=x;
Merge(Get(y),n);//并查集查找y堆顶
}
删除操作:将删除节点的左、右儿子分别合并即可。
这里设删除节点 \(y\) 所在堆堆顶
void Pop(int y){
int fy=Get(y);//同上
v[fy]=-1;//空节点值-1
bin[lc[fy]]=bin[rc[fy]]=bin[fy]=Merge(lc[fy],rc[fy]);//维护并查集并合并左右儿子,正常情况下只需写Merge(lc[fy],rc[fy])
}
鸽子中……
posted on 2024-07-17 01:01 zengziquan 阅读(1) 评论(0) 编辑 收藏 举报