我理解(或者至少我认为我这样做)heapsort。我正在看一本我有算法的书,遇到了这个我无法理解的问题。他们正在使用最大堆:
LARGE 是要排序的数字的较大一半。 I.E. LARGE = {n / 2,n / 2 + 1,... n}
最初,在HeapSort的构建堆阶段之后, LARGE 中有多少项可以位于堆的底层?
最初,在HeapSort的构建堆阶段之后, LARGE 中有多少项可以在堆的顶级(log n)/ 4级内?
设X为 LARGE 中的项目,这些项目最初既不是最低级别也不是最高级别(log n)/ 4级别。 X中至少有多少项?
构建堆后,你不应该堆积吗?哪个会把所有较大的元素移到树上?那么LARGE中的所有元素都不会在底层正确吗?也许他们在构建堆之后和heapify之前讨论,这意味着最多一半的LARGE元素可能在堆的底层?我主要是在寻找对这个问题的任何澄清。
对编辑感到抱歉,我不确定是否允许根据我剩下的问题提出新问题。
答案 0 :(得分:1)
[编辑:好吧,你似乎已经删除了我的答案所回答的部分问题!希望它仍然有用。]
pjs的评论是一个很好的具体例子,可以帮助您入门。为随后的文本墙道歉;或许这是一种分析问题的简单方法,但我不知道!
更一般地说,我们可以通过假设从LARGE中的某些元素处于底层,然后查看它具有什么含义来数学处理。如果这些影响产生矛盾,那么所谓的事情就不可能发生;但如果他们不这样做,那么我们发现的含义可能会为我们提供一种构建实例的方法。 (或者它可能意味着我们看起来并没有足够的矛盾;但在这种情况下,由于pjs的例子,我们已经知道他们不能产生一个。但是,做出假设如果没有立即想到具体的例子,那么寻找矛盾是一个很好的开始。)
因此,让我们假设LARGE中的某个元素x是堆中的一个叶子。这意味着什么?换句话说,这会以什么方式限制堆的其余部分?首先,max-heap中的heap属性告诉我们父节点永远不能小于它的子节点。所以x的父y必须不小于x,而y的父z必须不小于y,依此类推直到根。从x到根的路径上有多少个节点?对于完整的堆(即,对于某些k,n = 2 ^ k-1个节点),将精确地具有log2(n + 1)-1,不包括x本身。 (对于非完整堆,它稍微复杂一些,但是我们不要在此担心。)因此,我们当然可以得出结论x不能是最大的log2(n + 1)之一 - 1个元素,因为如果是的话,就没有足够的其他更大的元素放在祖先的链中直到根。 (事实上,由于堆可以包含重复的元素,因此它(再次)稍微复杂一些。但是上面的内容适用于大量独特元素,考虑到这些因素并不会让人感到痛苦。最初。)
所以,让我们尝试将x(log2(n + 1))个最大的元素作为x,并将所有较大的元素放在其祖先链中。如果log2(n + 1)< n / 2,然后x将成为LARGE的成员:对于所有n> = 7,这肯定是正确的。(仅考虑完整堆,这个界限是紧的:对于n = 3,它显然不可能LARGE中的单个元素出现在除根处以外的任何位置。)是否可以继续将其余元素分配给此堆中的位置,直到它完成为止?是的!
图片x的祖先链为/
形状的链,其根部位于右上角,x位于左下角,即此链中的每个节点(x本身除外)都具有一个左孩子,但没有正确的孩子。我们需要将剩余的元素插入到来自那些正确的孩子的子树(实际上是子集)中。根的右子需要成为一个包含2 ^(k-1)-1个元素的完整子堆;根的左子的右子需要成为一个包含2 ^(k-2)-1个元素的完整子堆;等等。特别注意这些log2(n + 1)-1个子堆彼此完全独立:它们不以任何方式相互约束。因为没有剩余的元素比/
形状的链中的任何元素都大,所以我们不可能通过选择一个大于它的孩子来意外地违反max-heap属性。这条链上的父母,所以我们不必担心这一点。我们可以简单地取剩余元素的任何旧的2 ^(k-1)-1,从它们构建一个堆,并将其作为根的右子项;然后取出任何旧的2 ^(k-2)-1之后剩下的元素,从它们构建一个堆,并将其作为根的左子项的右子项;等等,直到我们在2 ^ k-1个元素上构建完整的堆。
如果至少有7个元素,那么在底部的LARGE构建一个带有一个元素的堆当然是可能的。但是LARGE中有多少元素可以适应那里,同时还能满足max-heap属性?我不会进入太多细节(部分原因是因为我还没有完全认识到自己!)但这里有一些想法:
假设我们想在底行有2 ^ r个LARGE元素。解决这个问题的一种方法是将它们全部留在高度-r子堆中,该子堆总共必须包含2 ^(r + 1)-1个元素,其中2 ^ r-1将是非叶子。 (2 ^ r叶子必须以某种方式排列成具有父母的2 ^(r-1)对,它们本身必须被排列成具有父母的2 ^(r-2)对,依此类推;总结元素数量在所有级别,直到根给出2 ^(r + 1)-1)。
此子堆中的节点当然必须遵守max-heap属性。保证这一点的一种简单方法是对所有2 ^(r + 1)-1个节点进行排序,取其中最小的2 ^ r,并将它们(以任何顺序)分配给叶子;然后取下它们的下一个最小的2 ^(r-1),并将它们(再次以任何顺序)分配给这些叶子的父母;等等。
我们仍然需要注意这个子堆的根需要小于链中的所有祖先,直到n个元素上的原始完整堆的根。它与之前的情况类似,只有一个LARGE叶子,但是我们的子堆的根是高于x的r级别,因此我们只需要log2(n + 1)-r-1祖先(而不是此链中的log2(n + 1)-1)。因此,为了使用这种结构将2 ^ r给定元素放入完整堆的底行,我们需要"机器"由2 ^ r-1个较大的元素组成,用于填充它们上面的子堆,加上log2(n + 1)-r-1甚至更大的元素,用于链到根,总共2 ^ r + log2 (n + 1)-r-2个较大的元素。由于我们希望尽可能多的这些2 ^ r元素在大型中,因此使用最大可能元素是有意义的:采用总体最大的2 ^ r + log2(n + 1)-r-2元素来用于机器,然后把下一个最大的2 ^ r元素作为叶子。如果r足够小,这些叶子中的全部或部分都将处于较大的范围内。 (注意,对于r = 0,我们恢复原始的单LARGE叶结构。)为了找出LARGE中有多少元素我们可以使用这种技术进入底行,继续增加r直到2 ^中的一个或多个r叶子不再大了;要么在这次迭代中,要么在前一次迭代中LARGE叶子的数量是最大的,因为进一步增加r只会导致在机器上花费更多的LARGE元素。
上面的方式构建一个堆m"非常大"底部的元素。它可能是也可能不是最佳方式,以尽可能多地在底部安装LARGE元素,如果是这样,这仍有待证明。它可能不是,即使在我们已经做出的各种简化假设(完整堆,不同元素)下,因为我们实际构造了满足更强约束的东西而不是堆所需的(特别是,在具有2 ^ r个元素作为其叶子的子堆中,我们的构造确保每个父级都大于每个叶子,即使这不是必需的一堆)。
事实上,我已经可以想到一个改进。当将2 ^(r + 1)-1个元素排列到子堆中,而不是将2 ^ r最小值分配为叶子时,我们可以通过执行以下操作来隐藏一些较大的叶子:取3个最小的元素,这些叶子中最小的2个,使它们的父母成为第3小。使用第4到第6个最小元素重复此过程以生成另一个3元素堆。现在将第7个最小的元素作为这2个3元素堆的父元素,依此类推。这样做效果更好,因为它将一些(至少2 ^ r / 3)的最小元素走私到子堆中,这意味着更大(和更多)的元素会使它进入2 ^ r元素底行。我怀疑它是最佳的,因为在任何完整的子堆中,最小的和第二小的元素都不能。