Вопрос с собеседования · Основы

Что такое амортизированная сложность?

JuniorСложность алгоритмов
Ещё спрашивают так:
  • Почему добавление в динамический массив считается O(1), если иногда нужно копирование?
  • Чем амортизированная сложность отличается от средней?
Коротко

Амортизированная сложность — средняя стоимость операции в длинной серии: редкие дорогие операции «размазываются» по многим дешёвым.

  • Пример: добавление в динамический массив — обычно O(1), иногда копирование O(n), в среднем O(1)
  • Это гарантия на серию, а не вероятность: не путать со средним случаем по случайным данным

А ещё Ключ — массив растёт в разы (×2 или ×1,5), а не на фиксированное число

Подробно

Некоторые структуры данных иногда выполняют дорогую операцию, но так редко, что в сумме по серии стоимость на одну операцию остаётся маленькой.

Классический пример — динамический массив (list в Python, ArrayList в Java, vector в C++). Пока есть свободное место, добавление в конец — O(1). Когда место кончилось, выделяется массив вдвое больше и все элементы копируются — O(n). Посчитаем суммарную работу для n добавлений: копирования происходят при размерах 1, 2, 4, 8, …, n, их сумма меньше 2n. Плюс n простых вставок. Итого меньше 3n операций на n добавлений, то есть O(1) на операцию в среднем по серии.

Важно, что массив растёт в разы. Если бы он увеличивался на 10 элементов, копирований было бы n/10, каждое до n элементов — итого O(n²) на серию, O(n) на операцию.

Отличие от средней сложности: средняя считается по распределению входных данных (например, хеш-таблица при «хорошем» хеше). Амортизированная — это гарантия для любой последовательности операций, без вероятностей.

Другие примеры: расширение хеш-таблицы, очередь на двух стеках, система непересекающихся множеств (union-find).

Пример

import sys

a = []
prev = sys.getsizeof(a)
for i in range(20):
    a.append(i)
    size = sys.getsizeof(a)
    if size != prev:        # память выделяется скачками, а не на каждый append
        print(f"после {len(a)} элементов: {size} байт")
        prev = size

Что хотят услышать

  • Определение через стоимость серии операций.
  • Разбор динамического массива с подсчётом.
  • Почему рост должен быть в разы.
  • Отличие от средней сложности.

Частые ошибки

  • Говорить, что добавление в динамический массив «всегда O(1)».
  • Путать амортизированную сложность со средним случаем.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✗Средняя сложность операции на случайных входных данных: если входы распределены равномерно, дорогие случаи встречаются редко и в среднем дают O(1)
  • ✓Средняя стоимость операции в длинной серии: редкие дорогие операции распределяются по многим дешёвым, как append в динамический массив
  • ✗Это сложность в лучшем случае
  • ✗Динамический массив растёт на фиксированные 10 элементов и даёт амортизированное O(1)

Что спросят дальше

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования