Дан фрагмент на Python: def quicksort(a): if len(a)<=1: return a; pivot=a[len(a)//2]; left=[x for x in a if xpivot]; return quicksort(left)+middle+quicksort(right) — проанализируйте сложность по времени и памяти в среднем и в худшем случаях, обсудите стабильность и предложите практические улучшения (включая in-place варианты и выбор опорного элемента)

12 Окт в 14:17
4 +3
0
Ответы
1

Код, который вы привели, — классическая «питоновская» реализация quicksort в функциональном стиле:

def quicksortaaa:
if lenaaa <= 1:
return a
pivot = alen(a)//2len(a)//2len(a)//2 left = [x for x in a if x < pivot]
middle = xforxinaifx==pivotx for x in a if x == pivotxforxinaifx==pivot right = [x for x in a if x > pivot]
return quicksortleftleftleft + middle + quicksortrightrightright

1) Временная сложность

В среднем: Onlognn log nnlogn. При «хороших» разбиениях обычноглубинарекурсии≈O(logn)обычно глубина рекурсии ≈ O(log n)обычноглубинарекурсииO(logn) на каждом уровне делаем Onnn сравнений, уровней ≈ Olognlog nlogn.В худшем случае: On2n^2n2. Если опорный элемент постоянно даёт сильно несбалансированные разбиения например,одинподмассивразмерn−1,другой0например, один подмассив размер n−1, другой 0например,одинподмассивразмерn1,другой0, глубина ≈ n и суммарно ≈ 1+2+...+n сравнений → On2n^2n2.

2) Память auxiliary/peakauxiliary / peakauxiliary/peak

Текущая реализация создаёт новые списки left/middle/right на каждом вызове:
В среднем при«хороших»разбиенияхпри «хороших» разбиенияхпри«хороших»разбиениях: пик занимаемой дополнительной памяти — Onnn. Более точный подсчёт: на уровнях рекурсии суммарно живые элементы дают геометрическую сумму n + n/2 + n/4 + ... = Onnn.В худшем случае сильныйперекоссильный перекоссильныйперекос: пик может быть On2n^2n2 вызовысоздаютспискиразмеромn,n−1,n−2,...иониостаютсяживыминастекевызовы создают списки размером n, n−1, n−2,... и они остаются живыми на стекевызовысоздаютспискиразмеромn,n1,n2,...иониостаютсяживыминастеке, то есть очень плохо по памяти.Рекурсивная глубина: в среднем Olognlog nlogn, в худшем Onnn — это дополнительно стек вызовов.

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

Данная реализация стабильна. Разделение выполнено фильтрами, которые сохраняют порядок элементов, и при конкатенации относительный порядок равных элементов сохраняется. Тоестьэлементысравнымизначенияминеменяютпорядокотносительнодругдруга.То есть элементы с равными значениями не меняют порядок относительно друг друга.Тоестьэлементысравнымизначенияминеменяютпорядокотносительнодругдруга.

4) Практические улучшения и варианты

a) Выбор опорного элемента

Случайный pivot random.choiceилиswapсrandomindexrandom.choice или swap с random indexrandom.choiceилиswapсrandomindex сильно уменьшает шанс худшего случая на «враждебных» входах → часто достаточное эмпирическое решение.median-of-three например,median(a[low],a[mid],a[high])например, median(a[low], a[mid], a[high])например,median(a[low],a[mid],a[high]) уменьшает шанс плохих разбиений на почти-отсортированных данных.Для жёстких гарантий — медиана медиан median−of−mediansmedian-of-mediansmedianofmedians даёт детерминированный Onnn выбор опоры и гарантирует Onlognn log nnlogn времени, но сложнее и в практике редко используется.

b) In-place варианты меньшедополнительнойпамятименьше дополнительной памятименьшедополнительнойпамяти

Классический in-place quicksort с разделением Ломуто или Хоара использует Olognlog nlogn рекурсивной памяти в среднем и O111 дополнительной памяти для данных безсозданияновыхсписковбез создания новых списковбезсозданияновыхсписков. Минус: неустойчив.Пример Hoare−партитция,итеративный/рекурсивныйвариантHoare-партитция, итеративный/рекурсивный вариантHoareпартитция,итеративный/рекурсивныйвариант:

