左偏树

左偏树

一般的堆,仅能支持一些基本操作。

当我们要合并两个堆时,二叉堆要枚举其中一个堆的所有值并插入另一个堆,时间复杂度高达惊人的 \(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编辑  收藏  举报

导航