|
по информатике |
procedure полный_перебор(m : integer);
var i : integer;
begin
if m > n then
<проверка построенной позиции>
else
for i := 1 to n do begin
ферзь[m] := i;
полный_перебор(m+1);
end;
end;
Теперь – рекурсивный вариант алгоритма перебора с возвратом. procedure перебор_с_возвратом(m : integer);
var i : integer;
begin
if m > n then
<найдено решение>
else
for i := 1 to n do begin
ферзь[m] := i;
if <ферзь[m] не бьёт предыдущих> then
перебор_с_возвратом(m+1);
end;
end;
Чем отличаются данные алгоритмы? Тем, что в первом проверка позиции происходит на самом позднем (``глубоком'') этапе перебора, а во втором на каждом этапе перебора происходит частичная проверка. (Если же все частичные проверки завершились успешно, получившуюся позицию уже можно не проверять.) Таким образом, перебор с возвратом исключает из перебора большое количество вариантов по сравнению с полным перебором. procedure перебор_с_возвратом(m : integer);
var i : integer;
begin
if m > n then
<найдено решение>
else
for i О Xm do begin
x[m] := i;
if <x[m] совместимо с x[1],...,x[m-1]> then
перебор_с_возвратом(m+1);
end;
end;
Первая задача – очень известная задача о раскраске карты (математики предпочитают обычно говорить о раскраске вершин графа). procedure перебор_с_возвратом(m : integer);
var i : integer;
begin
if m > n then
<найдено решение>
else
for i := 1 to k do begin
цвет[m] := i;
if <цвета стран 1,...,m-1 соседних с m-ой не i> then
перебор_с_возвратом(m+1);
end;
end;
Однако в отличие от задачи о ферзях, в данной задаче перебор можно сократить ещё больше за счёт симметрии решений задачи. Предположим, мы нашли решение задачи. Если мы теперь в этом решении у всех вершин поменяем местами i-ую и j-ую краску, то получившаяся раскраска тоже будет решением. Из таких симметричных решений нам достаточно найти одно. Таким образом, при выборе краски для очередной страны нам достаточно перебрать все уже используемые краски и всего одну (первую) из новых. procedure перебор_с_возвратом(m : integer);
var i : integer;
begin
if m > n then
<найдено решение>
else begin
for i := 1 to кол_красок do begin
цвет[m] := i;
if <цвета стран 1,...,m-1 соседних с m-ой не i> then
перебор_с_возвратом(m+1);
end;
if кол_красок < k then begin
кол_красок := кол_красок+1;
цвет[m] := кол_красок;
перебор_с_возвратом(m+1);
кол_красок := кол_красок-1;
end;
end;
end;
Задача 4
Укладка рюкзака. Из заданных n предметов выбрать такие, чтобы их суммарная стоимость была не менее чем S, а суммарный объём не превосходил V. procedure перебор_с_возвратом(m : integer);
var i : integer;
begin
if m > n then
<найдено решение>
else begin
x[m] := 0;
ост_стоимость := ост_стоимость+стоимость[m];
if ост_стоимость <= доп_ост then
перебор_с_возвратом(m+1);
ост_стоимость := ост_стоимость-стоимость[m];
x[m] := 1;
сумм_объём := сумм_объём+объём[m];
if сумм_объём <= V then
перебор_с_возвратом(m+1);
сумм_объём := сумм_объём-объём[m];
end;
end;
Задача 5
Расстановка знаков. Дано целое число m. Вставить между некоторыми цифрами 1, 2, 3, 4, 5, 6, 7, 8, 9, записанными именно в таком порядке, знаки ``+'' и ``-'' так, чтобы значением получившегося выражение было число m. Например, если m=122, то подойдёт выражение: 12+34-5-6+78+9. Если расставить знаки требуемым образом невозможно, сообщить об этом. program signs1;
var i, ii, kod : integer;
t : string; op : char;
s,r,m : longint;
begin
readln(m);
{ полный перебор вариантов }
for ii := 0 to 6560 do begin
{ построение выражения }
kod := ii; t := '1';
for i := 2 to 9 do begin
case (kod mod 3) of
1: t := t+'+';
2: t := t+'-';
end;
t := t+chr(48+i);
kod := kod div 3
end;
{ вычисление результата }
t := t+'.'; s := 1; r := 0; op := '+';
for i := 2 to length(t) do begin
if t[i] < '0' then begin
if op = '+' then r := r+s else r := r-s;
s := 0; op := t[i];
end
else s := s*10+(ord(t[i])-48)
end;
{ проверка равенства }
if r=m then writeln(t)
end;
end.
Если основной цикл представить несколькими вложенными циклами – каждый для отдельной позиции знака в строке, то получится 8 вложенных циклов. Тогда уже можно начинать строить выражение сразу для группы вариантов. На самом нижнем (и наиболее часто исполняемом, а значит и наиболее дорогом) уровне перебора уже нет операций построения строки – они делаются раньше – сразу для групп элементов. Получим следующий текст (строка на первом уровне – t1, на втором – t2 и т.д.):for i2:= 1 to 3 do begin
case (i2 mod 3) of
1: t2 := t1+'+'+'2';
2: t2 := t1+'-'+'2';
else t2 := t1+'2';
end;
for i3 := 1 to 3 do begin
case (i3 mod 3) of
1: t3 := t2+'+'+'3';
2: t3 := t2+'-'+'3';
else t3 := t2+'2';
end;
...
И так 8 циклов. Довольно длинно, но более эффективно. Причём рекурсивный вариант этого алгоритма устраняет громоздкость текста:procedure sign_choice2(n : integer; var t : string);
var tnew : string; i : integer;
begin
if n <= 9 then
for i := 1 to 3 do begin
case i of
1: tnew := t+'+'+chr(48+n);
2: tnew := t+'-'+chr(48+n);
else tnew := t+chr(48+n);
end;
sign_choice2(n+1,tnew);
end
else begin
{ вычисление результата }
t := t+'.'; s := 1; r := 0; op := '+';
for i := 2 to length(t) do begin
if t[i] < '0' then begin
if op = '+' then r := r+s else r := r-s;
s := 0; op := t[i];
end
else s := s*10+(ord(t[i])-48)
end;
{ проверка равенства }
if r=m then writeln(t)
end
end;
(Вызов данной процедуры заменяет в программе signs1 весь основной цикл.)tnew := t+'+'+chr(48+n); sign_choice(n+1,tnew); tnew := t+'-'+chr(48+n); sign_choice(n+1,tnew); tnew := t+chr(48+n); sign_choice(n+1,tnew);
procedure sign_choice3(s,r : longint; op : char; n : integer;
t : string);
var newr : longint;
begin
if op = '+' then newr := r+s else newr := r-s;
if n <= 9 then begin
sign_choice3(n,newr,'+',n+1,t+'+'+chr(48+n));
sign_choice3(n,newr,'-',n+1,t+'-'+chr(48+n));
sign_choice3(s*10+n,r,op,n+1,t+chr(48+n));
end
else { проверка равенства }
if newr=m then writeln(t)
end;
И, наконец, последнее, уже не столь существенное улучшение. Раз построенное выражение (символьную строчку t) мы не используем для вычисления результата, то можно её вообще не строить (правда, запоминать расставленные знаки всё же придётся, чтобы можно было выдать результат).program signs4;
var t : array[2..9] of string;
procedure sign_choice4(s,r : longint; op : char; n : integer);
var newr : longint;
begin
if op = '+' then newr := r+s else newr := r-s;
if n <= 9 then begin
t[n] := '+'; sign_choice4(n,newr,'+',n+1);
t[n] := '-'; sign_choice4(n,newr,'-',n+1);
t[n] := ''; sign_choice4(s*10+n,r,op,n+1);
end
else { проверка равенства }
if newr=m then begin
for i := 2 to 9 do write(i-1,t[i]);
writeln('9');
end;
end;
begin
readln(m);
sign_choice4(1,0,'+',2);
end.
Итак, мы пришли всё к той же схеме перебора с возвратом. Правда в этой задаче не удаётся отсечь варианты на ранних этапах перебора (поэтому перед рекурсивным вызовом нет оператора if) и есть еще одно непринципиальное отличие – цикл заменён на три последовательных рекурсивных вызова. 7
3 8
8 1 0
2 7 4 4
4 5 2 6 5