【问题标题】:Is libc++'s implementation of `std::make_heap` nonconformantlibc++ 的 `std::make_heap` 实现是否不符合
【发布时间】:2014-08-19 22:26:33
【问题描述】:

编辑:这不是问std::make_heap O(n) 的方式,而是问这个特定的实现是否确实是 O(n)

教科书式的O(n)时间建堆方法是从下往上依次建堆。但是std::make_heap在我的Mac机器上在libc++中的实现是

template <class _RandomAccessIterator, class _Compare>
inline _LIBCPP_INLINE_VISIBILITY
void
make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
{
#ifdef _LIBCPP_DEBUG
    typedef typename add_lvalue_reference<__debug_less<_Compare> >::type _Comp_ref;
    __debug_less<_Compare> __c(__comp);
    __make_heap<_Comp_ref>(__first, __last, __c);
#else  // _LIBCPP_DEBUG
    typedef typename add_lvalue_reference<_Compare>::type _Comp_ref;
    __make_heap<_Comp_ref>(__first, __last, __comp);
#endif  // _LIBCPP_DEBUG
}

其中__make_heap 定义为

template <class _Compare, class _RandomAccessIterator>
void
__make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
{
    typedef typename iterator_traits<_RandomAccessIterator>::difference_type difference_type;
    difference_type __n = __last - __first;
    if (__n > 1)
    {
        __last = __first;
        ++__last;
        for (difference_type __i = 1; __i < __n;)
            __push_heap_back<_Compare>(__first, ++__last, __comp, ++__i);
    }
}

template <class _Compare, class _RandomAccessIterator>
void
__push_heap_back(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp,
                 typename iterator_traits<_RandomAccessIterator>::difference_type __len)
{
    typedef typename iterator_traits<_RandomAccessIterator>::difference_type difference_type;
    typedef typename iterator_traits<_RandomAccessIterator>::value_type value_type;
    if (__len > 1)
    {
        __len = (__len - 2) / 2;
        _RandomAccessIterator __ptr = __first + __len;
        if (__comp(*__ptr, *--__last))
        {
            value_type __t(_VSTD::move(*__last));
            do
            {
                *__last = _VSTD::move(*__ptr);
                __last = __ptr;
                if (__len == 0)
                    break;
                __len = (__len - 1) / 2;
                __ptr = __first + __len;
            } while (__comp(*__ptr, __t));
            *__last = _VSTD::move(__t);
        }
    }
}

这不是简单地迭代插入到堆中,因此时间复杂度为 O(n log n)?这是一个错误,我说得对吗?

【问题讨论】:

  • See this answer。简而言之,该算法并不像最初看起来那么简单。
  • @WhozCraig:我知道 O(n) 算法。我在问这个特定的实现(libc++)是否无法使用正确的实现。
  • @WhozCraig:这绝不是一个重复的问题。我不是在问如何以 O(n) 的方式进行操作。相反,我问的是这个实现是否确实是 O(n)。
  • @Matthieu M.:请参阅我的新编辑,说明这不是重复的。
  • 我没有将其标记为重复。我仅为算法分析链接了该答案(这是准确的)。您的问题是关于该算法的具体实现,虽然相关,但 不是 重复(恕我直言)并且不应该被标记为这样(这就是为什么我 没有 这样做)。投票重新开放。

标签: c++ stl clang llvm libc++


【解决方案1】:

这确实是一个不符合标准的 O(n log n) 实现。

将其与来自Wikipedia article on heapsort 的 heapify 的“筛选”版本进行比较表明它本质上是相同的算法。在递增的整数序列(最坏的情况)上对其进行测试,得出的运行时间与n log n 曲线非常吻合,并且所需的比较次数超过了标准规定的3n 数字,即使对于小的n 也是如此。

虽然平均而言,该算法在 3n 限制内表现良好,但标准要求最坏情况下的性能,而不是平均性能。

【讨论】:

  • @C.R.感谢您提交错误。
  • +1 感谢您完成此操作。我总是忘记我的算法类和最坏的数据建模与标准的期望离子相比进入算法。均匀随机分布的平均成本似乎很合适,但在最坏的情况下,这肯定是它们实施中的一个问题。再次感谢。
  • David Majnemer 在修订版 213615 中修复
【解决方案2】:

我相信这里的讨论似乎已经切线了。

问题的答案是:不; libc++ 对 std::make_heap 的实现满足了 C++ 标准对该例程的要求。

引用 C++11 标准(即将推出的 C++14 标准似乎对此没有改变)

template<class RandomAccessIterator>
  void make_heap(RandomAccessIterator first, RandomAccessIterator last);
template<class RandomAccessIterator, class Compare>
  void make_heap(RandomAccessIterator first, RandomAccessIterator last, Compare comp);

* Effects: Constructs a heap out of the range [first,last).
* Requires: The type of *first shall satisfy the MoveConstructible requirements (Table 20) and the MoveAssignable requirements (Table 22).
* Complexity: At most 3 * (last - first) comparisons.

唯一的复杂性要求是对比较运算符的调用次数。 我进行了几次测试,得出的结论是 libc++ 的实现满足了这个要求。我得到了关于 2.3*N 操作的比较。我在https://llvm.org/svn/llvm-project/libcxx/trunk/test/algorithms/alg.sorting/alg.heap.operations/make.heap/make_heap_comp.pass.cpp 使用了测试。 @n.m,您另有主张;我很高兴看到您的测试用例。我的测试是使用 std::random_shuffle 打乱的各种大小的整数数组完成的。

@WhozCraig 链接到的问题表明,该算法可以使用明显少于3N 的比较来实现。我已将该文章添加到我的(可悲的是,很长的)阅读列表中,以供进一步研究和可能改进 libc++ 的 make_heap 实现。 (谢谢!)

【讨论】:

  • 请也出示您的测试,以便我自己验证。
  • "已经使用 std::random_shuffle 进行了洗牌"。这正是问题所在。该算法(可能)平均为O(n),但在最坏的情况下为O(n log n)。尝试升序(这是我的测试用例)。
  • @n.m.你说得对。我在一般评论中显示的相同测试代码在最坏情况与平均情况下的输出有显着不同。基于 ideone (seen here) 的 gcc 提供了您希望的数字。我的本地构建使用与 OP (output seen here) 相同的工具链和 lib 提供了截然不同的数字,轻松超过 3N 并迅速接近 O(NlogN)。你真的应该发布一个答案,这样我就可以升级它。谢谢你让我诚实。我在此同意这是一个错误的实现。
  • @n.m.我进一步测试了相同的工具链,只将使用的标准库从 libc++ 切换到 gnu 的 libstdc++,并且这个数字再次可观且与 ideone 帖子中的数字相当。再次感谢您将其保持在水面之上。
  • 这个答案与接受的答案相矛盾。您是否同意那里的评估,还是仍然声称 libc++ 满足标准强加的复杂性限制?
猜你喜欢
  • 1970-01-01
  • 2021-02-18
  • 2012-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-12
  • 2016-10-15
相关资源
最近更新 更多