Files
hello-algo/ru/docs/chapter_greedy/greedy_algorithm.md
2026-01-20 15:08:42 +08:00

16 KiB
Raw Blame History

Жадные алгоритмы

Жадный алгоритм -- это распространенный метод решения задач оптимизации. Его основная идея заключается в том, чтобы на каждом этапе принятия решения выбирать наиболее оптимальный на данный момент вариант, т. е. с жадностью принимать локально оптимальные решения в надежде получить глобально оптимальное решение. Жадные алгоритмы просты и эффективны, и они находят широкое применение в решении многих практических задач.

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

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

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

!!! question

Дано *n* видов монет, номинал *i*-й монеты равен *coins*[*i* - 1], целевая сумма *amt*, каждый вид монет можно выбирать многократно. Найти минимальное количество монет, необходимое для получения целевой суммы. Если получить целевую сумму невозможно, вернуть -1.

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

Жадная стратегия для задачи о размене монет

Ниже приведен код реализации.

[file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy}

Вы можете невольно воскликнуть: «Эврика!» Жадный алгоритм решает задачу размена монет всего за десяток строк кода.

Преимущества и ограничения жадных алгоритмов

Жадные алгоритмы не только просты в реализации, но и обычно очень эффективны. Если в приведенном выше коде обозначить минимальный номинал монеты как min(coins), то жадный выбор выполняется не более amt / min(coins) раз. Тогда временная сложность составляет O(amt / min(coins)). Это на порядок меньше временной сложности решения с использованием динамического программирования O(n × amt).

Однако для некоторых комбинаций номиналов монет жадный алгоритм не сможет найти оптимальное решение. На следующем рисунке приведены два примера.

  • Положительный пример coins = [1, 5, 10, 20, 50, 100]: при данной комбинации монет для любого amt жадный алгоритм сможет найти оптимальное решение.
  • Отрицательный пример coins = [1, 20, 50]: если amt = 60, жадный алгоритм найдет комбинацию 50 + 1 × 10, всего 11 монет. Но динамическое программирование может найти оптимальное решение 20 + 20 + 20, всего 3 монеты.
  • Отрицательный пример coins = [1, 49, 50]: если amt = 98, жадный алгоритм найдет комбинацию 50 + 1 × 48, всего 49 монет. Но динамическое программирование может найти оптимальное решение 49 + 49, всего 2 монеты.

Примеры, когда жадный алгоритм не может найти оптимальное решение

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

В общем случае жадные алгоритмы применимы в следующих двух ситуациях:

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

Свойства жадных алгоритмов

Итак, возникает вопрос: какие задачи подходят для решения с помощью жадного алгоритма? Или, иначе говоря, в каких случаях жадный алгоритм может гарантировать нахождение оптимального решения?

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

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

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

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

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

!!! quote

Существует статья, в которой представлен алгоритм временной сложности *O*(*n*³) для определения того, можно ли использовать жадный алгоритм для нахождения оптимального решения для любой суммы при данной комбинации монет.

Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234.

Этапы решения задач жадным алгоритмом

Процесс решения жадных задач можно разделить на следующие три этапа:

  1. анализ задачи: изучение и понимание характеристик задачи, включая определение состояния, цели оптимизации и ограничения. Этот этап также присутствует в методах поиска с возвратом и динамического программирования;
  2. определение жадной стратегии: определение того, как делать жадный выбор на каждом шаге. Эта стратегия позволяет уменьшать размер задачи на каждом шаге и в конечном итоге решить всю задачу;
  3. доказательство корректности: обычно требуется доказать наличие свойства жадного выбора и оптимальной подструктуры задачи. Этот этап может потребовать использования математических доказательств, таких как метод математической индукции или доказательство от противного.

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

  • Жадные стратегии для различных задач могут значительно различаться. Для многих задач жадная стратегия очевидна, и ее можно определить с помощью общего размышления и эмпирических проб. Однако для некоторых сложных задач жадная стратегия может оказаться очень скрытой, что потребует значительного опыта в решении задач и навыков работы с алгоритмами.
  • Некоторые жадные стратегии могут быть обманчивыми. Бывает, жадная стратегия разработана с полной уверенностью в ее правильности, код написан и отправлен на выполнение. Но оказывается, что некоторые тестовые примеры не проходят проверку на корректность. Это происходит потому, что разработанная жадная стратегия является лишь частично правильной, как в случае с задачей о размене монет, описанной выше.

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

Тем не менее доказательство корректности может оказаться непростой задачей. Если нет ясности, обычно выбирается отладка кода на основе тестовых примеров с постепенной модификацией и проверкой жадной стратегии.

Типичные задачи для жадного алгоритма

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

  • Задача о размене монет: при некоторых комбинациях монет жадный алгоритм всегда может получить оптимальное решение.
  • Задача о расписании интервалов: пусть у вас есть несколько задач, каждая из которых выполняется в течение определенного времени, и ваша цель -- выполнить как можно больше задач. Если каждый раз выбирать задачу с наименьшим временем окончания, то жадный алгоритм может дать оптимальное решение.
  • Задача о дробном рюкзаке: дана группа предметов и вместимость. Ваша цель -- выбрать группу предметов так, чтобы