mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-04 11:44:21 +00:00
91 lines
16 KiB
Markdown
91 lines
16 KiB
Markdown
# Жадные алгоритмы
|
||
|
||
<u>Жадный алгоритм</u> -- это распространенный метод решения задач оптимизации. Его основная идея заключается в том, чтобы на каждом этапе принятия решения выбирать наиболее оптимальный на данный момент вариант, т. е. с жадностью принимать локально оптимальные решения в надежде получить глобально оптимальное решение. Жадные алгоритмы просты и эффективны, и они находят широкое применение в решении многих практических задач.
|
||
|
||
Жадные алгоритмы и динамическое программирование часто используются для решения задач оптимизации. Между ними есть некоторые сходства, например оба метода зависят от свойств оптимальной подструктуры, но их принципы работы различны.
|
||
|
||
- Динамическое программирование для получения текущего решения учитывает все предыдущие решения и использует решения предыдущих подзадач для построения решения текущей подзадачи.
|
||
- Жадный алгоритм не учитывает предыдущие решения, а просто движется вперед, делая жадные выборы и постепенно сокращая область задачи, пока она не будет решена.
|
||
|
||
Чтобы лучше понять принцип работы жадного алгоритма, рассмотрим его применение к задаче о размене монет. Она уже была рассмотрена в разделе «Задача о полном рюкзаке», и, вероятно, вы с ней уже знакомы.
|
||
|
||
!!! question
|
||
|
||
Дано *n* видов монет, номинал *i*-й монеты равен *coins*[*i* - 1], целевая сумма *amt*, каждый вид монет можно выбирать многократно. Найти минимальное количество монет, необходимое для получения целевой суммы. Если получить целевую сумму невозможно, вернуть -1.
|
||
|
||
Жадная стратегия, применяемая в этой задаче, показана на следующем рисунке. Для заданной целевой суммы **мы жадно выбираем монету, которая не превышает и наиболее близка к этой сумме**, и повторяем этот шаг, пока не будет достигнута целевая сумма.
|
||
|
||

|
||
|
||
Ниже приведен код реализации.
|
||
|
||
```src
|
||
[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. **доказательство корректности**: обычно требуется доказать наличие свойства жадного выбора и оптимальной подструктуры задачи. Этот этап может потребовать использования математических доказательств, таких как метод математической индукции или доказательство от противного.
|
||
|
||
Определение жадной стратегии является ключевым этапом решения задачи, но его реализация может быть непростой по следующим причинам.
|
||
|
||
- **Жадные стратегии для различных задач могут значительно различаться**. Для многих задач жадная стратегия очевидна, и ее можно определить с помощью общего размышления и эмпирических проб. Однако для некоторых сложных задач жадная стратегия может оказаться очень скрытой, что потребует значительного опыта в решении задач и навыков работы с алгоритмами.
|
||
- **Некоторые жадные стратегии могут быть обманчивыми**. Бывает, жадная стратегия разработана с полной уверенностью в ее правильности, код написан и отправлен на выполнение. Но оказывается, что некоторые тестовые примеры не проходят проверку на корректность. Это происходит потому, что разработанная жадная стратегия является лишь частично правильной, как в случае с задачей о размене монет, описанной выше.
|
||
|
||
Для обеспечения корректности необходимо провести строгое математическое доказательство жадной стратегии, **обычно с использованием метода доказательства от противного или метода математической индукции**.
|
||
|
||
Тем не менее доказательство корректности может оказаться непростой задачей. Если нет ясности, обычно выбирается отладка кода на основе тестовых примеров с постепенной модификацией и проверкой жадной стратегии.
|
||
|
||
## Типичные задачи для жадного алгоритма
|
||
|
||
Жадный алгоритм часто применяется в задачах оптимизации, удовлетворяющих свойству жадного выбора и оптимальной подструктуре. Ниже перечислены некоторые типичные задачи для жадного алгоритма.
|
||
|
||
- **Задача о размене монет**: при некоторых комбинациях монет жадный алгоритм всегда может получить оптимальное решение.
|
||
- **Задача о расписании интервалов**: пусть у вас есть несколько задач, каждая из которых выполняется в течение определенного времени, и ваша цель -- выполнить как можно больше задач. Если каждый раз выбирать задачу с наименьшим временем окончания, то жадный алгоритм может дать оптимальное решение.
|
||
- **Задача о дробном рюкзаке**: дана группа предметов и вместимость. Ваша цель -- выбрать группу предметов так, чтобы |