斜堆、左偏树简明教程

在堆的实际应用中,我们经常要做一些堆的合并操作。

这时候,我们就需要一种新的数据结构:可并堆。

 

可并堆有很多种,例如:斜堆,左偏树,(配对堆、斐波那契堆,这两个不会说的,因为我不会)。

这里介绍两种斜堆和左偏树。

 

斜堆:

我们将根节点值较大的堆称作a,较小的堆称作b(小根堆相反)。

那么我们把a的右儿子和b合并作为新堆的右儿子,将a的左儿子作为新堆的左儿子。

那么我们会遇到这样一个情况,就是随着合并的次数增多,右儿子会变得比较大,而左儿子会变得很小。

那么我们可以想到一个显而易见的做法,那就是在每次合并后,交换左右儿子,这样下一次合并的时候就会合并到较小的子树上。

得到了这样的一个算法后,我们来考虑一下复杂度。

在不加优化的合并方式中,会出现一种情况就是几乎连成一条链,在这种形态下的单次合并复杂度会几乎达到O(N)。

而我们每次将左右儿子交换,就能保证不会出现这种情况。

因为事实上每一次新加入的元素是类似平均地加入到这棵树中(因为每次都是加到较小的子树中)。

这样就能保证合并的复杂度均摊到O(logn)了。

void merge(int rta,int rtb){
	if(size[rta]==0)return rtb;
	if(size[rtb]==0)return rta;
    if(key[rta]<key[rtb])swap(rta,rtb);
    rchild[rta]=merge(rchild[rta],rtb);
	swap(lchild[rta],rchild[rta]);
    return rta;
}

  

 

左偏树:

顺着斜堆的想法,我们可以想到,一次合并的效率与这次合并多快能遇到【一个只有一个儿子的节点】有关(因为在递归合并两棵子树时,若一棵为空,则合并完成)。

那么我们顺着这个思路,可以定义一个值为与点i最近的【一个只有一个儿子的节点】的距离为dis(i),并且规定每个点左儿子的dis一定比右儿子的dis大,否则交换左右儿子。

这样每个点的dis就是右儿子dis+1,dis(一个只有一个儿子的节点)=0。

合并时,与斜堆类似,递归地合并。将根节点值较小的树插到较大的树的右子树里(小根堆相反),在此过程中更新每个点的dis值,并通过交换左右儿子使它满足刚才的性质。

void merge(int rta,int rtb){
	if(size[rta]==0)return rtb;
	if(size[rtb]==0)return rta;
    if(key[rta]<key[rtb])swap(rta,rtb);
    rchild[rta]=merge(rchild[rta],rtb);
    if(dis[lchild[rta]]<dis[rchild[rta]])
	    swap(lchild[rta],rchild[rta]);
	dis[rta]=dis[rchild[rta]]+1;
    return rta;
}

【两种可并堆的除合并以外的操作与堆相同,不再赘述】

 

原文地址:https://www.cnblogs.com/gdc-destinies/p/8377092.html