Глоссарий — DSA 01
Справочник ключевых терминов курса DSA 01.
complexity
Big-O notation
Асимптотическая верхняя граница роста функции. f(n) = O(g(n)) означает, что при больших n значение f(n) не превышает константу, умноженную на g(n). Используется для описания худшего случая времени или памяти алгоритма, отбрасывая константы и младшие члены.
Big-Theta notation
Асимптотически точная граница. f(n) = Theta(g(n)) означает, что f(n) ограничена и сверху, и снизу константами, умноженными на g(n). Сильнее, чем Big-O: Theta(n) — это и O(n), и Omega(n) одновременно.
Big-Omega notation
Асимптотическая нижняя граница. f(n) = Omega(g(n)) означает, что f(n) растёт не медленнее, чем константа, умноженная на g(n). Используется для нижних границ алгоритмов (например, сортировка сравнениями — Omega(n log n)).
amortized analysis
Метод анализа, при котором стоимость операции усредняется по последовательности из N операций. Отдельная операция может быть дорогой (resize массива — O(n)), но в среднем по последовательности она дешёвая (append — O(1) amortized). Это не то же самое, что average case.
aggregate method
Простейший способ доказать амортизированную сложность: посчитать суммарную стоимость T(n) для последовательности из n операций и поделить на n. Для x2 dynamic array сумма работы copy при resize — геометрическая прогрессия, меньше 2n, что даёт amortized O(1) на append.
accounting method
Банковский метод амортизации: каждой операции назначается амортизированная плата (больше реальной), излишек откладывается в «банк». Дорогие операции (resize) оплачиваются из банка. Если банк никогда не уходит в минус, амортизированная плата — корректная верхняя граница.
potential method
Формальный метод амортизации: вводится функция потенциала Phi от состояния структуры, amortized cost = actual cost + delta(Phi). Если Phi >= 0 и Phi_initial = 0, сумма amortized — верхняя граница реальной стоимости. Используется для сложных структур: splay tree, Fibonacci heap.
time complexity
Зависимость количества элементарных операций алгоритма от размера входа n. Меряется в Big-O: O(1) — константа, O(log n) — логарифм, O(n) — линейно, O(n log n) — сортировка сравнениями, O(n^2) — квадрат, O(2^n) — экспонента.
space complexity
Зависимость объёма памяти, нужного алгоритму, от размера входа n. Включает auxiliary space (рабочая память) и input space (сам вход). BFS — O(n) по памяти (нужна очередь), DFS — O(h) (глубина стека).
P
Класс задач, решаемых за полиномиальное время O(n^k) для константы k. Сюда входят сортировки, поиск, графовые обходы, кратчайшие пути. «Эффективно решаемые» в теоретическом смысле.
NP
Класс задач, решение которых можно проверить за полиномиальное время. Сюда входят все задачи из P, плюс задачи, для которых решения мы пока умеем находить только перебором (SAT, коммивояжёр). Открытый вопрос: P = NP?
NP-complete
Самые «трудные» задачи в NP: если для любой NP-complete найдётся полиномиальный алгоритм, то P = NP. Классические примеры: SAT, 3-COLOR, TSP, subset sum. На практике решаются эвристиками и приближёнными алгоритмами.
data-structure
array
Структура данных с фиксированным размером, элементы которой лежат в непрерывной памяти по равным offset-ам. Доступ по индексу — O(1). В Python чистого статического array нет; есть module array и numpy.ndarray для однотипных элементов.
dynamic array
Массив, который умеет расти при заполнении: при достижении ёмкости выделяется новый буфер большего размера (обычно x2 или x1.125), элементы копируются. Append амортизированно O(1), вставка в середину O(n). В Python это PyListObject.
linked list
Структура данных, в которой каждый элемент (узел) хранит данные и указатель на следующий узел. Узлы лежат в произвольных местах памяти. Insert/delete в начало — O(1), доступ по индексу — O(n). На современном железе медленнее массива из-за pointer chasing.
singly linked
Связный список, в котором каждый узел хранит указатель только на следующий. Удаление текущего узла за O(1) невозможно без предыдущего узла. Меньше памяти на узел, чем у doubly linked.
doubly linked
Связный список, в котором каждый узел хранит указатели и на следующий, и на предыдущий. Позволяет O(1) удаление узла, если есть ссылка на него. Используется внутри collections.deque и LRU cache.
stack
LIFO-структура: push и pop работают с одного конца. O(1) на обе операции. Реализуется поверх dynamic array (Python list) или связного списка. Применяется в DFS, парсерах, undo-историях, call stack.
queue
FIFO-структура: enqueue в конец, dequeue из начала. O(1) на обе операции в правильной реализации. В Python — collections.deque, не list (list.pop(0) — O(n)).
deque
Double-ended queue — структура, которая поддерживает O(1) добавление и удаление с обоих концов. В CPython collections.deque реализован как doubly linked list блоков (по 64 элемента в блоке), а не как массив.
ring buffer
Циклический массив фиксированного размера с двумя указателями (head и tail). Push/pop с обоих концов — O(1). Используется в low-latency-системах, аудио-буферах, network drivers. Хорошая spatial locality в отличие от связных списков.
hash map
Структура key -> value, использующая хеш-функцию для размещения ключа в bucket. Средняя сложность операций — O(1), худший случай — O(n) при плохом хеше. В Python это dict, реализованный через open addressing с perturbation.
hash set
Множество уникальных элементов на основе хеш-таблицы. В Python — set/frozenset. Внутренне почти идентичен dict, но без значений. O(1) на add/remove/in в среднем.
BST
Binary search tree — бинарное дерево, в котором для каждого узла левое поддерево содержит ключи меньше узла, правое — больше. Поиск, вставка, удаление — O(h), где h — высота. Без балансировки h может выродиться в n. Балансированные варианты: AVL, red-black.
binary heap
Полное бинарное дерево, хранящееся в массиве, со свойством heap (родитель меньше или равен детям для min-heap). Insert и extract-min — O(log n), peek — O(1). Используется для priority queue и heap sort. Дети узла i лежат на 2i+1 и 2i+2.
min-heap
Куча, в которой корень — минимальный элемент. Используется в priority queue (sched), top-K largest (хранить top-K через min-heap размера K), Dijkstra. Python heapq реализует именно min-heap.
max-heap
Куча, в которой корень — максимальный элемент. Применяется в heap sort, top-K smallest (через max-heap размера K), task schedulers. В Python максимум через heapq делается через инверсию знаков или кортежи (-key, value).
graph
Структура из вершин (V) и рёбер (E), описывающая отношения. Бывает направленный или ненаправленный, взвешенный или нет. Используется в DE для lineage, DAG-ов оркестрации (Airflow), графов транзакций. Представления: adjacency matrix, adjacency list, CSR.
adjacency matrix
Представление графа квадратной матрицей V x V, где matrix[i][j] = 1 если есть ребро. Память O(V^2), проверка ребра — O(1), обход соседей — O(V). Выгодна для плотных графов и алгоритмов вроде Floyd-Warshall.
adjacency list
Представление графа как dict (или массив) списков: для каждой вершины — список соседей. Память O(V + E), обход соседей — O(degree). Выгодно для разреженных графов. В Python обычно dict[int, list[int]] или defaultdict(list).
CSR
Compressed Sparse Row — компактное представление графа двумя массивами: offsets (где начинаются соседи каждой вершины) и indices (сами соседи). Отличная spatial locality, в 2-4 раза быстрее adjacency list в обходах. Используется в scipy.sparse, NetworkX-аналогах для больших графов.
memory
cache line
Минимальная единица, которой CPU читает из RAM в кэш — 64 байта на современных x86 и ARM. Чтение одного байта физически невозможно — всегда подгружаются 64 байта, выровненных по границе. Это основа spatial locality.
L1 cache
Первый уровень кэша CPU — обычно 32-64 KB на ядро, latency ~1 ns (3-4 такта). Разделён на L1d (данные) и L1i (инструкции). Самый быстрый, но самый маленький. Промах в L1 ведёт к чтению из L2.
L2 cache
Второй уровень кэша — обычно 256 KB - 1 MB на ядро, latency ~3-5 ns (10-15 тактов). Больше L1, медленнее. Промах ведёт к L3 или RAM.
L3 cache
Третий уровень кэша — обычно 4-64 MB, общий для всех ядер сокета. Latency ~10-20 ns. Последний уровень перед RAM. Промах — это уже ~60-100 ns на чтение из RAM.
RAM
Random Access Memory — основная память. Latency ~60-100 ns на чтение, в 60-100 раз медленнее L1. Чтение всегда выровнено по cache line. Имя «random access» обманчиво: последовательный доступ быстрее случайного из-за prefetcher и cache lines.
spatial locality
Свойство паттерна доступа: если код обращается к адресу X, скоро понадобятся X+1, X+2 и так далее. Превращает дорогие RAM-чтения в почти бесплатные cache hits. Хорошо: проход по массиву. Плохо: random access, linked list.
temporal locality
Свойство: если код обратился к X, скоро обратится к X снова. Cache держит недавние данные именно ради этого. Хорошо: переменная цикла в регистре, hot-data в hash-таблице. Плохо: однократный проход через гигантский dataset.
prefetcher
Аппаратный механизм CPU, который предсказывает следующие читаемые адреса и заранее подгружает их в кэш. Хорошо работает на sequential access. Не работает на linked list — указатели непредсказуемы.
branch prediction
Аппаратный механизм CPU: пытается предсказать, по какой ветке пойдёт if, чтобы не останавливать конвейер. Промах предсказания (branch misprediction) — 10-20 тактов простоя. Хорошо предсказываются монотонные паттерны (отсортированный массив), плохо — случайные.
pipeline
Конвейерное исполнение инструкций в CPU: пока одна инструкция декодируется, следующая уже читает операнды, следующая исполняется. На современных CPU — 14-20 стадий. Промах branch prediction или cache miss обнуляет полезную работу нескольких стадий.
pointer chasing
Паттерн доступа, при котором каждый следующий адрес становится известен только после чтения предыдущего (next = node.next). Prefetcher беспомощен, каждая итерация — потенциальный cache miss. Отсюда — медленность linked list, BST, graph через объекты.
sequential access
Последовательный проход по памяти с постоянным offset (arr[0], arr[1], arr[2]). Оптимальный паттерн: prefetcher успевает подгружать следующие cache lines, каждый доступ — cache hit. Основа быстрых обходов массивов и numpy.
memory hierarchy
Иерархия памяти: регистры (sub-ns) -> L1 (1 ns) -> L2 (3-5 ns) -> L3 (10-20 ns) -> RAM (60-100 ns) -> SSD (10000 ns) -> HDD (10 ms). Каждый уровень в 3-100 раз медленнее предыдущего. DSA-выбор должен учитывать, в какой уровень попадут данные.
algorithm
BFS
Breadth-first search — обход графа по уровням: сначала все соседи стартовой вершины, потом их соседи, и так далее. Использует очередь (collections.deque). Сложность O(V + E). Находит кратчайший путь в невзвешенном графе. Память — O(V).
DFS
Depth-first search — обход графа в глубину: спускаемся по одной ветке как можно глубже, затем откат. Использует стек (рекурсия или явный stack). O(V + E) по времени, O(h) по памяти, где h — глубина. Основа топологической сортировки.
binary search
Поиск элемента в отсортированном массиве делением пополам: O(log n). Реализован в bisect (Python). Тонкие места: off-by-one на границах, выбор upper vs lower bound, обработка дубликатов. Не работает на linked list — нужен random access.
linear search
Поиск элемента простым проходом: O(n) в худшем случае. На малых n (десятки элементов) обгоняет binary search и hash из-за лучшей cache locality и отсутствия overhead. Лучший выбор для коротких списков.
bubble sort
Простая сортировка: проходим массив, меняем соседей местами если нарушен порядок, повторяем. O(n^2) в худшем и среднем случае. Стабильная. Практически нигде не используется, кроме учебных целей.
insertion sort
Сортировка вставками: берём элемент, сдвигаем влево, пока не найдёт своё место. O(n^2) в худшем, O(n) на почти отсортированном. Используется как «нижний этаж» Timsort и introsort для малых подмассивов (обычно n < 32-64).
selection sort
Сортировка выбором: находим минимум в неотсортированной части и ставим в начало. O(n^2) всегда, не зависит от входных данных. Делает минимум swap-ов — O(n). Используется редко, в основном на embedded.
merge sort
Сортировка слиянием: рекурсивно делим массив пополам, сортируем половины, сливаем. O(n log n) по времени всегда. O(n) дополнительной памяти. Стабильная. Базис Timsort и внешней сортировки (k-way merge на дисках).
quicksort
Сортировка через partitioning вокруг pivot. Среднее — O(n log n), худший случай — O(n^2) (плохой pivot). Не стабильная, in-place. С randomized pivot или median-of-three используется в introsort (libc++, .NET).
Timsort
Гибридная стабильная сортировка, использующая insertion sort на малых блоках (runs) и merge sort для их слияния. Использует естественно отсортированные участки. O(n log n) в худшем, O(n) на отсортированном. Стандартная сортировка в Python и Java.
heapsort
Сортировка через построение max-heap и последовательный extract-max. O(n log n) в худшем случае, in-place. Не стабильная. Хуже Timsort на cache locality (произвольные обмены в массиве), используется реже.
radix sort
Не-сравнительная сортировка: распределяем элементы по «корзинам» по цифрам/битам, повторяя по разрядам. O(n * k), где k — длина ключа. Работает только на целых или строках фиксированной длины. Быстрее O(n log n) при больших n и коротких ключах.
counting sort
Не-сравнительная сортировка для целых из малого диапазона [0, k]: считаем сколько раз встретился каждый ключ, потом раскладываем. O(n + k) по времени и памяти. Стабильная. Используется как «низший уровень» radix sort.
partition
Операция разделения массива относительно pivot: слева — меньше или равно, справа — больше. Базовая операция quicksort. Lomuto и Hoare — два классических варианта. Один проход — O(n).
heapify
Построение кучи из массива за O(n) (а не O(n log n), как могло бы показаться). Идея: пройти от середины массива к началу, для каждого узла «просеять» вниз. Используется при инициализации heap (heapq.heapify).
k-way merge
Слияние k отсортированных последовательностей в одну. Через min-heap размера k: O(N log k), где N — суммарное число элементов. Основа external sort и merge-операции в Spark/Flink shuffle.
divide-and-conquer
Парадигма: задачу делим на меньшие подзадачи того же типа, решаем рекурсивно, объединяем. Merge sort, quicksort, binary search, FFT, Strassen. Анализ через рекуррентные соотношения и Master Theorem.
python-internal
PyListObject
C-структура CPython, реализующая list. Содержит указатель на массив PyObject*, длину и ёмкость. При переполнении ёмкости growth-формула CPython: new = (old + (old >> 3) + 6) & ~3 — рост примерно x1.125, не x2.
PyObject pointer
Указатель на структуру PyObject — представление любого Python-объекта на уровне C. В list хранятся именно указатели (8 байт на 64-битке), а не сами объекты. Поэтому list[int] на 1M элементов весит около 36 MB (8 байт на указатель плюс 28 байт на сам PyLongObject), а не 4 MB.
dict probing
Стратегия поиска свободного слота в Python dict при коллизии: j = (5*j + 1 + perturb) & mask, perturb >>= 5. Использует все биты хеша через perturbation, гарантирует обход всех слотов до повторения.
perturbation
Техника в Python dict probing: переменная perturb, изначально равная полному хешу, сдвигается на 5 битов каждую итерацию. Подключает старшие биты хеша к probing — ключи с одинаковыми младшими битами расходятся по разным слотам.
SipHash
Криптографически безопасная хеш-функция, которую CPython использует для hash(str), hash(bytes). Защищает от hash-flooding атак: при старте процесса генерируется случайный seed (PYTHONHASHSEED), поэтому hash(s) одной строки разный между запусками.
sys.getsizeof
Функция CPython: возвращает размер объекта в байтах без учёта объектов, на которые он ссылается. Поэтому sys.getsizeof([1,2,3]) даёт размер самого list-объекта (~88 байт), но не учитывает int-ов внутри. Для глубокого размера — pympler.asizeof.
pympler.asizeof
Утилита для рекурсивного измерения памяти Python-объектов: считает размер вместе со всеми ссылками, обрабатывая циклы. Медленнее sys.getsizeof, но даёт реальное потребление памяти структурой.
timeit
Модуль stdlib для микро-бенчмаркинга Python-кода: запускает фрагмент N раз, выключая GC, и возвращает время. timeit.timeit(stmt, number=10000) — основа всех измерений в курсе. Чувствителен к шуму CPU, fan-curve, фоновым процессам.
tracemalloc
Модуль stdlib для трекинга аллокаций памяти Python-программы. Показывает, где (по строкам кода) и сколько байт было выделено. Лучше sys.getsizeof для поиска утечек и узких мест в реальных пайплайнах.
collections.deque
Двусторонняя очередь CPython, реализованная как doubly linked list блоков по 64 указателя. appendleft, append, popleft, pop — O(1). Используется для BFS, sliding window, history buffer. Быстрее list для head-операций.
heapq
Модуль stdlib для работы с min-heap поверх обычного list. heappush, heappop — O(log n). heapify — O(n). nlargest, nsmallest — частые операции для top-K. Не имеет max-heap — нужна инверсия знаков.
sortedcontainers
Чистый Python-пакет, дающий SortedList/SortedDict/SortedSet с O(log n) на insert/delete/lookup. Внутри — список списков (B-tree-подобная структура), а не RB-tree. На практике быстрее BST из-за cache locality.
GIL
Global Interpreter Lock — глобальная блокировка CPython, разрешающая исполнять Python-байткод только одному потоку за раз. Из-за GIL multi-threading в Python не даёт CPU-параллелизма на чистом Python-коде. В 3.13 появился экспериментальный no-GIL build.
refcount
Reference counting — основной механизм управления памятью в CPython. Каждый объект хранит счётчик ссылок, при достижении нуля объект освобождается. Видно через sys.getrefcount(obj). Не справляется с циклическими ссылками — для них есть GC.
applied
top-K
Задача: найти K самых больших (или маленьких) элементов в потоке из N. Наивно через sort — O(N log N). Через min-heap размера K — O(N log K), сильно лучше при K << N. Базовый паттерн в DE: top-N клиентов по выручке.
dedup
Дедупликация — удаление повторяющихся записей. Через set — O(N) по времени, O(N) по памяти. На миллиардах записей set не помещается в RAM — нужны Bloom filter (вероятностный) или внешняя сортировка с merge.
Bloom filter
Вероятностная структура для проверки принадлежности множеству. Битовый массив + k хеш-функций. Не даёт false negatives (если говорит «нет», то точно нет), но допускает false positives. В 8-10 раз меньше памяти, чем set. Используется в Cassandra, HBase, RocksDB.
sliding window
Окно фиксированного размера W, которое скользит по потоку: на каждом шаге добавляется новый элемент справа, выкидывается крайний слева. O(1) на шаг через deque или ring buffer. Базовый паттерн стриминговых агрегаций.
tumbling window
Не-перекрывающееся окно фиксированного размера: события группируются в блоки по времени или количеству. Используется в Flink/Spark Structured Streaming для агрегаций по периодам (минуты, часы).
session window
Окно переменного размера, определяемое gap-ом неактивности: события одной сессии группируются, пока разрыв между ними меньше threshold. Используется для аналитики поведения пользователей: визит на сайт, активность в приложении.
external sort
Сортировка данных, не помещающихся в RAM. Классический алгоритм: разделить на чанки по размеру памяти, отсортировать каждый, записать на диск, сделать k-way merge на дисках. O(N log N) по сравнениям, но с минимизацией I/O.
lineage graph
DAG зависимостей между датасетами и трансформациями в пайплайне: какая таблица из какой получена и какой операцией. Хранится в Marquez, DataHub, OpenLineage. Используется для impact analysis и debug «откуда взялась эта строчка».
DAG
Directed acyclic graph — направленный граф без циклов. Базовая абстракция оркестраторов (Airflow, Dagster, Prefect): вершины — задачи, рёбра — зависимости. Допускает топологическую сортировку — порядок исполнения задач.
topological sort
Линейный порядок вершин DAG, в котором каждое ребро направлено вперёд. Алгоритм Кана: O(V + E) через очередь вершин с in-degree=0. Используется в оркестраторах для определения порядка задач, в build-системах, при проверке зависимостей пакетов.
LRU cache
Least-Recently-Used cache — хранит K последних использованных элементов, при переполнении выкидывает least-recently-used. Реализуется через doubly linked list + hash map: O(1) на get и put. В Python — functools.lru_cache.