📜  弱堆

📅  最后修改于: 2021-10-28 01:56:54             🧑  作者: 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-ancestor (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调用连接子例程。

//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$ 元素是秩为 r 的堆序二叉树的二叉树表示。节点 k 的左子节点位于索引 2k 处,右子节点位于索引 2k + 1 处,假设根位于索引 0(在二进制堆中,左子节点为 2k+1,右子节点为 2k+2) .唯一不同的是,弱堆的根没有左孩子,只有右孩子,存储在索引2*0+1=1处。

此外,弱堆的结构与二项式堆非常相似,高度为 h 的树由根加上高度为 h – 1, h – 2, …, 1 的树组成。 heaps 正在合并两个高度为 h 的堆,形成一个高度为 h+1 的弱堆。这需要在根之间进行一次比较。哪个根更大(假设最大堆)是最终根。最后一个根的第一个孩子是丢失的根,它保留它的孩子(右子树)。获胜根的孩子被插入为失败根的兄弟姐妹。

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

弱堆的应用
1. 可以作为高效构建二元堆的中间步骤。
2. 弱堆变体,允许一些鼻子违反弱堆排序,用于图搜索和网络优化,并且被证明比斐波那契堆更好。

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程。