Лекции / 4_Динамическое_программирование_Жадные_алгоритмы_ipynb_Colab
.pdf
- количество золота в клетке в строке i и столбце j
Из за большого количества перекрывающихся подзадач, то при рекурсивном решении нужно использовать мемоизацию.
Жадные алгоритмы
Жадные алгоритмы — класс алгоритмов для решения задач оптимизации. Методология
решения задач сходна с методологией решения задач динамического программирования и базируется на разбиении задачи на более простые подзадачи. При этом разбиение на подзадачи должно удовлетворять следующим критериям:
Подзадачи должны иметь общую структуру и для их решения используется
однотипный алгоритм.
Главный критерий выделяющий именно жадные алгоритмы — для каждой подзадачи выбирается локально оптимальное решение (на каждом шаге самое выгодное для именно этого шага решение).
Интересной особенностью жадных алгоритмов является то что они применяются как для получения точного оптимального решения, так и для получения решения близкого к оптимальному. В последнем случае основным критерием является простота
реализации и гораздо более высокая скорость работы каждого алгоритма по сравнению с алгоритмом который дает оптимальное решение.
Критерии задачи решаемой с помощью жадных алгоритмов
Задачи которые можно решить с помощью жадных алгоритмов можно определить с помощью следующих признаков:
Задача относится к задачам оптимизации. Наличие оптимальной подструктуры.
Возможно выбора локального оптимального решения (выбор не потребует пересчета шагов сделанных ранее, после выбора решение должно существовать).
Выбор локального оптимума на каждом шаге приводит к оптимальному решению общей задачи.
Примерный алгоритм решения
После определения факта, что задание может быть решено с помощью применения жадного алгоритма, можно придерживаться следующего алгоритма решения задачи:
1.Описать структуру оптимального решения.
2.Определить способ выбора локального оптимума.
3.Вычислить значения, соответствующие оптимальному решению.
Задача о размене
У вас есть некоторая сумма которую нужно разменять монетами. Существуют монеты номиналом 1, 5, 10, 25, 50 центов. Нужно разменять ее используя наименьшее количество монет.
Сумма (в центах) |
Вариант размена |
30 |
25, 5 |
70 |
50, 10, 10 |
82 |
50, 25, 5, 1, 1 |
Жадный подход заключается в том, чтобы на каждом шаге брать монету наибольшего номинала, не превышающую оставшуюся сумму.
Применимость жадного алгоритма
Построим граф пространства решений в зависимости от этого выбора номинала монеты на каждом шаге.
Применимость жадного алгоритма
Как можно видеть, присутствуют набор факторов которые указывают на то, что эта задача относится к задачам динамического программирования. Но также присутствует факт, что выбор на каждом шаге локального оптимума (выбор монет с самым высоким номиналом доступным для этого шага) приводит к оптимальному решению. Поэтому эту задачу можно решить используя подход на основе жадного алгоритма.
Реализация на Python
def exchange(money, coins):
"""
Функцияразменасуммымонетамис использованиемжадногоалгоритма.
Аргументы: money:суммадляразмена(в центах)
coins:списокдоступныхноминаловмонет (отсортированпо убыванию)
Возвращает:
Кортеж(остатоксловарь, количеством монеткаждогономинала)
""" |
|
|
|
|
result= {} |
# Словарьдляхранениярезультата{номинал: |
: |
количество} |
|
# Проходимпо всемноминаламмонет(отбольшегок |
|
меньшему) |
||
for coin |
in coins: |
|
|
|
# Проверяемможем, ли взятьхотябы однумонету |
|
этогономинала |
||
if money// coin> |
0: |
|
|
|
#Записываемколичеством нетданногономинала result[coin]= money// coin
#Уменьшаемоставшуюсуммуя
money= money% coin
# Еслисуммаполностьюразменянавыходимиз |
цикла |
|||
if money== 0: |
|
|
||
|
|
break |
|
|
# Возвращаемостаток(0 еслиразменялиполностью) |
|
и результат |
||
return (money,result) |
|
|
||
# Набормонет(отсортированпо убываниюдля |
жадногоалгоритма) |
|||
coins= [ 50, 25, 10, 5, 1] |
|
|
||
# Тестируемна разныхсуммах |
|
|
||
print(f"Размен11: |
{exchange(11, coins)} ") |
|
|
|
print(f"Размен30: |
{exchange(30, coins)} ") |
|
|
|
print(f"Размен70: |
{exchange(70, coins)} ") |
|
|
|
print(f"Размен82: |
{exchange(82, coins)} ") |
|
|
|
|
|
|
|
|
Размен 11: (0, |
{10: 1, 1: 1}) |
|
|
|
Размен 30: (0, |
{25: 1, 5: 1}) |
|
|
|
Размен 70: (0, |
{50: 1, 10: 2}) |
|
|
|
Размен 82: (0, |
{50: 1, 25: 1, 5: 1, 1: 2}) |
|
|
|
Замечание о решении
Стоит заострить внимание, что не для каждого номинала монет этот алгоритм работает корректно. Так рассмотрим задачу размена для номиналов {1, 3, 4}.
Предположим нужно разменять сумму 6 центов. Для номиналов {1, 3, 4} мы получим 4, 1, 1. Но оптимальным вариантом будет 3, 3.
Существуют следующие не оптимальные номиналы монет {1, 3, 4}, {1, 4, 9}, {1, 2, 7, 10} и
т.д.
Задача о расписании
Существует студент который хочет посетить ряд семинаров проводимых в течении дня. Каждый семинар характеризуется временем его начала и временем его завершения. Если студент уже зашел на семинар, то покидать его нельзя (ждем пока закончиться, даже если не интересно). Задача посетить как можно больше семинаров за день.
Номер семинара |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Начало |
12 |
11 |
10 |
14 |
13 |
15 |
14 |
18 |
19 |
Конец |
13 |
13 |
14 |
15 |
16 |
17 |
17 |
19 |
20 |
Одним из наиболее оптимальных будет следующий порядок 1-4-6-8-9.
Граф решений задачи о расписании
Проверка принадлежности задачи к классу решаемых с помощью жадных алгоритмов
Мы видим что решение на каждом шаге определяет свой набор дальнейших решений. Есть
пересекающиеся подструктуры. Выбор решения не отменяет возможность общего
решения решения. За жадный выбор на каждом шаге стоит принять семинар который начинается после текущего и заканчивается раньше всех остальных в наборе.
Задача о дозаправках
Автомобиль должен преодолеть путь от одного населенного пункта до другого. Выезжает он с полностью заправленным баком. На полном баке топлива он может проехать L = 400 км. По дороге расположен ряд заправок. Задача минимизировать количества посещенных заправок.
Эта задача также обладает признаками задачи которая может быть решена с помощью
жадного алгоритма.
Принцип жадного выбора — всегда выбирать самую дальнюю заправку доступную с текущей позиции.
Таким образом при жадном выборе для этой задачи нужно посетить только две заправки.
Реализация на Python
def task_solve(distance, gas_station_list, cars_range):
"""
Решениезадачио заправках(жадныйалгоритм). |
|
|
|
||||
Аргументы: |
|
|
|
|
|
|
|
distance:общеерасстояниепути |
|
|
|
|
|||
gas_station_list:списокрасстоянийот |
|
|
стартадо заправок |
|
|||
cars_range:максимальноерасстояние, |
котороемашинаможетпроехатьбездозапр |
||||||
Возвращает: |
|
|
|
|
|
|
|
Списокзаправокна, которыхнужно |
остановитьсяили, Noneеслидоехатьневозмо |
||||||
""" |
|
|
|
|
|
|
|
result= [] |
# списокзаправокна, которыхбудем |
|
останавливаться |
|
|||
current_position= |
0 |
# текущаяпозиция(откудавыехалиилипоследняя |
заправка) |
||||
# Сортируемзаправкипо возрастаниюрасстоянияот |
|
старта |
|
||||
gas_station_list.sort() |
|
|
|
|
|||
current_gas_station= |
|
0 # индекстекущейрассматриваемойзаправки списке |
|
||||
# Покане можемдоехатьдо финишабездозаправки |
|
|
|
||||
while current_position+ cars_range< distance: |
|
|
|
||||
next_position= current_position |
# потенциальнаяследующаязаправка |
|
|||||
# Ищемсамуюдальнююзаправкудо, которойможем |
|
доехатьс текущегоположения |
|||||
while (current_gas_station< |
len(gas_station_list) and |
|
|||||
gas_station_list[current_gas_statio |
n] <= current_position+ cars_range |
||||||
next_position= gas_station_list[curre |
|
nt_gas_station] |
|
||||
current_gas_station+= |
1 |
|
|
|
|||
# Еслине нашлини однойзаправки(неможем |
|
продвинуться) |
|
||||
if next_position== current_position: |
|
|
|
||||
return None |
|
# Доехатьневозможно |
|
|
|
||
# Переезжаемна выбраннуюзаправку current_position= next_position
# Добавляемеё в результат(индекс current_gas_stationт.-к1,.мы ужеувеличи result.append(gas_station_list[current_gas _station- 1])
return result
# Списокзаправокна расстоянияхот старта |
|
||
gas_station_list=[ |
200, 375, 550, 750] |
|
|
# Общеерасстояние950,машинапроезжает400без |
дозаправки |
||
print(task_solve(950, gas_station_list, |
400)) |
||
# Ожидаемыйрезультат[200,:550,750]или |
аналогичныйоптимальныймаршрут |
||
|
|
|
|
[375, |
750] |
|
|
Задача о утреннике
Есть список детей и их возраста (в годах). Нужно провести утренник с их участием. По правилам утренника в одной группе могут быть дети, возраст которых отличается не более чем на 2 года. Задача - сформировать минимальное количество групп. Возраст детей - 4, 7, 5, 6, 3, 8, 4, 9, 3, 10, 6, 5, 7, 4, 8
Это еще одна задача относящаяся к жадным алгоритмам — задача о перекрывающихся диапазонах. Задача сводится к нахождению минимального количества интервалов
перекрывающих необходимые данные.
Для решения используется следующий жадный подход сортируем возраст детей по возрастанию и начинаем заполнение групп просто добавляя в группу элементы по порядку. Если группа заполнена то переходят к следующей группе.
Матроиды и жадные алгоритмы
Существует ли критерий того, что задачу можно решить используя жадный алгоритм?
Жадный алгоритм гарантированно работает если структура задачи задается матроидом.
Матроид (matroid) — комбинаторный объект представляющий собой пару элементов —
.
— конечное множество (носитель матроида).
— непустое множество подмножеств (семейство независимых множеств)
.
При этом должны выполняться следующие условия:
1. |
Пустое множество является независимым |
. |
|
|||
2. |
Свойство наследственности — если множество независимо, то и его |
|||||
|
подмножества независимы. |
. |
|
|
||
|
|
. Если |
то и |
|
|
|
3. |
Свойство обмена — Если |
независимые множества и в |
больше элементов |
|||
|
чем в |
, то в можно найти элемент (не принадлежащий |
) что при добавлении |
|||
|
его в |
, множество |
остается независимым. |
|
|
|
Примеры матроидов
Тип матроида |
Носитель |
Независимые множества |
Универсальный |
Любое конечное множество |
Все подмножества |
Графический |
Множество рёбер графа |
Ациклические подмножества рёбер (леса) |
Матричный |
Строки матрицы |
Линейно независимые строки |
Пример: Алгоритм Краскала для поиска минимального остовного дерева работает именно на графическом матроиде.
Взвешенный матроид
Матроид называется взвешенным, если каждому элементу носителя матроида соответствует строго положительный вес (или существует функция которая это делает), вес множества рассчитывается как линейная сумма весов его элементов.
Алгоритм (теорема) Радо — Эдмондса позволяет определить возможно ли решение задачи жадным алгоритмом и общий способ их решения.
Жадный алгоритм даёт оптимальное решение задачи максимального веса независимого множества тогда и только тогда, когда семейство независимых множеств является матроидом.
Обобщенный алгоритм решения для случая - есть взвешенный матроид, нужно найти независимое множество A с максимальным весом:
1.Отсортировать элементы носителя матроида по невозрастанию веса
2.Положить начальным значением
3.Выполнить перебор элементов носителя матроида по невозрастанию веса. Если
объединение и элемента принадлежит , добавить его, в противном случае пропустить.
Теорема Радо-Эдмондса — фундаментальный результат, объясняющий, почему жадные алгоритмы работают для задач на матроидах (например,
алгоритм Краскала, алгоритм Прима).
Задача о библиотеке
Существует маленькая библиотека с книгами имеющую разную букинистическую ценность. По правилам библиотеки существует ограничение на выдачу книг разных категорий одному человеку.
Категория |
Название |
Лимит |
A |
Редкие книги |
1 |
B |
Учебники |
2 |
CРазвлекательная 3
Букинистическая ценность и книги в наличии:
Задача — взять набор книг имеющий максимальную букинистическую стоимость.
