19
АЛГОРИТМ ПЕРЕБОРА С ВОЗВРАТОМ Поиск с возвратом – это метод перебора всех возможных решений для нахождения так называемого множества полных решений. Каждое частичное решение определяет некоторое подмножество полных решений. Метод перебора с возвратом основан на том, что при поиске частичного решения многократно делается попытка расширить так называемое текущее частичное решение. Определение переборной подпрограммы следует понимать следующим образом: для всех первых ходов, будут перебраны всевозможные вторые ходы, затем для вторых ходов будут перебраны всевозможные третьи ходы и так далее, пока не будут перебраны все варианты ходов. Таким образом, ищется первый подходящий вариант, затем для этого варианта хода второй и так далее, пока для очередного хода не будут перебраны все варианты. Можно сравнить это с построением некой цепочки ходов. Поскольку для каждого хода есть несколько вариантов следующих ходов, образуется своеобразное дерево рекурсивных вызовов перебора с возвратами. Отсюда следует и оценка времени выполнения переборных программ - K N , где K - количество вариантов следующих ходов для данного хода, n - количество ходов, то есть время выполнения программы растет экспоненциально от количества различных вариантов для данного варианта 1

Алгоритм перебора с возвратом

  • Upload
    di2008

  • View
    2.754

  • Download
    7

Embed Size (px)

Citation preview

Page 1: Алгоритм перебора с возвратом

АЛГОРИТМ ПЕРЕБОРА С ВОЗВРАТОМ

Поиск с возвратом – это метод перебора всех возможных решений для

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

решение определяет некоторое подмножество полных решений. Метод перебора с

возвратом основан на том, что при поиске частичного решения многократно

делается попытка расширить так называемое текущее частичное решение.

Определение переборной подпрограммы следует понимать следующим

образом: для всех первых ходов, будут перебраны всевозможные вторые ходы,

затем для вторых ходов будут перебраны всевозможные третьи ходы и так далее,

пока не будут перебраны все варианты ходов. Таким образом, ищется первый

подходящий вариант, затем для этого варианта хода второй и так далее, пока для

очередного хода не будут перебраны все варианты. Можно сравнить это с

построением некой цепочки ходов. Поскольку для каждого хода есть несколько

вариантов следующих ходов, образуется своеобразное дерево рекурсивных

вызовов перебора с возвратами. Отсюда следует и оценка времени выполнения

переборных программ - KN, где K - количество вариантов следующих ходов для

данного хода, n - количество ходов, то есть время выполнения программы растет

экспоненциально от количества различных вариантов для данного варианта

Алгоритм перебора с возвратом используется, когда решение представляет

собой некоторую последовательность элементов: . Суть его заключается

в следующем. На каждом этапе имеется некоторое частичное решение ,

к которому мы пытаемся добавить следующий элемент из множества возможных

(происходит перебор возможных продолжений). Процесс добавления элементов

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

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

