Неделя 5 · Foundation

Алгоритмы

Теперь у нас есть данные в массиве. Но как их быстро найти и упорядочить? На этом уроке мы изучим алгоритмы: выполним одну задачу разными способами и увидим, какой из них быстрее. Поиск, сортировку и эффективность мы понаблюдаем в живых визуализаторах.

Что вы узнаете на этом уроке

Поймёте, что такое алгоритм и почему важна эффективность
Увидите, как работают линейный и двоичный поиск
Оцените скорость алгоритма с помощью Big-O
Пошагово понаблюдаете за сортировкой пузырьком
Найдёте и поставите на место наименьший элемент сортировкой выбором
Выберете правильный алгоритм в зависимости от задачи

5.1 Что такое алгоритм?

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

А в программировании одну задачу часто можно выполнить несколькими разными алгоритмами. Например, найти какое-то имя в телефонной книге. Можно листать с первой страницы до последней, а можно открыть книгу посередине и определять, в какой она половине. Оба способа дают верный результат, но один намного быстрее.

Именно здесь становится важна эффективность (efficiency): за сколько шагов алгоритм завершает работу. На малых данных разница незаметна, но в списке из миллиона элементов медленный алгоритм может работать часами, а быстрый — за секунды.

На этом уроке мы рассмотрим два классических вида задач: найти какое-то значение в массиве (поиск) и упорядочить массив (сортировка). Для каждого из них мы вживую понаблюдаем за несколькими алгоритмами.

5.2 Линейный поиск

Линейный поиск (linear search) — самый простой способ: начиная с начала массива, мы по очереди проверяем каждый элемент, пока не найдём искомое значение или не дойдём до конца. Проход по массиву с помощью цикла из прошлой недели — это именно оно.

Ниже измените искомое число и кнопкой Следующий шаг понаблюдайте за каждой проверкой. Жёлтая ячейка — проверяемый сейчас элемент, зелёная ячейка — найденное место:

Наблюдатель линейного поиска шагайте
ищем:

В худшем случае (значение в конце или его вообще нет) мы проверяем все элементы. Значит, для 100 элементов может понадобиться 100 шагов, а для миллиона — миллион шагов. Это медленно, но зато он работает и на неотсортированном массиве, вот чем он удобен.

5.3 Двоичный поиск

Если массив отсортирован (упорядочен от меньшего к большему), есть гораздо более умный способ: двоичный поиск (binary search). Идея похожа на телефонную книгу: мы открываем середину. Если искомое значение больше среднего, на левую половину мы вообще не смотрим; если меньше — не смотрим на правую. На каждом шаге оставшийся диапазон сокращается вдвое.

Ниже отсортированный массив. Выберите значение и наблюдайте: светлая ячейка — ещё рассматриваемый диапазон, жёлтая ячейка — середина, зелёная ячейка — найденное место:

Наблюдатель двоичного поиска шагайте
ищем:

Вот в чём магия: массив из 8 элементов двоичный поиск просматривает максимум за 3 шага, потому что каждый раз мы отбрасываем половину (8 → 4 → 2 → 1). А для миллиона элементов хватит всего 20 шагов! Но есть одно условие: массив должен быть заранее отсортирован.

Сделайте прогноз

Сколько шагов максимум сделает двоичный поиск в отсортированном массиве из 16 элементов?

16 шагов
4 шага
8 шагов

5.4 Эффективность: Big-O

Чтобы сравнивать скорость алгоритмов, программисты используют обозначение Big-O. Оно показывает не точное время, а то, как растёт число шагов при увеличении объёма данных (n). Самые распространённые:

  • O(1) постоянная: каким бы ни был объём, одно и то же число шагов. Самая быстрая
  • O(log n) логарифмическая: при удвоении объёма добавляется всего один шаг. Так работает двоичный поиск
  • O(n) линейная: равна объёму. Так работает линейный поиск
  • O(n^2) квадратичная: квадрат объёма. Так работают простые сортировки, медленно

Ниже выберите n и сравните, сколько шагов требует каждый вид скорости:

Счётчик шагов Big-O выберите n
n =
В Big-O мелкие детали отбрасываются. Важно, насколько хуже становится алгоритм при росте объёма. Алгоритм O(n^2) на 1000 элементов делает миллион шагов, а O(log n) — всего 10. Именно эта разница решает всё.

5.5 Что такое сортировка и зачем она нужна?

Под сортировкой (sorting) понимается размещение элементов массива в некотором порядке, например от меньшего к большему. Контакты телефона по алфавиту, товары магазина по цене, результаты экзамена по баллам — всё это сортировка.

Сортировка важна ещё по одной причине: она ускоряет другие алгоритмы. Вспомните, двоичный поиск работал только на отсортированном массиве. Значит, отсортировав один раз, потом мы можем искать тысячи раз быстро.

Способов сортировки много. Мы рассмотрим два простых, но прекрасных для обучения способа: сортировку пузырьком и выбором. Оба мы понаблюдаем вживую в виде столбчатой диаграммы, где высота обозначает значение.

