На каждом шаге берём локально лучший вариант и не пересматриваем решение. Работает не всегда — но когда работает, проще и быстрее динамики.
Ключевые тезисы
- корректна, если у задачи есть свойство матроида или доказуемо работает обмен аргументов.
- Классические примеры: интервальное , , алгоритм Краскала.
- Задача о рюкзаке — классический контрпример: там даёт неоптимальный ответ.
Какую задачу решает
на каждом шаге берёт локально лучший вариант и не пересматривает выбор. Когда это верно, решение получается коротким и быстрым — обычно плюс один проход. Когда неверно, оно тоже короткое и быстрое, только ответ неоптимальный, и на половине тестов это незаметно.
Поэтому содержание темы — не код, а обоснование: аргумент обмена, контрпримеры и понимание, где проходит граница. Самое большое семейство здесь — интервальные задачи, и в них всё решает ключ .
Подробный разбор
«Сколько встреч можно провести в одной переговорной», «сколько залов нужно на всё расписание», «как закодировать текст покороче» — множество допустимых решений здесь экспоненциально. Перебор невозможен, а динамика по подмножествам упирается в память уже на сорока элементах.
При этом у таких задач часто есть простой критерий выбора, который «выглядит правильным». Проблема в том, что выглядеть правильным и быть правильным — разные вещи, и неверный даёт не ошибку, а правдоподобный неоптимальный ответ.
Брать на каждом шаге локально лучший вариант и не пересматривать выбор — если доказано, что этот выбор не закрывает путь к оптимуму.
Вторая половина предложения и есть весь приём. Без неё это не алгоритм, а догадка; с ней — самое быстрое решение задачи, обычно плюс один проход.
[1,3] #####
[2,4] #####
[3,5] #####
[5,7] #####
1 2 3 4 5 6 7
сортировка по правому концу: [1,3] [2,4] [3,5] [5,7]
берём [1,3]
[2,4] пересекается -- пропускаем
берём [3,5]
берём [5,7]
итого 3 встречи| Шаг | Интервал | Свободно с | Решение |
|---|---|---|---|
| 1 | [1,3] | 1 | берём, освободится в 3 |
| 2 | [2,4] | 3 | начало 2 < 3 — пропускаем |
| 3 | [3,5] | 3 | берём, освободится в 5 |
| 4 | [5,7] | 5 | берём, освободится в 7 |
Соглашение о границах здесь нужно проговорить до кода: считаются ли пересекающимися и . Для встреч — нет (одна заканчивается, другая начинается), для занятых участков дороги — да. На этом знаке сравнения ломается больше решений, чем на самой идее.
intervals.sort(key=lambda x: x[1]) # по правому концу
taken = 0
last_end = -inf
for start, end in intervals:
if start >= last_end: # не пересекается с уже взятым
taken += 1
last_end = end
return taken- Сформулировать критерий одной фразой: что значит «лучший» на текущем шаге.
- Предположить, что существует оптимум, отличающийся от жадного решения.
- Найти первое место расхождения и обменять там выбор на жадный.
- Показать, что после обмена решение осталось допустимым и не стало хуже.
Сложность почти всегда определяется : времени и дополнительной памяти, если сортировать на месте. Сам проход линеен.
Аргумент обмена для интервалов выглядит так. Пусть в оптимальном расписании первой стоит встреча , а берёт — ту, что заканчивается раньше всех. Заменим на : заканчивается не позже , значит все остальные встречи оптимума по-прежнему помещаются. Размер решения не изменился, а совпадение с жадным увеличилось на один шаг. Повторяя обмен, получаем, что жадное решение не хуже оптимального.
Есть и вторая формулировка того же — «жадный идёт впереди»: после каждого шага частичное жадное решение не хуже любого другого решения той же длины. Для интервалов она короче: после взятых встреч освобождает зал раньше всех остальных вариантов.
Практический фильтр до доказательства: попробуйте сломать критерий контрпримером из трёх-пяти элементов. Минута на это экономит полчаса написания кода, который потом не проходит половину тестов.
| Сигнал в условии | Что делать |
|---|---|
| Интервалы, встречи, брони: максимум или минимум количества | по концам отрезков |
| Локальный выбор очевиден, ресурс невозобновляем | проверить аргумент обмена и писать |
| Произвольные веса или номиналы, нужен точный оптимум | динамика: неверна |
| Выбор меняет доступные варианты непредсказуемо | динамика или перебор с отсечениями |
| Данные огромны, точность не обязательна | как аппроксимация с оценкой качества |
| «Минимальное число действий», шаги равнозначны | чаще обход в ширину, чем |
| Задача | Ключ | Жадный шаг |
|---|---|---|
| Максимум непересекающихся встреч | правый конец | берём заканчивающуюся раньше всех |
| Минимум удалений до непересекающихся | правый конец | та же задача с другой стороны |
| Минимум залов | левый конец, по концам | освободившийся зал переиспользуется |
| Покрыть отрезок минимальным числом интервалов | левый конец | из достижимых берём тянущийся дальше |
| Слить пересекающиеся интервалы | левый конец | расширяем текущий, пока начало не больше конца |
| Классический алгоритм | Жадный шаг | Что гарантирует |
|---|---|---|
| сливаем два самых редких символа | оптимальный префиксный код | |
| Краскал | самое лёгкое ребро без цикла | минимальное остовное дерево |
| ближайшая нераскрытая вершина | при неотрицательных весах | |
| по дедлайнам | задача с ближайшим сроком | минимум просроченных |
| Дробный рюкзак | по убыванию цены за килограмм | оптимум — в отличие от целочисленного |
номиналы: 1, 3, 4 набрать 6
жадно: 4 + 1 + 1 = три монеты
оптимум: 3 + 3 = две монетырюкзак 10 кг
A: 6 кг, 30 -> 5.0 за кг
B: 5 кг, 20 -> 4.0 за кг
C: 5 кг, 20 -> 4.0 за кг
жадно: берём A, дальше ничего не влезает = 30
оптимум: B + C = 40- Привычные номиналы обманывают. На наборе 1, 2, 5, 10 верна, поэтому ошибка проходит все «обычные» тесты.
- Неверный ключ сортировки. По длительности или по началу задача о максимуме встреч ломается — контрпример строится за секунды.
- Границы интервалов. Строгое или нестрогое сравнение концов меняет ответ.
- Починка критерия вторым критерием. Если первый контрпример нашёлся, найдётся и второй: задача просто не жадная.
| Приём | Когда он лучше | Цена |
|---|---|---|
| Динамика | ломается контрпримером, нужен точный ответ | память и время на таблицу |
| Обход в ширину | минимальное число равнозначных шагов | состояний |
| Префиксные суммы | вопрос про число одновременных событий | разностный массив вместо кучи |
| Локальный поиск и эвристики | точный оптимум недостижим в принципе | нет гарантий, зато работает на огромных данных |
в этом ряду — не исключение, а иллюстрация: это , и ломается он ровно там, где ломается жадность, — на отрицательных весах «ближайшая» вершина перестаёт быть окончательно посчитанной.
- Почему в задаче о максимуме встреч сортируют по правому концу, а не по левому или по длительности?
- Постройте контрпример к жадному размену для номиналов 1, 5, 12.
- Минимум залов: что именно хранится в куче и почему ответ — её максимальный размер?
- Чем дробный рюкзак отличается от целочисленного настолько, что в одном верна, а в другом нет?
- Сформулируйте аргумент обмена для кода Хаффмана: почему два самых редких символа можно слить первыми?
Чем заменяют
Видеолекции
Тайм-кода именно на эту тему в записях нет. Но глава «Алгоритмы и структуры данных» разобрана в курсе целиком — с той оговоркой, что место в записи придётся искать самому.
Связанные темы
Алгоритмическое мышление
Problem Pattern Recognition85%
Как распознать паттерн задачи · Алгоритмы и структуры данныхУровень над алгоритмами: по формулировке условия понять, какой приём здесь работает. «Отсортированный массив», «подряд идущие элементы», «ближайший больший справа» — каждая такая фраза указывает на конкретное семейство решений.
Time and Space Complexity85%
Асимптотическая сложность · Алгоритмы и структуры данныхСпособ сравнить алгоритмы, не запуская их: как растёт время работы с ростом входа. Отбрасывает константы и оставляет то, что решает на больших данных.
Recursion and Divide and Conquer85%
Рекурсия и разделяй-и-властвуй · Алгоритмы и структуры данныхЗадача сводится к таким же задачам меньшего размера. Основа сортировки слиянием, быстрой сортировки, обхода деревьев и почти всей динамики.
Dynamic Programming85%
Динамическое программирование · Алгоритмы и структуры данныхПеребор, в котором каждое подсостояние считается ровно один раз. Превращает экспоненциальный перебор в полиномиальный, если подзадачи перекрываются.
Binary Search85%
Бинарный поиск · Алгоритмы и структуры данныхПоловинное деление на отсортированных данных: за логарифм шагов вместо линейного перебора. Приём шире поиска — им ищут ответ в любой монотонной задаче.
Всё, что названо в описании, разобрано здесь же или в соседней теме — переходы под каждым определением.
Аргумент обмена
Жадный выбор подставляется в оптимум, не ухудшая его. Основной способ доказательства.
Разбор ниже: Почему это работаетКлюч сортировки
В интервальных задачах он и есть решение: конец, начало или длительность.
Разбор ниже: Вариации и параметрыКонтрпример
Пять элементов, на которых проигрывает. Минута поиска экономит полчаса.
Разбор ниже: Ограничения и типичные ошибкиЖадность как аппроксимация
Когда точный оптимум недостижим, жадный выбор даёт решение с оценкой качества.
Разбор ниже: Сравнение с соседямиТема встречается в тесте по направлениям — ошибки приведут обратно на эту страницу.
Глава «Алгоритмы и структуры данных» последний раз правилась . Нашли ошибку — напишите.