Является ли libС++ реализация `std:: make_heap` несоответствующей

Изменить: это не спрашивает, как сделать std::make_heap путь O (n), а не является ли эта конкретная реализация действительно O (n)

Учебный способ построения кучи в O (n) времени состоит в том, чтобы последовательно строить кучу снизу вверх. Но реализация std::make_heap на моей машине Mac в libС++ - это

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)? Я прав, что это ошибка?

Ответы

Ответ 1

Это действительно несоответствующая реализация O (n log n).

Сравнивая его с "sift up" версией heapify из статьи Википедии о heapsort, показывает, что это по существу тот же алгоритм. Тестирование его при увеличении целых последовательностей (в худшем случае) дает время выполнения, которое прекрасно соответствует кривой n log n, а количество необходимых сравнений превышает стандартную величину 3n даже для малых n.

Хотя в среднем алгоритм хорошо работает в пределе 3n, стандарт указывает на производительность наихудшего случая, а не на среднюю.

Ответ 2

Я считаю, что дискуссия здесь, похоже, сошла на касательную.

Ответ на вопрос: Нет; Реализация libС++ std::make_heap соответствует требованиям, предъявляемым к стандарту С++ для этой процедуры.

Цитата из стандарта С++ 11 (для этого следующий вариант для С++ 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.

Единственное требование сложности - это количество вызовов оператора сравнения. Я провел несколько тестов и пришел к выводу, что реализация libС++ удовлетворяет этому требованию. Я получаю сравнения 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, вы заявляете иное; Я был бы рад видеть ваши тестовые примеры. Мои тесты проводились с массивами различных размеров ints, которые были перетасованы с помощью std::random_shuffle.

Вопрос, связанный с @WhozCraig, предполагает, что этот алгоритм может быть реализован с использованием значительно меньших сравнений 3N. Я добавил эту статью в мой (к сожалению, длинный) список для дальнейшего изучения и возможного улучшения реализации libС++ make_heap. (Спасибо!)