1. Введение в алгоритмизацию и зачем нужно оценивать эффективность кода
Введение в алгоритмизацию и зачем нужно оценивать эффективность кода
Добро пожаловать в курс «Основы алгоритмов и анализ сложности Big O». Это первая статья, и наша цель сегодня — заложить фундамент. Мы разберемся, что такое алгоритм на самом деле, почему работающий код не всегда является хорошим кодом, и зачем программистам нужна математика для оценки скорости программ.
Что такое алгоритм?
В мире программирования слово «алгоритм» часто окутано ореолом таинственности и сложности. Но если отбросить технический жаргон, алгоритм — это просто четкая последовательность действий, приводящая к желаемому результату.
Мы выполняем алгоритмы каждый день, даже не задумываясь об этом:
* Рецепт блинов: Взять муку, добавить молоко, разбить яйца, перемешать, жарить. Это алгоритм. * Путь на работу: Выйти из дома, дойти до метро, проехать 5 станций, пройти 200 метров. Это тоже алгоритм.
В информатике алгоритм обладает теми же свойствами, но выполняется компьютером. Он принимает какие-то данные на вход (ингредиенты), обрабатывает их по инструкции и выдает результат (готовое блюдо).
!Блок-схема, показывающая алгоритм приготовления кофе из входных ингредиентов.
Пример простейшего алгоритма в коде
Допустим, нам нужно найти сумму всех чисел в списке. Вот как это выглядит на языке Python:
Здесь алгоритм звучит так: «Создай переменную для суммы. Пройдись по каждому числу в списке. Прибавь число к сумме. Верни результат».
Почему просто «работает» — недостаточно?
Представьте, что вам нужно найти номер телефона друга в старом бумажном телефонном справочнике, где записи отсортированы по алфавиту. У вас есть два способа (два алгоритма) сделать это:
Оба алгоритма приведут к результату. Оба работают. Но есть нюанс.
Если в справочнике 1000 страниц: * Алгоритм А в худшем случае заставит вас просмотреть 1000 страниц (если друг на фамилию «Яковлев»). * Алгоритм Б найдет нужную страницу примерно за 10 попыток.
В программировании разница между «просто работает» и «работает эффективно» может означать разницу между программой, которая выполняется за 1 секунду, и программой, которая зависнет на 100 лет.
!Иллюстрация разницы между последовательным перебором и методом деления пополам.
Проблема измерения скорости
Как нам понять, какой алгоритм быстрее? Первая мысль — запустить код и засечь время секундомером.
Однако этот метод ненадежен по нескольким причинам:
Поэтому в Computer Science мы не измеряем скорость в секундах. Мы измеряем ее в количестве операций.
Введение понятия
Чтобы оценить алгоритм объективно, мы используем переменную . Обычно обозначает размер входных данных.
* Если мы сортируем массив из 10 чисел, . * Если мы ищем пользователя в базе из миллиона юзеров, .
Наша задача — понять, как растет количество операций при росте . Это называется асимптотическим анализом.
Рассмотрим простую математическую зависимость. Допустим, количество операций зависит от входных данных следующим образом:
Где: * — общее количество действий, которое совершит компьютер. * — количество элементов, которые нужно обработать (например, прочитать строк). * — какие-то подготовительные действия, которые выполняются всегда (например, объявление переменных).
Если , то операций . Если , операций . Мы видим, что количество работы растет линейно пропорционально .
А теперь представим другой алгоритм, где зависимость такая:
Где: * — количество операций. * — размер входных данных. * — квадрат размера данных (например, вложенный цикл, где мы сравниваем каждый элемент с каждым).
При , операций . Но при , операций уже ! Второй алгоритм при росте данных становится значительно медленнее первого.
Два кита эффективности: Time и Space
Когда мы говорим об эффективности, мы обычно подразумеваем два ресурса:
Часто в программировании существует компромисс, называемый Space-Time Tradeoff (компромисс времени и памяти). Вы можете ускорить программу, используя больше памяти (например, кэшируя результаты), или сэкономить память, пересчитывая данные каждый раз заново (тратя время).
> «Преждевременная оптимизация — корень всех зол». > — Дональд Кнут, The Art of Computer Programming
Эта цитата напоминает нам: сначала нужно написать чистый и понятный код. Оптимизировать его нужно только тогда, когда вы понимаете, что текущая сложность алгоритма не подходит для ваших задач.
Зачем это нужно на практике?
Вы можете спросить: «Зачем мне это знать, если современные компьютеры невероятно быстрые?»
Ответ прост: данные растут быстрее, чем скорость процессоров.
* Если вы пишете скрипт для обработки 50 файлов на своем компьютере, разница между эффективным и неэффективным алгоритмом будет незаметна (0.01 сек против 0.1 сек). * Если вы работаете в бэкенде веб-сервиса, который обрабатывает миллионы запросов в минуту, плохой алгоритм «положит» сервер, и компания потеряет деньги.
Знание основ алгоритмизации и умение оценивать сложность позволяет вам:
Что нас ждет дальше?
В этой статье мы ввели понятие эффективности и выяснили, что секунды — плохая метрика. Нам нужен универсальный язык для описания того, как «тяжелеет» алгоритм с ростом данных.
Этот язык называется Big O Notation (О-большое). В следующей статье мы подробно разберем, что это за буква «О», какие классы сложности существуют и как научиться определять их, просто взглянув на код.
Мы перейдем от абстрактных понятий к конкретным правилам, которые вы сможете применять в своей ежедневной работе.