为什么heapify将堆顶部与堆底部的元素交换?

时间:2012-07-14 23:38:26

标签: algorithm heapsort

在最大堆中(假设它由数组表示),堆的顶部(即堆中的最大值)与数组中的最后一个元素交换(即堆中最小值之一) ),删除最后一个元素,然后新的top-of-the-heap元素与其他值交换以重新回到适当的位置。

相反,为什么不删除顶部元素,然后其他元素可以“填写”堆?

4 个答案:

答案 0 :(得分:6)

堆的一个关键属性是底层二进制树是完整二叉树(即除了最后一个之外的每个级别必须完全“填充”)。这使得堆具有O(lg N)个操作,因为我们只需在每个O(lg N)级别修改一个元素。我们来看一个例子

    10
   /  \
  8    7
 / \  / \
5  6  4  3

如果我们按照您的方法并“填写”我们得到的堆

     8
   /   \
  6     7
 / \   / \
5  ?   4  3

树不再是完整的二叉树,因为?处有一个“洞”。由于我们不知道树是完整的,因此我们对树的高度一无所知,因此我们无法保证O(lg N)操作。

这就是为什么我们将堆中的最后一个元素放在顶部然后将其混乱 - 以维护完整的二叉树属性。

答案 1 :(得分:4)

  

为什么不删除顶部元素,然后其他元素可以“填充”堆?

原因是元素的索引在维护堆结构方面起着重要作用。索引为i的元素的两个子元素位于索引2*i+12*i+2。如果你“只删除”top元素,你就不会得到另一个堆:索引12将不再包含max元素的子元素,因为{ {1}}元素将不再存在。从某种意义上说,你最终会得到两个“破碎”的堆,而不是一个正常工作的堆。您必须替换索引为零的值,否则其余元素中的索引方案将会中断。

虽然从顶部删除元素不会被忽视,但删除底部的元素是正常的:您需要做的就是记下最小元素是max而不是{{1} }。因此,操作顺序如下:

  • 删除可以安全移除的元素
  • 将其替换为无法安全移除的元素
  • 将元素渗透到堆中直至其沉降,在每一步中选择其中两个父母中较高的一个

答案 2 :(得分:1)

堆算法的整个想法是,您始终维护一个完整的元素树(由数组表示)。如果您从树的根部删除了某些内容,则必须在其中放入其他内容。在数组中,实现这一目标的最有效方法是将最后一个元素移动到那里。

您的关注似乎是基于数组中的最后一个元素(树中的叶元素)是最小元素的假设。这是不正确的。堆数组未完全排序。堆在每个子树中都有一个“垂直”排序,但在子树之间没有“水平”排序。数组中的最后一个元素肯定是从根到该叶子的唯一路径中最小的元素,但在一般情况下,它将在整个堆中最小。

当您查看大小为N的堆的任何叶元素时,您当然可以说它不是整个堆中log N 最大元素之一。但这就是你所能说的。例如,如果您的树中有256个元素,那么数组中的最后一个元素(或任何其他叶元素)将排在第9到第256之间。看到?它可能是256中的第9位!把这样的元素称为“最小的”简直是荒谬的。平均而言,它不仅不是最小的,它甚至不是最小的。

同样,最后一个元素是专门选择的,因为它是维持连续数组的最便宜方式。如果您以其他方式实现堆,比如通过链接树而不是数组,那么在删除根之后恢复堆的最佳方式可能会有所不同。

答案 3 :(得分:1)

从概念上讲,你提出的建议会很好。堆的抽象定义允许将最顶层的元素移除另一个“sift-up”。

实际上,公共堆实现通过使用连续指针数组来模拟树(当元素 n 的父元素位于 n / 2 位置时)。在这个实现中,在指针数组中留下“漏洞”是不方便的。

解决这个问题的“诀窍”是交换最后一个元素并用“大幅下降”步骤重新定位它。这确保了所有连续的数组元素都是树的一部分,并且序列中没有空洞。这使得算法更容易实现,并节省了链接字段所需的空间。

执行摘要:它只是一个实现细节(非常方便且非常常见)。