Постановка: есть n предметов с весом wi и стоимостью ci. Рюкзак вмещает вес не более W. Какие предметы положить, чтобы суммарная стоимость была максимальной?
maxi=1∑ncixiприi=1∑nwixi≤W,xi∈{0,1}
Варианты:
0/1 рюкзак — каждый предмет берётся целиком или не берётся
Неограниченный рюкзак — предмет можно брать многократно
Разработка и оптимизация ИИС
Рекуррентное соотношение для задачи о рюкзаке
Пусть dp[i][w] — максимальная стоимость, используя первые i предметов при вместимости w.
Оптимальный набор: предметы 1 и 2, вес 2+3=5≤8, стоимость 3+4=7.
Исправление: повторим — предмет 4 при w=8: dp[3][3]+8=4+8=12=dp[4][8] ✓
Оптимальный набор: предметы 2 и 4, вес 3+5=8, стоимость 4+8=12.
Разработка и оптимизация ИИС
6. Программная реализация: задача о рюкзаке
defknapsack(weights, costs, W):
n = len(weights)
dp = [[0] * (W + 1) for _ inrange(n + 1)]
for i inrange(1, n + 1):
for w inrange(W + 1):
dp[i][w] = dp[i - 1][w]
if weights[i - 1] <= w:
dp[i][w] = max(dp[i][w],
dp[i - 1][w - weights[i - 1]] + costs[i - 1])
# Восстановление решения
w, result = W, []
for i inrange(n, 0, -1):
if dp[i][w] != dp[i - 1][w]:
result.append(i - 1)
w -= weights[i - 1]
return dp[n][W], result
val, items = knapsack([2, 3, 4, 5], [3, 4, 5, 8], 8)
print(f"Стоимость: {val}, Предметы: {items}")
# Стоимость: 12, Предметы: [3, 1]
Разработка и оптимизация ИИС
Программная реализация: Беллман-Форд
defbellman_ford(n, edges, s):
d = [float('inf')] * n
d[s] = 0
p = [-1] * n
for _ inrange(n - 1):
for u, v, w in edges:
if d[u] + w < d[v]:
d[v] = d[u] + w
p[v] = u
# Проверка отрицательных цикловfor u, v, w in edges:
if d[u] + w < d[v]:
raise ValueError("Отрицательный цикл")
# Восстановление путиdefpath(t):
route = []
while t != -1:
route.append(t)
t = p[t]
return route[::-1]
return d, path
Разработка и оптимизация ИИС
Программная реализация: Флойда-Уоршелл
deffloyd_warshall(n, dist):
d = [row[:] for row in dist]
p = [[i if dist[i][j] < float('inf') else -1for j inrange(n)] for i inrange(n)]
for k inrange(n):
for i inrange(n):
for j inrange(n):
if d[i][k] + d[k][j] < d[i][j]:
d[i][j] = d[i][k] + d[k][j]
p[i][j] = p[k][j]
return d, p
Разработка и оптимизация ИИС
Заключение
Разработка и оптимизация ИИС
Ключевые выводы лекции
ДП — разбиение задачи на перекрывающиеся подзадачи с запоминанием решений
Принцип оптимальности Беллмана — фундамент ДП
Беллман-Форд: O(∣V∣⋅∣E∣), отрицательные веса
Флойда-Уоршелл: O(∣V∣3), все пары вершин
Задача о рюкзаке: O(n⋅W), псевдополиномиальная
Два подхода: мемоизация (сверху вниз) и табуляция (снизу вверх)
Оптимизация памяти — хранить только необходимые предыдущие состояния
Кратко пробегитесь по плану: начнём с определения и ключевых понятий, затем принцип оптимальности, после — классические задачи (кратчайший путь, рюкзак), завершим программной реализацией.
Дайте определение: ДП — не конкретный алгоритм, а подход (парадигма). Приведите мотивирующий пример: маршрут с пересадками — каждый раз нужно выбирать лучший следующий шаг.
Приведите аналогию: чтобы найти лучший маршрут из Витебска в Минск через несколько городов, нужно знать лучшие маршруты между каждой парой соседних городов.
Подчеркните: ДП не работает для любых задач. Необходимы два свойства — оптимальная подструктура и перекрывающиеся подзадачи. Если их нет — нужен другой метод (жадный, перебор).
Разберите каждое свойство подробно с примерами. Подчеркните: оба свойства обязательны для применения ДП.
Сравните с другими подходами. Подчеркните: ДП занимает промежуточное положение между полным перебором и жадными алгоритмами.
Покажите дерево рекурсии для Фибоначчи и визуализируйте перекрывающиеся подзадачи. Это наглядная иллюстрация избыточных вычислений.
Подчеркните: оба подхода дают тот же результат, но отличаются порядком вычислений и требованиями к памяти стека.
Приведите оба варианта кода для Фибоначчи. Студенты увидят разницу на практике.
Подчеркните: для многих задач избыточное хранение всей таблицы не нужно. Достаточно хранить «узкое окно» предыдущих состояний.
Перейдите к классическим задачам. Начните с кратчайшего пути — это основная задача в теории графов и в лабораторной работе.
Опишите алгоритм Беллмана-Форда: простой, работает с отрицательными весами, но медленнее Дейкстры.
Пошаговый алгоритм Беллмана-Форда. Подчеркните: $|V| - 1$ итерация достаточно, т.к. кратчайший путь содержит не более $|V| - 1$ рёбер.
Решите конкретный пример на доске. Покажите таблицу итераций — это то, что студенты будут делать на лабораторной.
Объясните: на итерации 4 ничего не изменилось — алгоритм сошёлся. Кратчайший путь: A→B→E→C (для C), A→B→E→D (для D).
Перейдите к Флойду-Уоршеллу — второму классическому алгоритму ДП для графов. Подчеркните: это ДП на подмножествах вершин.
Решите пример Флойда-Уоршелла. Покажите, как на каждой итерации $k$ рассматриваются пути через вершину $k$.
Перейдите ко второй ключевой задаче — задача о рюкзаке. Это классическая задача комбинаторной оптимизации с множеством практических приложений.
Выведите рекуррентное соотношение. Подчеркните: для каждого предмета есть два варианта — взять или не взять.
Решите конкретный пример шаг за шагом. Покажите заполнение таблицы — это наглядно и поможет на лабораторной.
Покажите код для задачи о рюкзаке — снизу вверх, с восстановлением решения. Студенты будут реализовывать это на лабораторной.
Покажите код Беллмана-Форда с восстановлением пути.
Покажите код Флойда-Уоршелла — компактный и элегантный.
Подведите итоги. Свяжите все темы: принцип Беллмана → конкретные задачи → код.
Акцентируйте внимание на связи принципа оптимальности с конкретными задачами и на том, что ДП — это не алгоритм, а парадигма.
Предложите студентам несколько вопросов для самопроверки.