QuickSort
def quicksort(a):
random.shuffle(a)
quicksort(a, 0, len(a))
def quicksort(a, l, r):
if l < r:
return
# выбираем опорный элемент x
px = pivot(a, l, r)
# разбиваем все элементы массива
# на куски меньше x и больше x
m = partition(a, l, r, px)
quicksort(a, l, m)
quicksort(a, m + 1, r)
def partition(a, l, r, px):
# время работы O(n)
a[px], a[r] = a[r], a[px]
m = l
for p in range(l, r):
if a[p] < a[r]:
a[p], a[m] = a[m], a[p]
m += 1
a[m], a[r] = a[r], a[m]
# m -- индекс опрного элемента после разбиения
return m
def pivot(a, l, r):
# дубовый выбор опорного элемента
return l
# среднее
return (l + r - 1) / 2
# крутой вариант, даст элемент из середины отсортированного массива
# если медиана считается за O(n), то вообще заебись
# но тогда у сортировки получается большая, так что нафиг
# *как-то противоречиво получилось
return median(a, l, r)
# тоже неплохо
return median(a[l], a[(l + r) / 2], a[r])
# основной на практике вариант
return random.choice(a[l:r])
Худший случай: В худшем случае массив будет разбиваться на куски размера $1$ и $n - 1$. Это осуществляется, если исходный массив отсортирован. В таком случае алгоритм будет работать $T(n) = T(1) + T(n - 1) + O(n) = \Theta(n^2)$.
Средний случай: В среднем случае происходит неплохое разбиение, примерно на равные куски. Время работы $T(n) = T(\frac n4) + T(\frac {3n} 4) + \Theta(n) \leq 2T(\frac {3n} 4) + O(n) = O(n \log n)$
"Хорошее" разбиение: 1. $T(n) = T(n - 1000) + T(1000) + O(n)$ -- плохое в ассимптотическом смысле 2. $T(n) = T(0.99 n) + T(0.01 n) + O(n)$ -- хорошее, т.к. коэффициент влияет лишь на основание логарифма
Вероятностные алгоритмы
- Расширим модель, добавив функцию
rand()
, возвращающую случайное число - Добавляем к машине Тьюринга еще одну входную ленту, на которой записаны случайные биты.
A(x, r)
,x
-- входные данные,r
-- случайные данные,A
-- обычный детерминированный алгоритм
Есть два типа вероятностных алгоримов: 1. "Лас-Вегас" -- алгоритм дает точное решение, но иногда работает долго 2. "Монте-Карло" -- алгоритм всегда работает быстро, но дает только приближенное решение
QuickSort -- "Лас-Вегас" алгоритм.
Анализ QuickSort:
Предпологаем, что все элементы различны.
Мат. ожидание средней глубины рекурсии:
Пусть хорошее разбиение $[\frac 14; \frac 34]$
$P[\#\text{разбиение хорошее}] = \frac 12$
Вызываем pivot
до тех пор, пока разбиение не станет хорошим.
$E[\#\text{число разбиений, пока не получим хорошее}] = 1\cdot\frac 12 + 2 \cdot\frac1 4 + 3 \cdot\frac1 8 + ... = 2$
$E[\#] = p\cdot 1 + (1 - p)[1+ E[\#]] \Rightarrow E[\#]=\frac 1p$
Если не модифицировать разбиение, то средний размер куска дерева, в котором есть плохое разбиение и следующее за ним хорошее $=2$.
$E[\#\text{глубины рекурсии самой левой ветви}] \leq E[\#\text{глубины, на которой массив уменьшился в два раза}] + E[\#\text{глубины для}\frac n2]$ $R(n) \leq 2 + R(\frac n2), \ R(1) = 1 \Rightarrow R(n) \leq O(\log n)$. Т.о. средняя глубина рекурсии $O(\log n)$
Сложность вероятностного алоритма:
Сложность -- это мат.ожидание количества операций. Это называется "сложность в среднем".
Сложность QuickSort: $E[\#\text{количество сравнений}] = \sum\limits_{i=1}^{n-1}\sum\limits_{j=i+1}^n 1\cdot P[\text{сравним i и j}] = \sum\limits_{i=1}^{n-1}\sum\limits_{j=i+1}^n \frac 2 {j - i + 1}=$ $= \sum\limits_{i=1}^{n-1}\sum\limits_{k=1}^{n-i} \frac 2 {k+1} \leq \sum\limits_{i=1}^{n-1} \ln n = O(n \log n)$
Замечание: тот же анализ рабтает для детерменированного pivot
(например, первый элемент) и равномерного распределения на входах.
Варианты QuickSort:
1. IntroSort: запускает QuickSort и следит за глубиной рекурсии. Если глубина больше $C\log n$, то запускаем, например, HeapSort, на всем массиве.
2. QuickSort3: в partition
делим массив на три части -- меньше опорного, равные ему, большие его.
3. QuickSortStable: partition
будет рекурсивный и использовать циклические сдвиги. $O(n\log^2 n)$.
4. QuickSortRec: гарантирует глубину рекурсии $\Theta(\log n)$.
def quicksort_rec(a, l, r)):
while r - l > 0:
px = pivot(a, l, r)
m = partition(a, l, r, px)
if m - l > (r - l) // 2:
quicksort_rec(a, m + 1, r)
r = m - 1
else:
quicksort_rec(a, l, m)
l = m + 1