В этом параграфе вы разберётесь с жадными алгоритмами — стратегиями, которые на каждом шаге выбирают «самое выгодное» решение в надежде на общий успех. Мы увидим, когда такая тактика действительно работает, а когда — приводит к ошибкам. На примере задачи о бронировании переговорок вы научитесь оценивать и доказывать корректность жадных решений.

Ключевые вопросы параграфа

  • Что такое жадный алгоритм и в чём его идея?
  • Почему не всякая жадная стратегия приводит к оптимальному решению?
  • Как формализовать и доказать корректность жадного алгоритма?

Итерационный принцип алгоритмов

Многие алгоритмы — это итерационные процедуры: с каждым повтором они делают выбор из определенного количества вариантов.

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

Принцип работы жадного алгоритма

При каждом повторе жадный алгоритм выбирает «самый привлекательный» вариант. Например, самый большой номинал из доступных монет. В случае с американскими деньгами Change использует номиналы четвертак (25 центов), дайм (10 центов), никель (5 центов) и пенни (1 цент), чтобы выдать сдачу, в данном порядке. Разумеется, мы показывали, как такой «жадный» подход приводит к неправильным результатам при добавлении монет некоторых новых номиналов.

Пример с телефоном

В примере с телефоном «жадная» стратегия состояла бы в том, чтобы идти на звук, пока вы его не найдете. Но есть проблема: между вами и телефоном может оказаться стена (или хрупкая ваза). К сожалению, такие сложности часто возникают и в реальных задачах.

Во многих случаях «жадный» подход выглядит естественным и очевидным, но может оказаться неправильным.

Задача «Бронирование переговорки»

В задаче «Бронирование переговорки» вам дается несколько временных отрезков, и нужно выбрать как можно больше отрезков таким образом, чтобы ни один из них не пересекался с другим (отрезки пересекаются, если у них есть общая точка).

Название задачи основано на следующей гипотетической ситуации. Представьте, что у вас есть зал для переговоров, и вам присылают заявки на бронирование 11 компаний.

1 01:00PM—05:00PM
2 01:45PM—03:00PM
3 01:00PM—02:00PM
4 05:45PM—06:15PM
5 01:45PM—02:15PM
6 04:00PM—04:30PM
7 03:00PM—04:00PM
8 03:00PM—05:45PM
9 01:30PM—03:15PM
10 02:30PM—03:30PM
11 04:45PM—05:30PM

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

ne

Так как мы говорим о «жадных» стратегиях, давайте поэкспериментируем с разными «наиболее выгодными» подходами. Интуиция может нам подсказать, что нужно выбрать самый короткий отрезок, удалить пересекающиеся отрезки и повторить данное действие.

Остановитесь и подумайте:
Всегда ли это приведет к оптимальному решению?

Ответ

Не факт. В примере ниже такая «жадная» стратегия предлагает решение из одного отрезка посередине. Однако есть и решение из двух отрезков, которые не накладываются друг на друга.

ne

Возможно, логичнее было бы выбрать отрезок слева (тот, что начинается раньше всех), убрать все остальные и повторить данное действие.

Остановитесь и подумайте:
Всегда ли это приведет к оптимальному решению?

Ответ

Увы, но нет.

ne

Остановитесь и подумайте:
Может, есть и другие «жадные» подходы?

Ответ

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

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

Упражнение

Докажите, что если набор непересекающихся отрезков не содержит чемпионский отрезок, то при замене первого отрезка в этом наборе на чемпионский мы получаем набор непересекающихся отрезков.

Вот мы и нашли оптимальную «жадную» стратегию. И действительно, если существует решение задачи, включающее в себя чемпионский интервал, мы можем выбрать этот интервал на первом шаге и решить задачу выбора непересекающихся отрезков из оставшихся.

В нашем примере алгоритм работает следующим образом.

  • Выбрать сегмент и отбросить сегменты , , , и .
  • Выбрать сегмент и отбросить сегменты и .
  • Выбрать сегмент .
  • Выбрать сегмент .
  • Выбрать сегмент .

ne

Что дальше

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

Далее — динамическое программирование. Это подход, который позволяет решать сложные задачи, разбивая их на подзадачи и используя уже найденные решения. Вы узнаете, как избежать повторных вычислений и добиться эффективности там, где перебор и жадность не справляются.

А пока вы не ушли дальше — закрепите материал на практике:

  • Отметьте, что урок прочитан, при помощи кнопки ниже.
  • Пройдите мини-квиз, чтобы проверить, насколько хорошо вы усвоили тему.
  • Перейдите к задачам этого параграфа и потренируйтесь.
  • Перед этим — загляните в короткий гайд о том, как работает система проверки.

Хотите обсудить, задать вопрос или не понимаете, почему код не работает? Мы всё предусмотрели — вступайте в сообщество Хендбука! Там студенты помогают друг другу разобраться.

Ключевые выводы параграфа

  • Жадный алгоритм на каждом шаге выбирает наиболее выгодный вариант, не заглядывая вперёд. Такой подход прост и работает быстро, но не всегда даёт оптимальное решение.
  • Чтобы жадная стратегия была корректной, необходимо доказать, что локальный выбор всегда ведёт к глобально лучшему результату.
  • В некоторых задачах — например, при выборе максимального числа непересекающихся отрезков — удаётся найти и обосновать корректную жадную стратегию.
Чтобы добавить в заметки выделенный текст, нажмите Ctrl + E
Предыдущий параграф6.1. Полный перебор и оптимизация перебора

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

Следующий параграф6.3. Динамическое программирование

Здесь мы найдём оптимальную стратегию действий в игре «Камни». Решив большее количество меньших экземпляров задачи, мы сможем гарантированно определить, какой из игроков непременно победит.