быстрая сортировка итератор требования

tl; dr:можно ли эффективно реализовать quicksort в дважды связанном списке? Мое понимание, прежде чем думать об этом, было, нет, это не так.

на днях у меня была возможность рассмотреть требования итератора для основных алгоритмов сортировки. Основные O (N2) довольно просты.

  • Bubble sort - 2 вперед итераторы будет делать хорошо, один перетаскивание за другим.
  • сортировка вставками - 2 двунаправленные итераторы будут делать. Один для элемента вне порядка, один для точки вставки.
  • сортировка выбора-немного сложнее, но вперед итераторы могут сделать трюк.

Quicksort

introsort_loop в std:: sort (как в стандартной библиотеке gnu/ hp(1994) / silicon graphics(1996) ) требует, чтобы это был random_access.

__introsort_loop(_RandomAccessIterator __first,
         _RandomAccessIterator __last,
         _Size __depth_limit, _Compare __comp)

как я и ожидал.

теперь ближе осмотр я не могу найти реальную причину требовать этого для quicksort. Единственное, что явно требует random_access_iterators-это std::__median вызов, требующий вычисления среднего элемента. Обычный, ванильный quicksort не вычислить медиану.

разделение состоит из проверки

 if (!(__first < __last))
    return __first;

не очень полезно проверить bidirectionals. Однако следует иметь возможность заменить это с проверкой в предыдущем разбиении путешествия (слева направо/ справа налево) с простым условием

if ( __first == __last ) this_partitioning_is_done = true;

можно ли реализовать quicksort достаточно эффективно, используя только двунаправленные итераторы? Рекурсивная глубина все еще может быть защищена.

NB. Я до сих пор не предпринял реальной реализации.

3 ответов


tl; dr: да

как вы говорите, проблема заключается в том, чтобы найти элемент pivot, который является элементом в середине, поиск этого с произвольным доступом занимает O(1), поиск его с двунаправленными итераторами занимает O(n) (n/2 операции, чтобы быть точным). Однако на каждом шаге вы должны создать суб-контейнеры, левый и правый содержащие меньшее и большее число соответственно. Именно здесь происходит основная работа быстрого рода, не так ли?

Теперь, когда построение субконтейнеров (для шага рекурсии) мой подход состоял бы в создании итератора h указывая на их передний элемент. Теперь всякий раз, когда вы выбрать следующий элемент, чтобы перейти к суб контейнер, просто заранее h каждый второй раз. Это будет иметь h укажите на элемент pivot, как только вы будете готовы спуститься на новый шаг рекурсии.

вам нужно только найти первый поворот, который на самом деле не имеет значения, потому что O (N log n + n / 2) = O (N log северный.)

на самом деле это просто оптимизация времени выполнения, но не влияет на сложность, потому что, повторяете ли Вы список один раз (чтобы поместить каждое значение в соответствующий подконтейнер) или два раза (чтобы найти ось, а затем поместить каждое значение в соответствующий подконтейнер), все равно: O(2n) = O(n).
Это просто вопрос времени выполнения (а не сложности).


вам нужны итераторы произвольного доступа, потому что вы обычно хотите выбрать элемент pivot из середины списка. Если вы выберете первый или последний элемент в качестве pivot вместо этого, достаточно двунаправленных итераторов, но затем Quicksort вырождается в O(n^2) для предварительно отсортированных списков.


нет абсолютно никаких проблем с реализацией стратегии быстрого отсортировать двусвязный список. (Я думаю, что его также можно легко адаптировать к одиночному списку). Единственное место в традиционном алгоритме быстрой сортировки, которое зависит от требования случайного доступа,-это фаза настройки, которая использует что-то "хитрое" для выбора элемента pivot. На самом деле все эти "трюки" - не что иное, как эвристика, которую можно заменить практически одинаково эффективными последовательными методы.

я реализовал быструю сортировку для связанных списков раньше. В этом нет ничего особенного, вам просто нужно обратить пристальное внимание на правильный элемент, связывающий. Как вы, вероятно, понимаете, большая часть значения алгоритмов сортировки списков исходит из того, что вы можете переупорядочить элементы по relinking, вместо явной замены значений. Не только это может быть быстрее ,но и (и часто - что более важно) сохраняет ценность-валидность внешнего ссылки, которые могут быть приложены к элементам списка.

P.S. однако я бы сказал, что для связанных списков алгоритм сортировки слиянием приводит к значительно более элегантной реализации, которая имеет одинаково хорошую производительность (если вы не имеете дело с некоторыми случаями, которые лучше работают с быстрой сортировкой).