# конспект · шаг 7 из 7
Конспект: Timsort, powersort, introsort и pdqsort
Понятия
Серия - кусок, уже упорядоченный по возрастанию (неубывающий) или по убыванию (в Python 3.12 - строго, с 3.13 - нестрого); убывающую разворачивают.
minrun - до какой длины короткая серия добивается вставками с двоичным поиском; 32-64.
Галоп - экспоненциальный поиск длины куска при слиянии: позиции 0, 1, 3, 7, 15…, потом двоичный поиск.
Сила границы (powersort) - номер первого уровня деления массива пополам, который разделяет середины двух соседних серий.
Introsort - быстрая сортировка со счётчиком глубины и переходом на пирамидальную.
Что унести
TIMSORT: ПРОХОД
серия: сравнить два первых; убывает - пока строго меньше, развернуть
короткая серия -> до minrun вставками (сравнений log₂, сдвиги дешёвы)
minrun = старшие 6 битов n (+1, если отброшена единица); n < 64 -> n
2112 -> 33 (64 серии), 10⁶ -> 62 (16 130 серий, чуть меньше 2¹⁴)
sorted на 10⁵: готовые - 99 999 сравнений; случайные - 1 528 982
нижняя граница log₂ 100000! ≈ 1 516 704 - перерасход 0,8%
СЛИЯНИЕ
только соседей: иначе ломается устойчивость
обрезка краёв: начало A ≤ B[0] и хвост B ≥ A[-1] уже на месте
буфер - на меньшую часть; при равенстве берётся элемент A
галоп после 7 побед подряд: кусок k за 2⌊log₂ k⌋ + 2 сравнения
проигрывает при k = 2 и 4, выигрывает с k = 6; порог плавает
две серии блоками по 100: 27 992 сравнения на слияние вместо ~200 000
ПОРЯДОК СЛИЯНИЙ
цена слияния = сумма длин
до 3.11: len[-3] > len[-2] + len[-1], len[-2] > len[-1]
2015: проверяли 3 верхних -> инвариант рушился глубже, Java падала
с 3.11: powersort - сливать на стеке всё с силой > силы новой границы
гарантия powersort: ≤ n·H + 2n, n·H = Σ ℓᵢ log₂(n / ℓᵢ)
[1, 200, 200, 2, 2]: слева 1410, старое 1211, powersort = оптимум 814
[200, 5, 3, 200]: старое = оптимум 624, powersort 816
оптимум при известных длинах: динамика O(r³), Ху - Таккер O(r log r)
БЫСТРАЯ СО СТРАХОВКАМИ
противник Макилроя (1999): ломает быструю с дешёвым детерм. опорным
медиана трёх, n = 200: случайные 1574, убийца 10 135, с защитой 4664
introsort (libstdc++): запас 2⌊log₂ n⌋ -> пирамидальная; куски ≤ 16 -
вставками одним проходом в конце
pdqsort: плохое разбиение (< 1/8) -> перемешать; > log₂ n плохих ->
пирамидальная; без обменов -> добить вставками; блоки без ветвлений
КТО ЧТО ИСПОЛЬЗУЕТ
CPython sorted, list.sort Timsort + powersort, устойчиво
C++ std::sort introsort; stable_sort - слиянием
Java объекты / примитивы Timsort / двухопорная быстрая
Go 1.19+ sort.Sort pdqsort
Rust 1.81+ sort / sort_unstable driftsort / ipnsort
Ловушки джокера
NaN в сортируемых числах. Сравнение с NaN ложно, результат не отсортирован, ошибки нет.
Своя быстрая сортировка в сервисе. Противник строит вход-убийцу один раз и шлёт его снова и снова.
Неустойчивая сортировка для многоключевого порядка. std::sort, sort.Slice, sort_unstable
переставляют равные элементы.
Слияние не соседей «потому что дешевле». Порядок равных записей перепутается.
Инвариант, проверенный только сверху. Размер буфера, выведенный из рассуждения, проверяют доказательством, а не тестом.
«Introsort так же быстр на убийце». Страховка даёт n log n с константой побольше, не скорость случайного
входа.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий