Скачиваний:
20
Добавлен:
01.05.2014
Размер:
128.51 Кб
Скачать

Курсовая работа по логическому программированию.

выполнил: Шоничев М. А. (airmax_al@mail.ru)

Задача о 0/1 рюкзаке.

Условие задачи обычно формулируется так: есть N неделимых предметов с целыми положительными массами и стоимостью. Необходимо выбрать K из них таким образом, чтобы суммарная стоимость была максимальной, а суммарный вес не превышал константы Z.

Приведены два решения этой задачи на языке Prolog. Первая программа по методу динамического программирования находит одно лучшее решение. Вторая - перебирает все дерево решений, используя отсечения для оптимизации перебора.

Пусть элемент c[i] вектора C соответствует стоимости (cost), а элемент m[i] вектора M - массе i-го предмета. Для второго решения будем исходить из предположения, что предметы отсортированы по не возрастанию массы.

Решение методом динамического программирования.

Пусть msс обозначает функцию, значение которой соответствует решению нашей задачи (max sum cost for taken items for given mass).

Аргументами функции является количество предметов i (по этому аргументу можно определить их стоимости и массы соответствующих предметов), а также максимальная суммарная масса j, которую можно унести.

Начнем вычисление этой функции с очевидного факта: для i=0 или j=0, msc(i,j)=0. То есть если мы взяли ноль предметов или не можем взять больше 0кг, то стоимость взятых, естественно, будет равна 0.

Теперь попытаемся выразить набор значений функции msc(i0,j) для фиксированного i0, через значения msc(i,j) для i<i0.

Если мы не берем предмет i, то msc для данной массы равно msc для той же массы на этот предмет меньше.

msc(i,j) = msc(i-1,j), для всех 1<=j<=Z.

Если же предмет i будет взят, что возможно только если j>=m[i], то msc для данного значения массы j, по сравнению с msc для массы j без веса взятого предмета, увеличивается на стоимость унесенного предмета.

if (j >= m[i])

msc(i,j) = msc(i-1, j - m[i]) + c[i]

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

Теперь, по индукции, мы можем вычислить msc для любых i и j, в частности i=N и j=Z.

Для запоминания лучшего решения при выборе максимума будем добавлять в список выбранные предметы. Следует сказать, что программа на прологе не вычисляет msc начиная cо значений i=1, j=1, а ведет поиск от конечной точки i=N,j=Z, рекурсивно спускаясь до известных значений msc(i,0) и msc(0,j).

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

Программа на языке Prolog для первого решения

% m - вес (Mass) i-ого предмета

m(1, 4). m(2, 5). m(3, 3). m(4, 7). m(5, 6).

% c - цена (Cost) i-ого предмета

c(1, 5). c(2, 7). c(3, 4). c(4, 9). c(5, 8).

% вспомогательный предикат, выбирающий максимум и соответствующий % ему список с сохраненным решением.

maxl(A,B,A, L1, L2, L1):- A>=B,!. maxl(A,B,B, L1, L2, L2).

% собственно, предикат для вычисления msc: msc(i,j, MaxSummCost, ItemList) % сначала тривиальные случаи: максимальная цена равна 0, список предметов ­­– пуст. % msc(0,j) = 0, L=[] % msc(i,0) = 0, L=[]

msc(0,J,0,[]). msc(I,0,0,[]).

% для случая, если можно взять i-ый предмет: % выбираем максимум из двух вариантов: % - когда мы его берем, при этом msc(i,j) возрастает по сравнению с % msc(i-1, j-m[i]) на c[i], а к решению добавляется взятый предмет. % - когда мы его не берем, при этом msc(i,j) остается равным msc(i-1,j).

msc(I,J, Sc, L) :- m(I, MM), MM=<J, I1 is I-1, msc(I1, J, Sc1, L1), c(I, CC), J1 is J-MM, msc(I1, J1, Sc_, L2_), Sc2 is Sc_+CC, L2=[I|L2_], maxl(Sc1, Sc2, Sc, L1, L2, L).

% для случая, когда мы не можем взять i-ый предмет - мы просто переходим % к вычислению msc(i-1,j).

msc(I,J, Sc, L) :- m(I, MM), MM>J, I1 is I-1, msc(I1, J, Sc1, L), Sc is Sc1.

% вспомогательный предикат распечатки списка.

dump_list([X | []]) :- write(X),nl. dump_list([X | L]) :- write(X),write(','),dump_list(L).

% вспомогательный предикат, вычисляющий сумму списка.

sum_list([],Summ):-Summ is 0. sum_list([X | L], Summ) :- sum_list(L, Summ1), Summ is Summ1 + X.

% Например, может быть задана такая цель:

go :- msc(5, 16, Sc, L), write('max sum price is: '), write(Sc), nl, write('items combination: '), dump_list(L), write('sum weight: '), sum_list(L, Summ), write(Summ).

вывод Arity:

решение методом обхода с возвратом

Упорядочим массив предметов по не возрастанию их массы. В качестве примера рассмотрим m= [7,5,2,2], с=[1,2,2,4], Z=7.

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

Для определенности, анализ решения (суммарная стоимость, вывод решения на консоль) будем проводить в листьях (то есть всегда спускаться до низа дерева).

Figure 1. часть дерева решений.

