Является ли 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
. (Спасибо!)