logo search
Высокоцровневые методы информатики и првые методы информатики и программированияограммирования

5.8.2 Дискретная задача о рюкзаке

На складе есть N предметов, для оторых известны их веса w[1..N] и их стоимости v[1..М]. На склад про- брался вор, который хочет украсть предметов на максимальную сум- му денег. Однако вес, который вор может вынести, ограничен и рав- няется TotalW. Какие предметы должен взять вор, чтобы их сум- марная стоимость была наиболь- шей, а вес был ограничен величи- ной TotalW ?

Эта задача встречается во многих источниках, но часто ее решают простым backtracking'ом, где на каждом шаге пробуют взять или не взять предмет. Однако такой под- ход не всегда эффективен. Суще- ствует как жадный, так и ДП-алго- ритм ее решения.

Жадный алгоритм поступает так: вычисляется цена единицы веса каждого предмета, то есть рriсе[i] := v[i] / w[i]. Потом предметы сорти- руются в порядке убывания price[i], и вор начинает помещать в свой рюкзак предметы по порядку (i=1,2,...N) из отсортированного списка. Если предмет i не помещается (по ограничению оставшегося свобод- ного веса в рюкзаке)  вор рассма тривает следующий предмет 1+1 (price[+ 1] < price[i]), и так до конца. Жадность состоит в том, что вор на каждом шаге пытается взять предмет с наибольшей ценой.

Оценим сложность описанного алгоритма. Для сорти- ровки надо O(N log(N)) операций. Далее надо пробежать циклом по i от 1 до N и пробовать впихнуть предмет i, таким образом это зай- мет еще O(n) операций. Итого, об- щая сложность есть O(N log(N) +  N), что в общем случае эквива- лентно O(N log(N)).

Но если хорошо присмотреться, то такой алгоритм не всегда дает оп- тимум. Вот вам пример: 3 предме- та, 1  ($60, 10 кг), 2  ($100, 20 кг), 3  ($120, 30 кг) с ценами соот- ветственно 6 $/кг, 5 $/кг, 4 $/кг. Предметы уже отсортированы по убыванию цены. Допустим, макси- мальный вес рюкзака вора  50 кг. Следуя жадному алгоритму, вор берет первый предмет с ценой 6 $/кг, который весит 10 кг, затем следующий предмет с ценой 5 $/кг (и весом 20 кг), после чего в его рюкзаке остается место только на 20 кг, и оставшийся предмет уже не влезает, так как весит 30 кг. Итого, действуя по жадному алго- ритму, вор украл два предмета на сумму $160 и весом 30 кг. Если бы вор украл второй и третий предме- ты (суммарным весом 50 кг), он бы вынес $220. Как видите, жадный алгоритм не дает оптимального решения, а значит, он является приблизительным алгоритмом.

Если решать эту же задачу с по- мощью динамического програм- мирования, то надо поступить следующим образом. Допустим, надо найти максимальную сумму val[Wi], которую может вынести вор, если допустить, что объем его рюкзака не больше W и мож- но брать предметы от 1 до i (предметы не отсортированы!) Допустим, мы уже нашли все val[1..W, 1..i1] (для веса не боль- ше W и с возможностью брать предметы от 1 до i1). Рассмат- ривается предмет i. Если его вес w[i] меньше W, рассмотрим, сто- ит ли его брать:

Из этих двух вариантов выбирается тот, что дает большее значение val[W, i]. Реализация ДП-алгорит- ма будет выглядеть так:

const MAXW = 500; MAXN = 25;

var

val: array[0..MAXW,0..MAXN]of integer; (динамические массивы}

take: array[0.. MAXW,0..MAXN] of boolean;

v,w: array[1..MAXN] of integer;(ценности и веса предметов)

л, TotalW: integer; (кол. предметов,максимальный вес)

weight, i: integer;(переменные циклов}

begin

{читаем входные данные} read(n, TotalW);

for i:=1 to n do read(w(i] v[i]);

(делаем начальную инициализацию для веса 0}

for i:=0 tо n dо begin

val[0,i]:= 0;

take[0,i]:= false;

end;

for weight:=1 to Totalw do val[weight 0] := 0;

for weight:=1 to TotalW do

for i := 1 to N do

if (w[i]>weight)

{если вещь не влезет} {или лучше ее не брать} or ( val[weight, i-1] >= val [weight-w[i],i-l] i v[i))

then begin (то не берем ее)

val[weight,i] :=val[weight,i-1]; take[weight,i] := false;

(отмечаем, что вещь i не взята)

end

else {иначе) begin

{оптимум := оптимум для веса weight-w[i] и использования

вещей 1..i-l + цена вещи i, которую мы берем}

val [weight, i]:= val(weight-w[i],i-1] + v[i];

take[weight,i]:= true;

(отмечаем, что вещь i взята)

end;

(вывод результатов) writeln('Best value:’, val[TotalW, N]);

write('Taken things; '); weight := TotalW; for i := N downto 1 do

if take[weight, i] then begin

write(i,' ');

weight := weight - w[i];

end;

end.

Листинг 5.23 – Дискретная задача о рюкзаке, решаемая ДП-алгоритмом

Этот алгоритм будет выдавать оптимальное решение, но какой ценой? Ценой лишней памяти: надо порядка O(TotalW  N) памяти под динамические таблицы стои- мости и запоминания того, брать и каждый предмет или нет. Сложность алгоритма та же: O (TotalW N), что следует из цик- лов по weight и i.

Таким образом, ДП-алгоритм хоть и работает безупречно правильно, но требует дополнительных затрат. Есть, правда, еще одна проблема с ДП-алгоритмом: если TotalW слиш- ком велико, или же оно является действительным (а не целым) чис- лом, то ДП-алгоритм вообще не- применим.