получено, то последний добавленный элемент удаляется (осуществляется возврат

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

Алгоритм заканчивается если найдено решение либо все варианты исчерпаны.

1

Page 2: Алгоритм перебора с возвратом

Классическими задачами, на которых обычно демонстрируется этот подход,

являются обход конем доски размером N×N, расстановка N ферзей на доске N×N

и задача коммивояжера.

Решаемые методом перебора с возвратом задачи, как правило, принадлежат

одному из трех классов – требуется либо найти произвольное из решений, либо

перечислить все возможные решения, либо найти решение, оптимальное по

заданному критерию.

Пусть глобальная логическая переменная Success получает значение True

как только будет найдено решение. До начала работы алгоритма она должна быть

инициализирована в False. Пусть также i – переменная, характеризующая

глубину рекурсии и передаваемая как параметр рекурсивной процедуре.

Алгоритм перебора с возвратом для нахождения одного решения

procedure Back1(i:integer);

begin

  формирование списка кандидатов

  repeat

    выбор очередного кандидата (перебор)

    if подходит then

    begin

      запись кандидата

      if решение неполное then

      begin

        Back1(i+1);

        if not Success then

          стирание кандидата  (возврат)

     end;

      else Success:=True

    end;

  until Success or кандидатов больше нет

end;

2

Page 3: Алгоритм перебора с возвратом

Отметим, что как только переменная Success получает значение True, мы

выходим из всех рекурсивных вызовов процедуры Back1, не совершая никаких

дополнительных действий.

Пример: Рассмотрим реализацию указанного выше алгоритма на примере

задачи обхода конем шахматной доски размера . Обход, если он существует,

можно выполнить за n2-1 шагов.

Доска представляется в виде двумерного массива n×n. Элементами массива

будут номера ходов коня. Значение 0 означает, что клетка еще не посещалась,

значение равное k означает, что в клетку попали на k-ом шаге.

Опишем функцию, осуществляющую обход доски конем. Функция имеет три

параметра: i – номер шага, x и y – текущее положение коня, т.е. координаты

клетки, в которой располагается конь. В результате выполнения функции

выдается логическое значение true, если с заданной позиции удалось обойти

доску полностью, и false в противном случае.

Требуется продолжать обход доски, если верно i<n2. По правилам хода коня

из клетки с координатами (x, y) можно сделать 8 ходов и получить текущее

положение с координатами (u, v).

Пронумеруем ходы так, как показано на рисунке.

Чтобы получить очередной ход, следует изменить текущие координаты

расположения коня на значения, хранящиеся в массивах dx и dy. Следующий ход

коня будет: u:=x+dx[k]; v:=y+dy[k].

3

Page 4: Алгоритм перебора с возвратом

Для каждого из этих ходов необходимо проверить, находится ли клетка в

пределах шахматной доски, и не ходил ли конь в эту клетку раньше. Если клетка

– в пределах доски и конь в нее еще не ходил, то мы пробуем совершить в нее

ход, помечая клетку номером хода, после чего, если все клетки обойдены, то мы

устанавливаем переменную Success в True (решение найдено), в противном

случае вызываем функцию Back1 повторно с координатами новой клетки в

качестве параметра и проверяем, решена ли задача. Если задача не решена, то ход

следует признать неудачным и стереть его из списка ходов (возврат).

Процедура init осуществляет формирование массива для получения ходов

коня и заполнения клеток таблицы Solution нулевыми значениями.

program KnightWay;

const

n=8;

var

x0,y0,i,j: integer;

dx: array [1..8] of integer;

dy: array [1..8] of integer;

Solution: array [1..n,1..n] of integer;// массив

ходов

Procedure Init;

Begin dx[1]:=2; dy[1]:=1; dx[2]:=1; dy[2]:=2;

dx[3]:=-1; dy[3]:=2; dx[4]:=-2; dy[4]:=1;

dx[5]:=-2; dy[5]:=-1; dx[6]:=-1; dy[6]:=-2;

dx[7]:=1; dy[7]:=-2; dx[8]:=2; dy[8]:=-1;

for i:=1 to n do

for j:=1 to n do

Solution[i,j]:=0;

End;

4

Page 5: Алгоритм перебора с возвратом

function Back1(i,x,y: integer):boolean;

var k, // номер кандидата или число возможных вариантов ходов

коня

u,v: integer; // координаты следующего хода коня 

Success: boolean; //результат обхода после выполнения

очередного шага

begin

k:=0; Success:=False;

repeat

k:=k+1; // номер варианта для хода коня

u:=x+dx[k];

v:=y+dy[k];//вычисление координат следующего хода коня

if (u>0) and (u<=n) and (v>0) and (v<=n)

then //в пределах доски

If Solution[u,v]=0

then //клетку не посещали или она свободна

begin

Solution[u,v]:=i;

if i<n*n then

//есть не пройденные клетки

begin//обход доски со следующей позиции коня

Success:= Back1(i+1,u,v);

if not Success then //возврат на

предыдущий шаг

Solution[u,v]:=0;//клетка объявляется

свободной

end

else

Success:=True//всю доску обошли

end

until Success or (k=8);

5

Page 6: Алгоритм перебора с возвратом

Back1:= Success;

end;

begin // основная программа

read(x0,y0);

Solution[x0,y0]:=1;

if Back1(2,x0,y0) then

for i:=1 to n do // вывод массива ходов

begin

for j:=1 to n do

Write(Solution[i,j],' ');

writeln

end

else writeln('net resenia');

end.

Если размер доски 5×5, а начало обхода осуществляется с клетки с

координатами (n,n), то в результате вызова функции Back1(2,5,5) будет

сформирована матрица, содержащая номера ходов коня.

Алгоритм перебора с возвратом для нахождения всех решений

Алгоритм построения всех решений проще предыдущего, поскольку в нем

отсутствует флаг Success и после нахождения решения оно сразу же

обрабатывается (например, выводится на экран), и поиск оставшихся решений

продолжается.

6

Page 7: Алгоритм перебора с возвратом

procedure Backall(i: integer);

begin

Формирование списка кандидатов

repeat

выбор очередного кандидата

if подходит then

begin

запись кандидата

if решение неполное then

Backall(i+1)

else печать решения (или его обработка)

стирание кандидата

end

until кандидатов больше нет

end;

Пример: Задача о n ферзях. На шахматной доске размера n×n расставить n

ферзей так, чтобы они не били друг друга. Эту задачу решил больше 200 лет тому

назад великий математик Леонард Эйлер. Очевидно, на каждой из 8 вертикалей

должно стоять по ферзю.

X

X

X

X

X

Решение:

7

Page 8: Алгоритм перебора с возвратом

диагональ 1-го типа и диагональ 2-го типа:

var

{признак занятости диагоналей первого типа }

up: array[2 .. 16] of boolean;

{признак занятости диагоналей второго типа }

down: array[-7 .. 7] of boolean;

{признак занятости вертикали }

vert: array[1 .. 8]of boolean;

{номер вертикали, на которой стоит ферзь на каждой

горизонтали }

ihor: array[1 .. 8]of integer;

n: integer;

{проверка на допустимость хода в позицию (i,j)}

function d_hod(i, j: integer): boolean;

begin

d_hod := vert[j] and up[i+j] and down[i-j];

end;

procedure hod(i, j: integer); { сделать ход }

begin

ihor[i] := j;

8

Page 9: Алгоритм перебора с возвратом

vert[j] := false;

up[i+j] := false;

down[i-j] := false;

end;

procedure o_hod(i, j: integer); { отменить ход }

begin

vert[j] := true;

up[i+j] := true;

down[i-j] := true;

end;

Нахождение всех решений:

procedure print;

var i: integer;

begin

write(' ',s,' ');

for i:=1 to n do write(ihor[i],' ');

writeln;

end;

procedure find_all(i: integer);

var j: integer;

begin

if i<=n then begin

for j:=1 to n do

if d_hod(i,j) then begin

hod(i,j);

find_all(i+1);

o_hod(i,j);

end;

9

Page 10: Алгоритм перебора с возвратом

end

else begin

inc(s);

print;

end;

end;

Общая схема перебора всех возможных решений, где X1 , X2 , ... , Xn –

представляет собой пространство перебора.

procedure перебор_с_возвратом(i : integer);

var k : integer;

begin

if i > n then

<найдено решение - печать>

else

for k:=1 to nr[i] do begin

x[i]:= k;

if <x[i] совместимо с x[1],...,x[i-1]> then

перебор_с_возвратом(i+1);

end;

end;

Пример: Задача о лабиринте

Дано клеточное поле, часть клеток занята препятствиями. Необходимо

попасть из некоторой заданной клетки в другую заданную клетку путем

последовательного перемещения по клеткам.

Классический перебор выполняется по правилам:

в каждой клетке выбирается еще не исследованный путь;

если из исследуемой в данный момент клетки нет путей, то

возвращаемся на один шаг назад (в предыдущую клетку) и пытаемся

выбрать другой путь.

10

Page 11: Алгоритм перебора с возвратом

Пусть лабиринт представлен матрицей L, состоящей из n строк и m колонок,

со значениями элементов 0 (проход) и 1 (стена/барьер). На позиции (xb,yb)

расположен кусок сыра, на позиции (xs,ys) – мышка. Необходимо вывести все

возможные достижимые перемещения мышки к сыру, зная, что она может

перемещаться только через свободные проходы в направлениях С, СВ, В, ЮВ, Ю,

ЮЗ, З, СЗ.

Например, для лабиринта 4×4 с позицией мыши (1,1) и сыра (4,4)

перемещения м.б.

0 1 1 10 1 1 10 1 0 01 0 1 0

Решение 1: Решение 2:

* 1 1 1 * 1 1 1* 1 1 1 * 1 1 1* 1 * * * 1 * 01 * 1 * 1 * 1 *

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

позиции, исключая риск петли, обозначим в лабиринте пройденные позиции

номером шага k.

11

Page 12: Алгоритм перебора с возвратом

Массивы Dx и Dy описывают смещения по x и по y при ходе в

соответствующем направлении, инициализируем их как строка и колонка, для

всех 8 возможных перемещений.

Чтобы не проверять постоянно, если мышка не дошла каким-то образом до

границы лабиринта, обнесем лабиринт стеной (соответственно две строки и

колонки определим значением 1).

Условия:

Из позиции (x,y) мышка может переместиться в направлении dir, т.е. на

позицию (x+Dx[dir],y+Dy[dir]), только если

L[x+Dx[dir],y+Dx[dir]]=0

(эта позиция является проходом, через который сможет пройти мышка).

program Labirint;

const

NMax = 20; {максимальная размерность лабиринта}

Dx: array[1..8] of integer = (-1,-1,0,1,1,1,0,-1);

Dy: array[1..8] of integer = (0,1, 1,1,0,-1,-1,-1);

type

Index = 0 .. NMax +1;

Labirint = array[Index, Index] of integer;

var L: Labirint;

n, m, xs, ys, xb, yb: Index;

k,NRes: word;

procedure Init;

var i, j: Index;

begin

12

Page 13: Алгоритм перебора с возвратом

readln(n,m);

readln(xs, ys, xb, yb);

for i:=1 to n do

for j:=1 to m do

read(L[i,j]) ;

end;

procedure Stena; {обнесем лабиринт стеной-барьером}

var i: Index;

begin

for i := 0 to n+1 do {стена слева и справа}

begin L[i,0]:= 1; L[i,m+1]:= 1 end;

for i := 0 to m +1 do {стена сверху и снизу}

begin L[0,i]:= 1; L[n+1,i]:= 1 end

end;

function Final(x,y: Index): boolean;

{возвращает true, если на позиции (x,y) есть сыр}

begin

Final := false;

if (x = xb) and (y = yb) then Final:= true

end;

procedure Vivod;

var i,j: Index;

13

Page 14: Алгоритм перебора с возвратом

begin

inc(Nres);

writeln(Решение №', NRes);

for i:= 1 to n do begin

for j:= 1 to m do

write(L[i,j]);

writeln

end;

end;

procedure Poisk(x,y: Index; k:word);

var dir: 1..8;

begin

L[x,y]:= k; {ставим метку для позиции x y}

if Final(x,y) then

Vivod

else

for dir:=1 to 8 do

if L[x+Dx[dir],y+Dy[dir]]=0 then {непройденная

позиция}

Poisk(x+Dx[dir], y+Dy[dir], k+1);

L[x,y]:=0; {при возврате снимаем метку, что

посетить данный проход (позицию) и при других вариантах}

end;

14

Page 15: Алгоритм перебора с возвратом

begin {основная программа}

Init; Stena;

Poisk(xs,ys,1);

if NRes=0 then writeln('Нет решений!');

end.

15