def quicksort_inplacea,lo=0,hi=Nonea, lo=0, hi=Nonea,lo=0,hi=None:
if hi is None: hi = lenaaa-1
while lo < hi:

median-of-three можно сюда вставить для alololo, a(lo+hi)//2(lo+hi)//2(lo+hi)//2, ahihihi p = partitiona,lo,hia, lo, hia,lo,hi # реализуйте Hoare/Lomuto
# рикурсивно сортируем меньшую часть, итеративно — большую tailrecursioneliminationtail recursion eliminationtailrecursionelimination if p - lo < hi - p:
quicksort_inplacea,lo,p−1a, lo, p-1a,lo,p1 lo = p+1
else:
quicksort_inplacea,p+1,hia, p+1, hia,p+1,hi hi = p-1

реализуйтеpartitionвстилеHoare/Lomuto;вHoare−партитцииp—индексразбиения.реализуйте partition в стиле Hoare/Lomuto; в Hoare-партитции p — индекс разбиения.реализуйтеpartitionвстилеHoare/Lomuto;вHoareпартитцииpиндексразбиения.

c) Трёх-путевая DutchNationalFlagDutch National FlagDutchNationalFlag партиция

При большом количестве равных ключей обычный quicksort деградирует. 3-way разбиение меньше=,равноpivot,больше=меньше =, равно pivot, больше =меньше=,равноpivot,больше= даёт Onnn на массиве с большим числом дубликатов и в целом улучшает константы:

def quicksort_3waya,lo=0,hi=Nonea, lo=0, hi=Nonea,lo=0,hi=None:
if hi is None: hi = lenaaa-1
if lo >= hi: return
lt, i, gt = lo, lo+1, hi
pivot = alololo while i <= gt:
if aiii < pivot:
altltlt, aiii = aiii, altltlt; lt += 1; i += 1
elif aiii > pivot:
aiii, agtgtgt = agtgtgt, aiii; gt -= 1
else:
i += 1
quicksort_3waya,lo,lt−1a, lo, lt-1a,lo,lt1 quicksort_3waya,gt+1,hia, gt+1, hia,gt+1,hi

d) Комбинация с insertion sort

Для маленьких подмассивов обычнопорог10–40обычно порог 10–40обычнопорог10–40 быстрее использовать insertion sort вместо рекурсии — уменьшает константы.

e) Избежать глубокой рекурсии

Применять tail recursion elimination sortsmallerhalfрекурсивно,абольшуювциклеsort smaller half рекурсивно, а большую в циклеsortsmallerhalfрекурсивно,абольшуювцикле, или использовать собственный стек, чтобы избежать переполнения стека при больших n.В Python глубокая рекурсия часто запрещена лимит 1000лимит ~1000лимит 1000, поэтому для больших n лучше in-place итеративный вариант или увеличить recursionlimit нолучшенеполагатьсянаэтоно лучше не полагаться на этонолучшенеполагатьсянаэто.

f) Использовать встроенный сортировщик Python

Для практических задач в Python предпочтительнее использовать list.sort или sorted — это Timsort: стабильный, Onlognn log nnlogn в худшем случае, хорош для частично отсортированных данных и обычно быстрее самописного quicksort.

5) Итог краткократкократко

Ваш код: время среднее Onlognn log nnlogn, худшее On2n^2n2; память средняя Onnn, худшая On2n^2n2; стабильный.In-place quicksort: время те же, память средняя Olognlog nlogn рекурс.стекрекурс. стекрекурс.стек, худшая Onnn; обычно нестабилен.Практические приёмы: случайный/median-of-three pivot, 3-way partition для дубликатов, использовать insertion sort для маленьких подмассивов, избегать глубокой рекурсии, или просто применять встроенный sorted/list.sort.

Если нужно, могу:

привести полный рабочий код in-place quicksort с Hoare-партитцией и median-of-three,или показать более подробный анализ памяти peakliveallocationspeak live allocationspeakliveallocations для разных сценариев.
12 Окт в 15:19
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир