📜  弱堆

📅  最后修改于: 2021-04-17 08:25:55             🧑  作者: Mango

它是具有以下属性的二叉树:
(1)节点右子树中的每个密钥都大于存储在节点本身中的密钥,
(2)根没有左子,并且
(3)仅在树的最后两个级别上找到叶子。

它用于实现优先级队列。用户可能更喜欢弱堆而不是二进制堆的原因是,在最坏的情况下,弱堆能够执行较少的元素比较。

基于弱堆的基于数组的实现的插入和删除的最坏情况下的时间复杂度是:
1.插入:O(lg n)
2.删除:O(lg n)

弱堆构造使用支持恒定时间插入的缓冲区。只要缓冲区大小小于阈值,就会将新元素插入缓冲区。一旦缓冲区已满,缓冲区的所有元素都将移至弱堆。

松散的堆是通过放松二进制堆的要求而获得的。为了表示内存中的弱堆,使用了两个数组。第一个是元素数组a,另一个是反向位数组r。

我们用a_i 引用数组a的索引i处的元素或引用相应树结构中的节点。构造一个弱堆,以便a_i ,其左子项的索引为2_i + r_i ,其正确子项的索引为2_i + 1 $-$ r_i ,并且(其中i!= 0)其父级的索引为 \lfloor\dfrac{i}{2}\rfloor

弱堆示例:
如果给定10个整数作为输入,例如8、7、4、5、2、6、9、3、11、1,则由以下输入构成的弱堆将如下图所示。

弱堆上的操作以及使用这些操作如何实现所需的时间复杂性
基本的弱堆操作以及伪代码如下:

1.杰出祖先:  a_j ,j!= 0,是的父级 a_j 如果 a_j 是一个合适的孩子,并且是父母的尊贵祖先 a_j 如果 a_j 是一个左孩子。我们使用d祖先(j)表示该祖先的索引。弱堆排序要求所有元素都不比其祖先的元素小。

// Finding the distinguished ancestor in a weak heap procedure: d-ancestor input: j: index while (j & 1) =  r_\lfloor\dfrac{j}{2}\rfloor   j \leftarrow \lfloor\dfrac{j}{2}\rfloor return  \lfloor\dfrac{j}{2}\rfloor

2. Join:子例程将两个弱堆组合为一个弱堆,条件是以下设置。连接需要O(1)时间,因为它涉及一个元素比较。

// Joining two weak heaps procedure: join input: i, j: indices if (  a_j < a_i ){ swap(  a_i, a_j ) r_j \leftarrow 1 $-$ r_j return false } return true

3.构造:可以使用以下方法构造大小为n的弱堆 n$-$1 通过执行元素比较 n$-$ 1调用join子例程。

//Constructing a weak heap procedure: construct input: a: array of n elements; r: array of n bits for i \in {0, 1, . . ., n  $-$ 1}  r_i \leftarrow 0 for j \in {n  $-$ 1, n  $-$ 2, . . ., 1}  i \leftarrow d$-$ancestor(j) join(i, j)

4.筛选:子例程sift-up(j)用于在元素e(最初在位置j)和元素祖先的元素之间重新建立弱堆顺序。  a_j 。从位置j开始,而e不在根处并且小于其可分辨祖先的元素,我们交换两个元素,翻转先前包含e的节点的位,然后从e的新位置重复。

// reestablishing the weak-heap ordering // on the path from  a_j upwards procedure: sift-up input: j: index while (j != 0){  i \leftarrow d$-$ancestor(j) if join(i, j){ break }  j \leftarrow i }

5.插入:要插入元素e,我们首先将e添加到下一个可用的数组条目中,使其成为堆中的叶子。如果此叶子是其父级的唯一子级,则通过更新父级的反向位使其成为左子级。为了重新建立弱堆排序,我们从e的位置开始调用sift-up子例程。因此,插入需要O(lg n)时间,并且最多涉及 \lceil lg   n\rceil  元素比较。

// Inserting an element into a weak heap. procedure: insert input: a: array of n elements; r: array of n bits; e: element  a_n \leftarrow e  r_n \leftarrow 0 if( (n & 1) = 0){ r_\lfloor\dfrac{n}{2}\rfloor  \leftarrow 0 } sift-up(n) ++n

6. Sift-down:子例程sift-down(j)用于在位置j的元素和右侧子树中的元素之间重新建立弱堆顺序。  a_j 。从正确的孩子开始 a_j ,是的右子树的左主干上的最后一个节点 a_j 被识别;这是通过反复访问左子节点直到到达没有左子节点的节点来完成的。从该节点到右孩子的路径 a_j 向上遍历,并且在之间重复执行连接操作 a_j 以及沿着这条路径的节点。每次联接之后,位置j处的元素都小于或等于在下一次联接中考虑的节点的左子树中的每个元素。

7. delete-min:要执行delete-min,将存储在弱堆根目录中的元素替换为存储在最后一个占用的数组条目中的元素。要恢复弱堆排序,将对新根调用筛选。因此,delete-min需要O(lg n)时间,并且最多涉及lg n个元素比较。

弱堆与二元堆和二项式堆的关系

弱堆的结构与二叉树排列相同。完美存储的完美弱堆 $2^r$ elements是等级为r的按堆排序的二叉树的二叉树表示。节点k在索引2k处有一个左子节点,在索引2k +1中有一个右子节点,假设根在索引0处(在二进制堆中,左子节点为2k + 1,右子节点为2k + 2)。 。唯一的区别是,弱堆的根没有左子代,只有右子代存储在索引2 * 0 + 1 = 1中。

同样,弱堆的结构与二项式堆非常相似,高度为h的树由根加上高度为h – 1,h – 2,…,1的树组成。像二项式堆一样,弱堆的基本运算堆将高度相等的两个堆合并为一个高度为h + 1的弱堆。这需要在根之间进行一个精确的比较。最终的根是哪个根更大(假定最大堆)。最终根的第一个子节点是丢失的根,保留其子节点(右子树)。获胜根的孩子将作为败根的兄弟姐妹插入。

弱堆的显着特性是:
1)它可能是不完美的(与二叉树相反);
2)它是一棵树(与二项式队列相反,后者是完美树的集合);
3)它是相当平衡的。

弱堆的应用
1.它可用作有效构建二进制堆的中间步骤。
2.弱堆变体(允许某些鼻子不遵守弱堆排序)用于图形搜索和网络优化,并且已知比斐波那契堆更好。