Алгоритмы и структуры данных

Greedy Algorithms

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

актуальноТекущий рабочий стандарт

Направления: Основы · Алгоритмы и структуры данных

На каждом шаге берём локально лучший вариант и не пересматриваем решение. Работает не всегда — но когда работает, проще и быстрее динамики.

Ключевые тезисы

  • корректна, если у задачи есть свойство матроида или доказуемо работает обмен аргументов.
  • Классические примеры: интервальное , , алгоритм Краскала.
  • Задача о рюкзаке — классический контрпример: там даёт неоптимальный ответ.

Какую задачу решает

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

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

Подробный разбор

10 подтем — объяснения, формулы, примеры и интерактивные графики.

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

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

На практике

Брать на каждом шаге локально лучший вариант и не пересматривать выбор — если доказано, что этот выбор не закрывает путь к оптимуму.

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

[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. Сформулировать критерий одной фразой: что значит «лучший» на текущем шаге.
  2. Предположить, что существует оптимум, отличающийся от жадного решения.
  3. Найти первое место расхождения и обменять там выбор на жадный.
  4. Показать, что после обмена решение осталось допустимым и не стало хуже.

Сложность почти всегда определяется : времени и дополнительной памяти, если сортировать на месте. Сам проход линеен.

Аргумент обмена для интервалов выглядит так. Пусть в оптимальном расписании первой стоит встреча , а берёт — ту, что заканчивается раньше всех. Заменим на : заканчивается не позже , значит все остальные встречи оптимума по-прежнему помещаются. Размер решения не изменился, а совпадение с жадным увеличилось на один шаг. Повторяя обмен, получаем, что жадное решение не хуже оптимального.

Есть и вторая формулировка того же — «жадный идёт впереди»: после каждого шага частичное жадное решение не хуже любого другого решения той же длины. Для интервалов она короче: после взятых встреч освобождает зал раньше всех остальных вариантов.

На практике

Практический фильтр до доказательства: попробуйте сломать критерий контрпримером из трёх-пяти элементов. Минута на это экономит полчаса написания кода, который потом не проходит половину тестов.

Сигнал в условииЧто делать
Интервалы, встречи, брони: максимум или минимум количества по концам отрезков
Локальный выбор очевиден, ресурс невозобновляемпроверить аргумент обмена и писать
Произвольные веса или номиналы, нужен точный оптимумдинамика: неверна
Выбор меняет доступные варианты непредсказуемодинамика или перебор с отсечениями
Данные огромны, точность не обязательна как аппроксимация с оценкой качества
«Минимальное число действий», шаги равнозначнычаще обход в ширину, чем

ЗадачаКлюч Жадный шаг
Максимум непересекающихся встречправый конецберём заканчивающуюся раньше всех
Минимум удалений до непересекающихсяправый конецта же задача с другой стороны
Минимум заловлевый конец, по концамосвободившийся зал переиспользуется
Покрыть отрезок минимальным числом интерваловлевый конециз достижимых берём тянущийся дальше
Слить пересекающиеся интервалылевый конецрасширяем текущий, пока начало не больше конца
Классический алгоритмЖадный шагЧто гарантирует
сливаем два самых редких символаоптимальный префиксный код
Краскалсамое лёгкое ребро без цикламинимальное остовное дерево
ближайшая нераскрытая вершина при неотрицательных весах
по дедлайнамзадача с ближайшим срокомминимум просроченных
Дробный рюкзакпо убыванию цены за килограммоптимум — в отличие от целочисленного

номиналы:  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. Почему в задаче о максимуме встреч сортируют по правому концу, а не по левому или по длительности?
  2. Постройте контрпример к жадному размену для номиналов 1, 5, 12.
  3. Минимум залов: что именно хранится в куче и почему ответ — её максимальный размер?
  4. Чем дробный рюкзак отличается от целочисленного настолько, что в одном верна, а в другом нет?
  5. Сформулируйте аргумент обмена для кода Хаффмана: почему два самых редких символа можно слить первыми?

Чем заменяют

Видеолекции

Тайм-кода именно на эту тему в записях нет. Но глава «Алгоритмы и структуры данных» разобрана в курсе целиком — с той оговоркой, что место в записи придётся искать самому.

Связанные темы

Алгоритмическое мышление

Problem Pattern Recognition85%

Как распознать паттерн задачи · Алгоритмы и структуры данных

Уровень над алгоритмами: по формулировке условия понять, какой приём здесь работает. «Отсортированный массив», «подряд идущие элементы», «ближайший больший справа» — каждая такая фраза указывает на конкретное семейство решений.

Time and Space Complexity85%

Асимптотическая сложность · Алгоритмы и структуры данных

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

Recursion and Divide and Conquer85%

Рекурсия и разделяй-и-властвуй · Алгоритмы и структуры данных

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

Dynamic Programming85%

Динамическое программирование · Алгоритмы и структуры данных

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

Binary Search85%

Бинарный поиск · Алгоритмы и структуры данных

Половинное деление на отсортированных данных: за логарифм шагов вместо линейного перебора. Приём шире поиска — им ищут ответ в любой монотонной задаче.

Проверить себя

Тема встречается в тесте по направлениям — ошибки приведут обратно на эту страницу.

Следующий шагПроверить себя: Алгоритмы и структуры данныхТема встречается в этом тесте 1 раз. Ошибка приведёт обратно на эту страницу — с объяснением, что именно не сошлось.Перейти →

Глава «Алгоритмы и структуры данных» последний раз правилась . Нашли ошибку — напишите.