gpt4 book ai didi

c++ - 如何在最多进行 3N 次比较时实现 std::make_heap ?

转载 作者:IT老高 更新时间:2023-10-28 12:46:35 29 4
gpt4 key购买 nike

我查看了 C++0x 标准,发现 make_heap 的比较次数不应超过 3*N。

IE。 heapify 无序集合可以在 O(N) 中完成

   /*  @brief  Construct a heap over a range using comparison functor.

为什么是这样?

来源没有给我任何线索(g++ 4.4.3)

while (true) + __parent == 0 不是线索,而是对 O(N) 行为的猜测
template<typename _RandomAccessIterator, typename _Compare>
void
make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
_Compare __comp)
{

const _DistanceType __len = __last - __first;
_DistanceType __parent = (__len - 2) / 2;
while (true)
{
_ValueType __value = _GLIBCXX_MOVE(*(__first + __parent));
std::__adjust_heap(__first, __parent, __len, _GLIBCXX_MOVE(__value),
__comp);
if (__parent == 0)
return;
__parent--;
}
}

__adjust_heap 看起来像一个 log N 方法:
while ( __secondChild < (__len - 1) / 2)
{
__secondChild = 2 * (__secondChild + 1);

对我来说是沼泽标准日志 N。
  template<typename _RandomAccessIterator, typename _Distance,
typename _Tp, typename _Compare>
void
__adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,
_Distance __len, _Tp __value, _Compare __comp)
{
const _Distance __topIndex = __holeIndex;
_Distance __secondChild = __holeIndex;
while (__secondChild < (__len - 1) / 2)
{
__secondChild = 2 * (__secondChild + 1);
if (__comp(*(__first + __secondChild),
*(__first + (__secondChild - 1))))
__secondChild--;
*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __secondChild));
__holeIndex = __secondChild;
}
if ((__len & 1) == 0 && __secondChild == (__len - 2) / 2)
{
__secondChild = 2 * (__secondChild + 1);
*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first
+ (__secondChild - 1)));
__holeIndex = __secondChild - 1;
}
std::__push_heap(__first, __holeIndex, __topIndex,
_GLIBCXX_MOVE(__value), __comp);
}

任何关于为什么这是 O <= 3N 的线索将不胜感激。
编辑:

实验结果:

这个实际的实现使用
  • <2N 堆堆比较
  • <1.5N 用于以相反的顺序堆放堆。
  • 最佳答案

    使用巧妙的算法和巧妙的分析,可以在 O(n) 时间内创建一个包含 n 个元素的二进制堆。在接下来的内容中,我将讨论假设您有显式节点和显式左右子指针的情况下它是如何工作的,但是一旦将其压缩为数组,这种分析仍然完全有效。

    该算法的工作原理如下。首先取大约一半的节点并将它们视为单例最大堆 - 因为只有一个元素,所以只包含该元素的树必须自动成为最大堆。现在,拿走这些树并将它们配对。对于每对树,取其中一个您尚未使用的值并执行以下算法:

  • 使新节点成为堆的根,使其左右子指针指向两个最大堆。
  • 虽然此节点有一个比它大的子节点,但将子节点与其较大的子节点交换。

  • 我的主张是,这个过程最终会产生一个新的最大堆,其中包含两个输入最大堆的元素,并且它在时间 O(h) 中这样做,其中 h 是两个堆的高度。证明是对堆高度的归纳。作为基本情况,如果子堆的大小为零,则算法立即以单例最大堆终止,并且在 O(1) 时间内完成。对于归纳步​​骤,假设对于某些 h,此过程适用于大小为 h 的任何子堆,并考虑在大小为 h + 1 的两个堆上执行它时会发生什么。 h+1,有三种可能:
  • 新的根大于两个子树的根。然后在这种情况下,我们有一个新的最大堆,因为根大于任一子树中的任何节点(通过传递性)
  • 新的根比一个 child 大,比另一个小。然后我们将根与较大的子子交换,并再次递归执行此过程,使用旧根和子树的两个子树,每个子树的高度为 h。根据归纳假设,这意味着我们交换的子树现在是一个最大堆。因此整个堆是一个最大堆,因为新的根比我们交换的子树中的所有东西都大(因为它比我们添加的节点大并且已经比该子树中的所有东西都大),而且它也比所有东西都大在另一个子树中(因为它比根大而且根比另一个子树中的所有东西都大)。
  • 新的根比它的两个 child 都小。然后使用对上述分析稍加修改的版本,我们可以证明生成的树确实是一个堆。

  • 此外,由于在每一步子堆的高度都减少 1,因此该算法的总运行时间必须为 O(h)。

    此时,我们有了一个简单的堆算法:
  • 取大约一半的节点并创建单例堆。 (您可以明确计算此处需要多少个节点,但大约是一半)。
  • 将这些堆配对,然后使用未使用的节点之一和上述过程将它们合并在一起。
  • 重复步骤 2,直到只剩下一个堆。

  • 因为在每一步我们都知道到目前为止我们拥有的堆是有效的最大堆,最终这会产生一个有效的整体最大堆。如果我们聪明地选择要创建多少个单例堆,这最终也将创建一个完整的二叉树。

    然而,这似乎应该在 O(n lg n) 时间内运行,因为我们进行 O(n) 合并,每个合并都在 O(h) 中运行,在最坏的情况下,我们正在合并的树的高度是 O(lg n)。但是这个界限并不严格,我们可以通过更精确的分析来做得更好。

    特别是,让我们考虑一下我们合并的所有树有多深。大约一半的堆深度为 0,剩下的一半深度为 1,剩下的一半深度为 2,依此类推。如果我们总结一下,我们得到总和

    0 * n/2 + 1 * n/4 + 2 * n/8 + ... + nk/(2k) = Σk = 0⌈log n⌉ (nk / 2k) = n Σk = 0⌈log n⌉ (k / 2k+1)



    这是交换次数的上限。每次交换最多需要两次比较。因此,如果我们将上述总和乘以 2,我们将得到以下总和,这是交换次数的上限:

    n Σk = 0 (k / 2k)



    这里的总和是总和 0/20 + 1/21 + 2/22 + 3/23 + ... 。这是一个著名的总结,可以用多种不同的方式进行评估。给出了一种评估方法 in these lecture slides, slides 45-47 .它最终正好是 2n,这意味着最终进行的比较次数肯定以 3n 为界。

    希望这可以帮助!

    关于c++ - 如何在最多进行 3N 次比较时实现 std::make_heap ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6299859/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com