Лекции / 4_Динамическое_программирование_Жадные_алгоритмы_ipynb_Colab
.pdf
Построим матроид для этой задачи. Носитель матроида это множество всех имеющихся книг.
Выбор принципа построение независимого множества — берем подмножество в котором количество элементов одного типа не нарушает правила библиотеки.
Подходящие подмножества Не подходящие подмножества
Докажем свойства матроида.
Наследственность — если какой-то набор книг не выходит за лимиты, то любое из его подмножеств (по сути часть этого набора) точно за лимиты не выйдет.
Обмен — если какой-то набор книг больше другого, это означает что меньший набор использует меньший лимит, поэтому всегда можно добавить элемент из большего в меньшее на место не используемого лимита.
Следовательно эта задача гарантированно вернет оптимальное решение с помощью жадного алгоритма. Мало того можно использовать обобщенный алгоритм для этого. Отсортировать книги по невозрастанию букинистической стоимости, после чего при
каждом добавлении проверять не вышли ли мы за лимиты (остается ли множество независимым).
# Реализацияжадногоалгоритмадлязадачио |
библиотеке |
```python
# Класс,представляющийкнигу class Book:
def __init__(self, book_type, book_cost):
# Сохраняемкатегориюкнигии её стоимость self.book_type= book_type self.book_cost= book_cost
def __str__(self): |
|
|
|
|
|
# Длякрасивоговыводаинформациикниге |
|
|
|||
return f"Book(type= |
{self.book_type}, cost= {self.book_cost})" |
||||
def solve(book_list, limit): |
|
|
|
|
|
""" |
|
|
|
|
|
Жадныйалгоритмдлявыборакнигс максимальной |
стоимостью |
|
|||
с учётомлимитовпо категориям |
|
|
|
|
|
""" |
|
|
|
|
|
# 1. Сортируемкнигипо убываниюстоимости |
|
|
|
||
book_list= |
sorted(book_list,key= |
lambda x: x.book_cost,reverse= |
True) |
||
# 2. Итоговыйнаборкниг(независимоемножество) |
|
|
|||
book_set= [] |
|
|
|
|
|
# 3. Проходимпо всемкнигамв порядкеубывания |
стоимости |
|
|||
for book |
in book_list: |
|
|
|
|
# Проверяемможно, ли ещёвзятькнигуэтой |
категории |
|
|||
if limit.get(book.book_>type) |
0: |
|
|
||
#Добавляемкнигув набор book_set.append(book)
#Уменьшаемдоступныйлимитдляэтойкатегории
limit[book.book_type]-= 1
return book_set
#Словарьс лимитамипо категориям
#A - редкиекниги:1 штука
#B - учебники2:штуки
#C - развлекательная3 штуки:
limit= { "A": 1, "B": 2, "C": 3}
# Списоквсехдоступныхкнигс указаниемкатегории |
и стоимости |
|
book_list= [ |
|
|
Book( |
"A", 10), Book( "A", 7), |
|
Book( |
"B", 6), Book( "B", 9), Book( "B", 5), |
|
Book( "C", 2), Book( "C", 4), Book( "C", 1), Book( "C", 3)
]
# Применяемжадныйалгоритм best_books= solve(book_list,limit)
# Вычисляемобщуюстоимостьвыбранныхкниг
total_book_cost= sum(book.book_cost for book in best_books)
# Выводимрезультат print("Выбранныекниги:" ) for book in best_books:
print(book)
print()
print("Общаястоимость= " , total_book_cost)
Задача о задачах со сроком завершения
Существует набор задач (одинаковой длительности равной 1 единице времени) которые характеризуется сроком завершения и прибылью от ее выполнения. За единицу времени может выполняться только одна задача.
Нужно выполнить задачи так, чтобы:
ни одна задача не выполнялась позже своего срока завершения — то есть нужно
поставить её на момент времени |
; |
|||
суммарная прибыль была максимальна. |
|
|||
Задача |
Прибыль |
Срок завершения |
|
|
A |
50 |
2 |
|
|
B |
10 |
1 |
|
|
C |
40 |
3 |
|
|
D |
30 |
3 |
|
|
E |
20 |
1 |
|
|
F |
60 |
2 |
|
|
G |
25 |
4 |
|
|
H |
45 |
2 |
|
|
I |
15 |
4 |
|
|
J |
55 |
3 |
|
|
Идея построения матроида для этой задачи
Построим матроид для этой задачи. Носитель матроида это множество всех имеющихся задач.
Выбор принципа построение независимого множества — берем подмножество в котором каждая задача может быть выполнена до своего срока завершения.
Подходящие подмножества Не подходящие подмножества
Докажем свойства матроида.
Наследственность — если существует корректное расписание заданий, то извлекая из него задания мы и дальше будем получать корректное расписание.
Обмен — если какое то расписание содержит больше заданий, то из него достаточно выбрать задание с максимально большим сроком завершения, и для него гарантировано найдется место в расписании с меньшим количеством элементов.
Следовательно эта задача гарантировано вернет оптимальное решение с помощью жадного алгоритма. Мало того можно использовать обобщенный алгоритм для этого.
Жадный алгоритм для решения задачи — сортируем задачи по не возрастанию стоимости. Идем по отсортированному списку и пытаемся добавить задачу на свободный слот с максимальным сроком завершения работы.
Жадные алгоритмы дают решение близкое к оптимальному
Полезным свойством жадных алгоритмов является их возможность давать решение близкое к оптимальному (не самое оптимальное, а близкое) при этом оставляя решение
простым и быстро выполняемым.
Задача о дорожных картах. Есть несколько платных шоссе, для каждого из которых нужно купить отдельную карту (пропуск). Маршрут проходит через несколько участков, и нужно определить минимальное количество карт, которые необходимо купить, чтобы проехать весь маршрут.
Жадный алгоритм
Жадный алгоритм — на каждом шаге брать карту оплачивающую максимальное количество еще не оплаченных участков.
Жадный алгоритм:
Оптимум:
Как можно видеть жадный алгоритм дает не самое оптимальное решение, но оно близко к оптимальному и к тому же понятное и просто реализуется.
Реализация жадного алгоритма для задачи о дорожных картах
class TrafficTicket: |
|
"""Класспредставляющий, пропуск(картуна) |
платноешоссе""" |
def __init__(self, ticket_type, roads): |
|
self.ticket_type= ticket_type |
# Тип/названиепропуска |
self.roads= roads |
|
# Множествоучастковкоторые, покрываетпропу |
|
def __str__(self): |
|
|
|
return f"TrafficTicket(type= |
{self.ticket_type}, roads= |
{self.roads})" |
|
class Route: |
|
|
|
"""Класспредставляющий, маршрут""" |
|
|
|
def __init__(self, highway): |
|
|
|
self.highway= highway |
# Множествоучастковкоторые, нужнопроехать |
|
|
def __str__(self): |
|
|
|
return f"Route[highway= |
|
{self.highway}]" |
|
def solve(route, traffic_tickets_set):
"""
Жадныйалгоритмдлявыбораминимального количествапропусков
На каждомшагевыбираетсяпропускпокрывающий, |
максимальное |
||||
количествоещёне покрытыхучастковмаршрута |
|
||||
""" |
|
|
|
|
|
result_set= |
set() |
# Множествовыбранныхпропусков |
|||
# Продолжаемпока, естьнепокрытыеучастки |
маршрута |
||||
while len(route.highway)> |
0: |
|
|||
next_ticket= |
|
|
None |
|
|
max_route= |
|
0 |
|
|
|
# Ищемпропускпокрывающий, максимумнепокрытых |
участков |
||||
for ticket |
in traffic_tickets_set: |
|
|||
# Количествонепокрытыхучастковкоторые, |
покрываеттекущийпропуск |
||||
covered= |
|
|
len(route.highway.intersection(ticket.roads)) |
||
if covered> max_route: |
|
||||
next_ticket= ticket |
|
|
|||
max_route= covered |
|
|
|||
# Еслине нашлиподходящийпропускрешение |
невозможно |
||||
if next_ticket |
is None: |
|
|
||
return None |
|
|
|||
# Добавляемвыбранныйпропускв результат |
|
||||
result_set.add(next_ticket) |
|
||||
# Удаляемпокрытыеучасткииз маршрута |
|
||||
route.highway= route.highway.difference(n |
ext_ticket.roads) |
||||
# Удаляемиспользованныйпропускиз доступных traffic_tickets_set.discard(next_ticket)
return result_set
# Создаёмнабордоступныхпропусков |
|
|
traffic_tickets=set{ |
|
|
TrafficTicket( |
"A", { 1, 2, 3}), |
# ПропускA покрываетучастки1, 2, 3 |
TrafficTicket( |
"B", { 4, 5, 6}), |
# ПропускB покрываетучастки4, 5, 6 |
TrafficTicket( |
"C", { 1, 2, 4, 5}), |
# ПропускC покрываетучастки1, 2, 4, 5 |
TrafficTicket( |
"D", { 3}), |
# ПропускD покрываеттолькоучасток3 |
TrafficTicket( |
"E", { 6}) |
# ПропускE покрываеттолькоучасток6 |
} |
|
|
# Создаёммаршруткоторый, нужнопокрыть(участки |
1-6) |
|
route= Route({ 1, |
2, 3, 4, 5, 6}) |
|
# Применяемжадныйалгоритм
result= solve(route,traffic_tickets_set)
# Выводимрезультат print("Выбранныепропуски:" ) for ticket in result:
print(ticket)
Выбранные пропуски:
Traffic Ticket(type = B, roads = {4, 5, 6}) Traffic Ticket(type = C, roads = {1, 2, 4, 5}) Traffic Ticket(type = D, roads = {3})
Задача о посещении друзей (задача коммивояжера)
Вы решили заехать в гости к друзьям которых давно не видели. Но они живут в разных частях города и на разном расстоянии друг от друга. Ваша задача выбрать такой порядок их посещения, чтобы суммарный путь который вы преодолеете был минимальным.
Жадный подход — всегда переходить к ближайшему не посещенному другу.
Пример: начнем с друга A
Друг |
Расстояние до остальных |
A |
5(B), 9(C), 4(D) |
B |
5(A), 3(C), 8(D) |
C |
9(A), 3(B), 2(D) |
D |
4(A), 8(B), 2(C) |
В этом случае жадный алгоритм дает оптимальное решение. Но это частный случай, в
общем случае решение может быть в разы (2-3 раза) хуже оптимального пути.