5.6 Сортировка пузырьком

Сортировка пузырьком (bubble sort) — самый легко понимаемый способ. Идея: мы сравниваем два соседних элемента, и если левый больше, меняем их местами. Так мы проходим по массиву, затем снова с начала, пока не понадобится ни одной перестановки. На каждом проходе самый большой элемент, словно пузырь, «всплывает» на своё последнее место.

Кнопкой Следующий шаг понаблюдайте за каждым сравнением и перестановкой. Жёлтые столбцы — те, что сравниваются сейчас, оранжевые — те, что меняются местами, зелёные столбцы — нашедшие своё место:

Визуализатор сортировки пузырьком шагайте
сравнивается меняется местами отсортировано

Сортировка пузырьком проста, но медленна: поскольку она снова и снова сравнивает каждую пару, её эффективность O(n^2). То есть для 100 элементов примерно 10 000 операций. Для малых массивов хороша, для больших не годится.

5.7 Сортировка выбором

Сортировка выбором (selection sort) — другая стратегия: каждый раз мы находим наименьший элемент из оставшейся неотсортированной части и ставим его вперёд. На первом проходе находим наименьший во всём массиве и ставим на позицию 0, на втором — наименьший из оставшихся на позицию 1, и так далее.

Наблюдайте ниже: жёлтый столбец — проверяемый сейчас элемент, оранжевый — найденный к этому моменту наименьший кандидат, зелёные столбцы — нашедшие своё место:

Визуализатор сортировки выбором шагайте
проверяется наименьший кандидат поставлен на место

Сортировка выбором тоже O(n^2): она тоже для каждого элемента просматривает остальные. Но она делает меньше перестановок (всего одну за проход), поэтому в некоторых случаях практичнее пузырьковой.

5.8 Сравнение алгоритмов

Теперь сравним всё увиденное в одной таблице. Обратите внимание: скорость зависит не только от алгоритма, но и от условия (например, двоичный поиск требует отсортированного массива).

АлгоритмЗадачаСкоростьУсловие
Линейный поискнайтиO(n)без условий
Двоичный поискнайтиO(log n)должен быть отсортирован
Сортировка пузырькомсортироватьO(n^2)без условий
Сортировка выборомсортироватьO(n^2)без условий

Есть и более быстрые способы сортировки (например, сортировка слиянием, O(n log n)), их мы рассмотрим ниже и в следующих неделях. Пока основной вывод: O(log n) очень быстра, O(n) средняя, а O(n^2) на большом объёме медленна.

5.9 Практика: какой алгоритм выбрать?

Лучшего алгоритма не существует, выбор зависит от ситуации. Вот простые правила:

  • Если массив маленький или вы ищете всего один раз: линейного поиска достаточно, он проще
  • Если вы ищете в одном массиве много раз: сначала отсортируйте один раз, затем используйте двоичный поиск
  • Если данные не отсортированы и их много: выбирайте не простые сортировки, а более быстрый способ (O(n log n))
  • Для обучения или небольшой задачи: сортировки пузырьком и выбором отлично подходят, их код короткий
На практике программисты чаще всего не пишут сортировку и поиск сами: в каждом языке есть готовые, очень быстрые функции. Но понимать, что происходит у них внутри, необходимо, чтобы делать правильный выбор. Именно поэтому мы разобрали их вручную.

5.10 Глубже advanced

Вы изучили основные алгоритмы. Теперь кратко взглянем ещё на две идеи, которые мы глубже изучим в следующих неделях.

Разделяй и властвуй

Самые быстрые сортировки (например, сортировка слиянием, merge sort) работают иначе: массив делится на две равные части, каждая половина сортируется отдельно, затем две отсортированные половины объединяются. Этот подход даёт скорость O(n log n), что намного лучше O(n^2). Его называют «разделяй и властвуй» (divide and conquer).

Рекурсия

Шаг «отсортируй каждую половину отдельно» выше интересен: функция вызывает сама себя на меньшей части. Это называется рекурсией — вызов функцией самой себя. Это мощное, но тонкое понятие мы полностью изучим на 8-й неделе.

Словарь терминов

алгоритмточная, пошаговая инструкция решения задачи.
линейный поискпоиск проверкой элементов по одному, O(n).
двоичный поискпоиск в отсортированном массиве делением диапазона пополам, O(log n).
Big-Oобозначение того, как растёт число шагов при увеличении объёма.
сортировкаразмещение элементов в порядке (например по возрастанию).
эффективностьза сколько шагов алгоритм выполняет задачу.

5.11 Тест знаний

16 вопросов. Чтобы завершить неделю, ответьте правильно как минимум на 11 из них.

Поздравляем! Неделя 5 завершена

Теперь вы можете говорить на языке алгоритмов: вы понимаете поиск, сортировку и эффективность Big-O. Это один из важнейших шагов на пути к становлению сильным программистом.

Следующая неделя: Память и указатели (адрес, указатель, разыменование, malloc и free).

Перейти к следующему модулю