Для оптимизации перебора учтем, что если масса уже выбранных предметов превышает максимально допустимую (Z), то добавление еще хотя бы одного предмета только ухудшит ситуацию, поэтому не имеет смысла продолжать обход ниже данного узла и необходимо подниматься выше.

Для учета критерия максимума стоимости будем хранить оценку стоимости достижимой под данным узлом. Верхняя граница (максимальная оценка) - сумма всех с[i]. Опять же, не имеет смысла продолжать обход, если при обходе мы уже нашли решение со стоимостью больше чем сумма стоимости выбранных в данном узле предметов и оценки стоимости, которую можно достичь спускаясь ниже. То есть мы хотим только "улучшать" стоимость решения, нас не интересуют решения хуже чем мы уже знаем.

Оценка изменяется, очевидно, так: в корневом узле дерева - максимальная оценка, при спуске "направо" (при выборе данного предмета) оценка уменьшается на стоимость выбранного предмета.

Figure 2. оценка стоимости.

Программа на языке Prolog для второго решения, выводящая все комбинации предметов весом не больше Z.

% m - Mass of the item m(1, 7). m(2, 5). m(3, 2). m(4, 2).

% c - Cost of the item c(1, 1). c(2, 2). c(3, 2). c(4, 4).

z(7). % max weight (limit) n(4). % number of items

% вспомогательный предикат: обходит одну часть дерева (без первого элемента). tree1(L, I, S) :- n(I), write('items: '), write(L), write(' sum: '), write(S), nl. tree1(L, I, S) :- n(NN), I<NN, z(SS), I1 is I+1, tree1(L, I1, S), m(I1, MM), S1 is S+MM, S1=<SS, % отсечение слишком больших масс... tree1([I1|L], I1, S1).

% полный обход: сначала половинка дерева без предмета 1, затем с предметом 1 tree:- tree1([], 0, 0). tree:- m(1,MM), tree1([1],0,MM).

go:-tree.

вывод Arity:

Программа на языке Prolog для второго решения, с отсечением по весу (не больше Z) и отсечением по найденной максимальной стоимости

% m - Mass of the item m(1, 7). m(2, 5). m(3, 2). m(4, 2).

% c - Cost of the item c(1, 1). c(2, 2). c(3, 2). c(4, 4).

z(7). % max weight (limit) n(4). % number of items

% вспомогательный предикат: сохраняет лучшее решение

store(C,Best):-C=<Best,!. store(C,Best):-retract(best(Best)), assert(best(C)), write('stored('),write(C),write(')').

best(0). % исходно лучшее решение неизвестно

summ(I,0):-n(N), I>N. summ(I,C):-I1 is I+1, c(I,M), summ(I1,C1), C is C1+M.

% сначала пытаемся взять предмет с номером I... node(I, L, Sm, Sc, E) :- z(ZZ), m(I,MM), c(I,CC), best(Best), Sm1 is Sm+MM, Sc1 is Sc+CC, E1 is E-CC, Sm1=<ZZ, % отсечение по весу E1 +Sc1>=Best, % отсечение по лучшей найденной ранее стоимости I1 is I+1, iterate(I1,[I|L],Sm1,Sc1, E1).

% ... затем пытаемся найти решение без этого предмета node(I, L, Sm, Sc, E) :- c(I,CC), best(Best), E1 is E-CC, E1 + Sc>=Best, % отсечение по лучшей найденной ранее стоимости I1 is I+1, iterate(I1, L, Sm, Sc, E1).

% для узла перед листом: берем этот предмет leaf(I, L, Sm, Sc, E):-z(ZZ), m(I,MM), c(I,CC), Sm1 is Sm+MM, Sc1 is Sc+CC, Sm1=<ZZ, best(Best), E1 is E-CC, E1+Sc1 >=Best, % отсечение по лучшей найденной ранее стоимости write('leaf1: '), write(I), write(' list: '), write([I|L]), write(' Sc1: '), write(Sc1), write(' Sm1: '), write(Sm1), nl, store(Sc1, Best), % сохраняем лучшее решение fail. % продолжаем поиск

% для узла перед листом: не берем этот предмет leaf(I, L, Sm, Sc, E):-z(ZZ), best(Best), E1 is E-CC, Sm=<ZZ, % отсечение по весу E1+Sc >=Best, % отсечение по стоимости write('leaf2: '), write(I), write(' list: '), write(L), write(' Sc: '), write(Sc), write(' Sm: '), write(Sm), nl, store(Sc, Best), % сохраняем лучшее решение fail. % продолжаем поиск

% итерация: или узел перед листом (leaf) или нет... iterate(I,L,Sm,Sc,E):-n(NN), NN>I, node(I,L,Sm,Sc,E),fail. iterate(I,L,Sm,Sc,E):-n(NN), NN=I, leaf(I,L,Sm,Sc,E),fail.

% цель go:-summ(1,C), iterate(1,[],0,0, C).

вывод Arity:

во время второго запуска видно, что программа не сохраняет стоимость 6 как лучшую, так как уже сохраняла её.

Если переставить порядок обхода (сначала ветвь "без предмета", а затем "с предметом"), то станет видно, как программа постепенно улучшает стоимость найденного решения, пока не найдет лучшее.

Соседние файлы в папке Задача о рюкзаке на языке Пролог