English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

nu

гость
1 / ?
назад к урокам

Скорости роста, а не абсолютные стоимости

Big O: Измерение скорости роста стоимостей

Обозначение Big O измеряет скорость роста стоимости алгоритма — не абсолютную стоимость.

Вопрос, на который отвечает Big O: если размер входа N удвоится, удвоится ли работа? Увеличится в четыре раза? Останется неизменной?

'O' обозначает 'порядок величины.' Выражение в скобках описывает форму кривой роста.

Таблица роста Big O: операции при N=10, 100 и 1000 для O(1), O(N) и O(N²)

Основные классы сложности:

- O(1) — константа: стоимость не растёт. Пример: поиск значения по индексу в массиве. Независимо от того, содержит ли массив 10 элементов или 10 миллионов, один поиск всегда остаётся одним поиском.

- O(N) — линейная: стоимость растёт с входом. Пример: чтение каждого элемента в списке один раз. Удвойте список, удвойте чтения.

- O(N²) — квадратичная: стоимость растёт как квадрат входа. Пример: сравнение каждого элемента со всеми остальными. Удвойте N, увеличьте работу в четыре раза.

Таблица сравнения роста:

NO(1)O(N)O(N²)
10110100
100110010,000
1,00011,0001,000,000

При N=10 разница выглядит небольшой. При N=1000 разрыв становится огромным.

Сравните O(N) и O(N²)

Используйте приведённую выше таблицу сравнения роста.

При N=1000: O(N) требует 1000 операций. O(N²) требует 1 000 000 операций.

При N=1000, во сколько раз больше операций требует O(N²) по сравнению с O(N)? Покажите свои расчёты.

Как структура кода раскрывает сложность

Как идентифицировать Big O из кода

Big O скрывается в форме вашего кода. Четыре паттерна охватывают большинство случаев:

Один цикл по N элементам: O(N)

# O(N): один проход по списку
for item in list_of_n_items:
    process(item)

Вложенные циклы, каждый по N элементам: O(N²)

# O(N²): каждый элемент соединён с каждым другим элементом
for item_a in list_of_n_items:
    for item_b in list_of_n_items:
        compare(item_a, item_b)

Поиск в постоянное время: O(1)

Доступ по индексу массива, поиск в таблице хеширования — один шаг независимо от размера.

Разделяй и властвуй (сократить проблему вполовину на каждом шаге): O(log N)

Двоичный поиск: каждая проверка сокращает оставшиеся элементы вполовину.

---

Скрытый O(N²): список внутри цикла

# Это выглядит как O(N), но на самом деле это O(N²)
visited = []
for item in list_of_n_items:
    if item not in visited:   # сканирует весь visited: O(N)
        visited.append(item)  # весь цикл: O(N²)

Строка if item not in visited сканирует каждый элемент visited на каждой итерации. Сканирование списка стоит O(N). Цикл, который работает N раз, каждый раз выполняя O(N) работы: O(N) × O(N) = O(N²).

Этот паттерн появляется в 1000+ проектах с открытым исходным кодом. Исправление занимает один символ: замените visited = [] на visited = set(). Проверка членства в наборе стоит O(1), не O(N). Одно изменение. Те же результаты. В 1000 раз быстрее при N=1000.

Классифицируйте & исправьте этот код

Прочитайте этот код:

result = []
for name in student_names:
    if name not in result:
        result.append(name)
Какова сложность Big O этого кода? Объясните, почему строка `if name not in result` делает его дорогим. Затем перепишите код, чтобы сделать его O(N).

Теория встречает практику

Big O в дикой природе

Big O — это не просто теория. Она отделяет программы, которые заканчиваются за секунды, от программ, которые занимают 17 минут — на точно такую же задачу.

Реальный пример: обнаружение цикла зависимостей в системе сборки.

Когда вы устанавливаете программное обеспечение, ваш компьютер разрешает граф зависимостей: пакет A нужен B, B нужен C, и так далее. Система сборки проверяет этот граф на наличие циклов.

Алгоритм обнаружения цикла O(N²) работает хорошо при N=100 пакетах. При N=50000 пакетов (реальный проект): это занимает 17 минут. Исправление O(N): 10 секунд.

Этот точный дефект существует в GHC (компилятор Haskell), pip (менеджер пакетов Python), Maven (система сборки Java), Cargo (менеджер пакетов Rust), & 1000+ других проектов.

Не потому, что их авторы были небрежны. visited = [] была написана, когда N была малой. Код отвердел. N выросла. Никто не заметил, пока сборка не заняла полчаса.

Big O — это словарь, который позволяет вам назвать то, что произошло — & исправить это.

---

Хотите углубиться? Наш курс unhamming включает полную главу о Big O, MOAD-0001 (реальный дефект O(N²), найденный в 1000+ проектах с открытым исходным кодом), & почему назвать паттерн позволяет вам найти его везде. См. Недостающая глава: сложность алгоритмов.

Предскажите время сборки

Система сборки использует обнаружение цикла O(N²).

Измеренное время сборки:

- N=100 пакетов: 0,01 секунды

- N=1000 пакетов: 1 секунда

Для O(N²): время масштабируется как (N_new / N_old)².

Для O(N): время масштабируется как (N_new / N_old).

Используя эти формулы & измеренные данные: (A) при N=10000, сколько времени занимает версия O(N²)? (B) после исправления O(N), используя N=1000 в качестве базовой линии, сколько времени занимает версия O(N) при N=10000? Покажите свои расчёты для обоих.