算法基础:打开程序设计之门
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.3 左倾堆

前面介绍过堆,当需要合并两个堆时,往往效率较低。本节介绍的左倾堆能高效地实现两个堆的合并,称之为可并堆。左倾堆又称为左偏树、左偏堆、最左堆等。

1.3.1 左倾堆相关定义和性质

零距离(Null Path Length,NPL)是指从一个节点到一个“最近的不满节点”的路径长度。不满节点是指该节点的左、右子节点至少有一个为空。叶子节点的NPL为0;空节点的NPL为-1。

在左倾堆中的每个节点均维护两个值—键值和零距离。左倾堆满足:

(1)节点的键值小于或等于它的子节点的键值,即堆性质。

(2)节点的左子节点的NPL大于或等于右子节点的NPL,即左倾性质。

(3)节点的NPL等于它的右子节点的NPL+1。

(4)左倾堆的任意子树也是左倾堆。

1.3.2 左倾堆的操作

(1)两个左倾堆的合并。将AB两个左倾堆合并为C,如果其中一个为空,只需返回另一棵树即可;当AB都为非空时,假设A的根节点小于等于B的根节点(否则交换AB),把A的根节点作为新树C的根节点,然后合并A的右子树和B,之后右子树的NPL可能会变大,当A的右子树的NPL大于其左子树的NPL时,左倾堆的左倾会被破坏。在这种情况下,只需要交换左、右子树。最后,由于A的右子树的NPL可能发生改变,必须更新A的NPL:NPL(A)←NPL(A的右子树)+1。

(2)插入新节点。单个节点也可以看成一个左倾堆,因此向左倾堆插入一个节点可以看成两个左倾堆的合并。

(3)删除最小节点。左倾堆的根节点就是最小节点,在删除根节点后,需要将两棵子树合并。

左倾堆的实现如下所述。

1.3.3 例题讲解

例1-2 Financial Fraud

Time Limit:3000 ms Memory Limit:65536 KB

题目描述:

给定一个整数序列A1,A2,…,AN,求一个非递减序列B1,B2,…,BN(1≤i<jNBiBj),使得风险值最小,即|A1-B1|+|A2-B2|+…+|AN-BN|最小。

输入:

第一行输入NN≤50000),第二行输入N个整数表示序列A(-109Ai≤109),N等于0时结束输入。

输出:

最小风险值。

样例输入:

4

300 400 200 100

0

样例输出:

400

题目来源:ZOJ3512。

解题思路:

A是一个非递减序列时,只需让Bi=Ai即可;当A是一个非递增序列时,最优解是让B1=B2=…=BN=序列A的中位数(中位数是指序列中第N/2大的数);当A不是以上两种特殊情况时,可以考虑把序列A分为一些区间段,相对应的序列B为那一段的中位数。

假设已经找到前k个数A1,A2,…,Akk<N)的最优解,需要求前k+1个数的最优解。先把第k+1个数单独作为一个区间,则中位数就是Ak+1,因为要求B是非递减序列,如果Ak+1大于或等于前一个区间的中位数,就保留Ak+1作为单独一个区间;否则,需要将Ak+1和前一个区间合并。

因为涉及区间合并,很容易想到本节介绍的可合并堆—左倾堆。如何用左倾堆维护区间中位数呢?只需要用大顶堆维护区间较小的一半元素,这样堆顶元素就是中位数。在每次从左向右求解时,可把每一个数单独建一个左倾堆,如果当前区间的中位数小于前一个区间的中位数时,就将两个左倾堆合并,并弹出堆顶多余的元素。

题目实现: