Рассмотрите алгоритм быстрой сортировки с выбором опорного элемента «случайный»; проведите анализ среднего и худшего времени работы, объясните влияние выбора опоры и повторяющихся ключей, предложите адаптации для устойчивой производительности

10 Окт в 13:31
5 +1
0
Ответы
1

Ниже — компактный, но полный разбор быстрой сортировки с выбором опорного элемента «случайный», с анализом среднего и худшего времени, эффектом выбора опоры и повторяющихся ключей, а также практическими адаптациями для надёжной производительности.

1) Короткое описание алгоритма

Алгоритм: выбрать случайный элемент pivot обычнослучайныйиндексобычно случайный индексобычнослучайныйиндекс, выполнить разбиение partitionpartitionpartition массива на элементы < pivot и > pivot и,приобычнойреализации,равныемогутидтивлюбуюизчастейи, при обычной реализации, равные могут идти в любую из частейи,приобычнойреализации,равныемогутидтивлюбуюизчастей, затем рекурсивно сортировать части.Характеристики: сравнение-ориентированный, нестабильный в базовом варианте, in-place всреднемO(logn)дополнительнойпамятинастекрекурсиив среднем O(log n) дополнительной памяти на стек рекурсиивсреднемO(logn)дополнительнойпамятинастекрекурсии.

2) Среднее время работы рандомизированныйpivotрандомизированный pivotрандомизированныйpivot

Теоретический результат: при выборе pivot равновероятно из текущего подмассива ожидаемое число сравнений — Θnlognn log nnlogn. Более точная асимптотика числа сравнений:
E[#сравнений] = 2 n ln n + Onnn натуральныйлогарифмнатуральный логарифмнатуральныйлогарифм. Это ~1.386 n log2 n + Onnn.Следовательно, ожидаемое время работы Θnlognn log nnlogn. Константы хорошие, поэтому на практике быстрая сортировка очень эффективна.

3) Худшее время работы

В худшем случае систематическиоченьнеудачныеразбиения:одинподмассивразмерn−1,другой0систематически очень неудачные разбиения: один подмассив размер n-1, другой 0систематическиоченьнеудачныеразбиения:одинподмассивразмерn1,другой0 сложность — Θn2n^2n2 примерноn(n−1)/2сравненийпримерно n(n-1)/2 сравненийпримерноn(n1)/2сравнений.Для детерминированного выбора опоры например,всегдапервый/последнийэлементнапример, всегда первый/последний элементнапример,всегдапервый/последнийэлемент вход, уже отсортированный или обратно отсортированный, даёт худший случай.При настоящем случайном выборе опоры вероятность получить последовательность крайне неудачных разбиений на каждом шаге экспоненциально мала. То есть рандомизированный алгоритм имеет квадратичное время, но с очень малой вероятностью; с высокой вероятностью w.h.p.w.h.p.w.h.p. время — Θnlognn log nnlogn.

4) Влияние выбора опоры

Случайный pivot:
защищает от злонамеренных / специфичных входов нетвозможностизаранееподобратьвход,приводящийвсегдакхудшемуслучаюнет возможности заранее подобрать вход, приводящий всегда к худшему случаюнетвозможностизаранееподобратьвход,приводящийвсегдакхудшемуслучаю;даёт хорошее среднее поведение и низкую среднюю глубину рекурсии O(logn)всреднемO(log n) в среднемO(logn)всреднем.Медиана по трём median−of−threemedian-of-threemedianofthree:
берётся медиана из трёх элементов например,первый,средний,последнийнапример, первый, средний, последнийнапример,первый,средний,последний — уменьшает шанс плохих разбиений на «почти отсортированных» входах, снижает константу.всё ещё не обеспечивает гарантий — могут существовать входы, которые обманывают этот выбор.Дет. выбор медианы median−of−mediansmedian-of-mediansmedianofmedians:
даёт детерминированную выборку приблизительной медианы за Onnn и позволяет обеспечить худший случай Θnlognn log nnlogn при каждой рекурсии, но накладные расходы значительны; редко используется в практической сортировке из-за больших констант.

5) Влияние повторяющихся ключей

Проблема: при большом числе равных ключей двухчастное partition (разбить на < и >= либо <= и >) часто создаёт очень несимметричные разбиения, т.к. равные элементы попадают в одну из сторон, что может привести к ухудшению производительности вплотьдоквадратичнойвплоть до квадратичнойвплотьдоквадратичной.Решение: трёхпутевая 3−way3-way3way сортировка DutchnationalflagDutch national flagDutchnationalflag:
Разбивает на три части: < pivot, = pivot, > pivot. Затем рекурсивно сортирует только меньшую и большую части.При многих равных ключах эффективно — если все элементы одинаковы, алгоритм работает за Θnnn проходитпоэлементамодинразпроходит по элементам один разпроходитпоэлементамодинраз.Рекомендуется всегда использовать 3-way-партиционирование, если ожидаются повторяющиеся ключи.

6) Практические адаптации для устойчивой производительности
Рекомендуемый гибрид и инженерные приёмы частоприменяемыевбиблиотечныхреализацияхчасто применяемые в библиотечных реализацияхчастоприменяемыевбиблиотечныхреализациях:

Рандомизация

Выбирать pivot как случайный элемент изтекущегоподмассиваиз текущего подмассиваизтекущегоподмассива. Это простая и эффективная защита от худших случаев на специально подобранных входах.

Трёхпутевая партиция

Использовать Dijkstra 3-way partition, если возможны повторяющиеся ключи — это существенно улучшает поведение при дубликатах.

Интроспективная сортировка introsortintrosortintrosort

Запускать quicksort с рандомным pivot или median-of-three; если глубина рекурсии превышает c·log n например,2⋅⌊log2n⌋например, 2·⌊log2 n⌋например,2log2n, переключаться на heapsort. Это даёт гарантированный Onlognn log nnlogn худший случай и при этом сохраняет хорошие средние константы quicksort.Этот подход используется в многих стандартных реализациях например,вlibstdc++/std::sort—снекоторымивариацияминапример, в libstdc++/std::sort — с некоторыми вариацияминапример,вlibstdc++/std::sortснекоторымивариациями.

Порог для малых подмассивов

Для маленьких подмассивов обычноразмер≤10–32обычно размер ≤ 10–32обычноразмер10–32 лучше применять insertion sort — у него низкие константы и высокая локальность памяти.Типичное сочетание: quicksort + insertion sort как финальный шаг.

Выбор опоры: median-of-three

Для невраждебных входов median-of-three часто даёт лучшую практическую константу, чем полностью случайный pivot меньшесравненийменьше сравненийменьшесравнений, но не защищает от всех паттернов.

Хвостовая оптимизация и итеративная реализация

Рекурсию заменить на итерацию для одной стороны tailrecursioneliminationtail recursion eliminationtailrecursionelimination и всегда рекурсивно обрабатывать меньшую часть — гарантирует Olognlog nlogn глубину стека в среднем, снижает риск переполнения стека.

Стабильность

Стандартный inplace quicksort нестабилен. Для стабильности нужно дополнительная память например,stablequicksort/mergesortгибридынапример, stable quicksort/mergesort гибридынапример,stablequicksort/mergesortгибриды или сортировка пар ключ,исходныйиндексключ, исходный индексключ,исходныйиндекс.

Параллелизм и кеширование

Блоковая партиция и внимание к локальности данных улучшают скорость на современных кэшах. Для больших данных — распараллеливание рекурсивных вызовов.

7) Конкретная рекомендуемая конфигурация для практических реализаций

Использовать рандомный pivot или median-of-three.Делать 3-way partition при возможных дубликатах.Обрабатывать меньшую часть рекурсивно, большую итеративно tailcalltail calltailcall.Если глубина рекурсии > c·log n, переключаться на heapsort introsortintrosortintrosort.Для подмассивов размера ≤ 16 — insertion sort.При необходимости стабильности — использовать stable sort merge−basedmerge-basedmergebased либо stable quicksort с доп. памятью.

8) Итоги по сложностям

Ожидаемое время: Θnlognn log nnlogn (E[#сравнений] ≈ 2 n ln n + O(n)).Худшее время: Θn2n^2n2 для классического quicksort; при использовании introsort — гарантированно Θnlognn log nnlogn.Память: in-place, дополнительная стек-память Θlognlog nlogn в среднем; в худшем случае Onnn еслинеприменятьtail−callизащитуесли не применять tail-call и защитуеслинеприменятьtailcallизащиту.Поведение при дубликатах: без 3-way — может ухудшаться; с 3-way — быстрое влучшемслучаеΘ(n)привсехравныхв лучшем случае Θ(n) при всех равныхвлучшемслучаеΘ(n)привсехравных.

9) Короткие псевдокоды схемасхемасхема

Рандомизированный quicksort двухчастныйpartitionдвухчастный partitionдвухчастныйpartition:

if n ≤ threshold: insertion_sortpivot_index = randomlo..hilo..hilo..hi; swapa[lo],a[pivotindex]a[lo], a[pivot_index]a[lo],a[pivoti ndex]p = partitiona,lo,hia, lo, hia,lo,hi // возвращает границуrecurse left, right обрабатыватьменьшуючастьрекурсивнообрабатывать меньшую часть рекурсивнообрабатыватьменьшуючастьрекурсивно

Трёхпутевая партиция DutchflagDutch flagDutchflag:

lt = lo; i = lo; gt = hi;pivot = arandom(lo..hi)random(lo..hi)random(lo..hi);while i ≤ gt:
if aiii < pivot: swapa[lt],a[i]a[lt], a[i]a[lt],a[i]; lt++; i++;
else if aiii > pivot: swapa[i],a[gt]a[i], a[gt]a[i],a[gt]; gt--;
else: i++;recurse on lo..lt−1lo..lt-1lo..lt1 and gt+1..higt+1..higt+1..hi

10) Небольшие замечания по реализации

Источник случайности: можно использовать xorshift/LCG для скорости; при желании воспроизводимости — фиксированный seed.Для числовых типов и простых структур median-of-three бывает быстрее на практике; для сложных ключей random+3-way чаще предпочтительнее.Профилируйте для конкретных данных — в реальности «лучший» набор оптимизаций зависит от распределения ключей и требований память,стабильность,кэшпамять, стабильность, кэшпамять,стабильность,кэш.

Если хотите, могу:

Привести пример кода C/C++/PythonC/C++/PythonC/C++/Python для варианта: randomized quicksort + 3-way partition + introsort-переход + insertion sort для мелких массивов.Сравнить по числам сравнений и времени несколько конфигураций на различных распределениях данных равномернослучайные,ужеотсортированные,сдубликатамиравномерно случайные, уже отсортированные, с дубликатамиравномернослучайные,ужеотсортированные,сдубликатами.
10 Окт в 13:51
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир