2. Алгоритми з повертанням

Розв’язок практичних задач в деяких випадках може вимагати перебору всіх можливих варіантів розв’язку. Такими є задачі з області «штучного інтелекту». Тут потрібно будувати алгоритми, що  знаходять рішення певної задачі не за фіксованими правилами обчислення, а методом спроб і помилок.

Звичайно процес спроб і помилок розділяється на окремі підзадачі. Часто ці підзадачі найбільш природно описуються за допомогою рекурсії. Процес проб і помилок можна розглядати в загальному виді як пошуковий процес, що поступово будує і переглядає дерево підзадач.

Розглянемо добре відомий приклад - задачу про хід коня.

Дано дошку n´n, що містить n2 полів. Кінь, що ходить відповідно до шахових правил, міститься на полі з початковими координатами (x0, y0). Потрібно покрити всю дошку ходами коня, тобто знайти обхід дошки, якщо він існує, з n2-1 ходів, такий, що кожне поле відвідувалось рівно один раз.

Очевидно, що цю задачу покриття n2 полів можна звести до більш простої: або виконати з поточної клітини черговий хід, або виявити, що ніякий хід з цієї клітини неможливий. Тому будемо будувати алгоритм, що намагається зробити черговий хід обходу дошки.

Умовно спробу ходу можна описати таким чином:

Procedure  спроба_наступного_ходу;

Begin

ініціація_вибірки_ходів:

repeat

вибір_наступного_можливого_ходу_з_

списку_чергових_ходів;

if хід_прийнятний then

  begin

   запис_ходу;

  if дошка_не_заповнена then

  begin

  спроба_наступного_ходу;

  if невдача then стирання_попереднього_ходу;

  end;

  end;

until (хід_є_вдалим) or (немає_інших_можливих_ходів);

End;

Щоб точно описати цей алгоритм, необхідно  вибрати зручне подання даних. Дошку представимо у виді матриці, індексованої за обома координатами числами від 1до n:

type index=1..n;

var h: array  [index, index] of  integer;

Кожен хід будемо  відзначати в матриці його порядковим номером. В такому випадку заповнення матриці буде мати такий вигляд:

h [x,y]=0;  поле з координатами (x,y) не відвідувалося,

h [x,y]=i;  поле з координатами (x,y)  відвідувалося на i-ому ході  (1£i£n2).

Тоді у випадку позитивного результату наприкінці природним чином роздрукується  вся матриця ходів. Початкові умови для наступного ходу задаються координатами поля (x,y), з якого робиться хід з номером ходу i. Для визначення вдалості ходу будемо користуватися логічним параметром–результатом: q=true  означає удачу (можливість наступного ходу), q=false - невдачу.

Спробуємо зробити ще кілька уточнень. «Дошка_не_заповнена» можна виразити у вигляді умови  i<n2. «Хід_прийнятний» означає наступне: якщо позначити через  u і v координати можливого наступного ходу, то необхідно, щоб поле з цими координатами знаходилося на дошці і раніше не відвідувалося. Очевидно, з кожної клітини є вісім варіантів вибору наступного ходу. Нові координати зручно задавати через відповідні прирости поточних координат. Однак слід врахувати, що не завжди клітина з новими координатами може бути на дошці.

Програма, що реалізує даний алгоритм, матиме такий вигляд:

program knightstour; 

uses dos,crt; 

const

n=5;

nsq=n*n; 

type index=1..n; 

var

i,j:index;

q:boolean;

s:set of index;

a,b:array[1..8] of integer;

h:array[index,index] of integer;

x1,y1:index;

t1,t2,t3,t4,t5,t6,t7,t8:word;

h1,h2:longint;

sec,sec1,min,msec:integer; 

procedure try(i:integer;x,y:index;var q:boolean);

{процедура визначення можливості ходу}

var

k,u,v:integer;

q1:boolean; 

begin

k:=0;

repeat

k:=k+1;

q1:=false;

u:=x+a[k];

v:=y+b[k];

if (u>0) and (v>0) and (u in s) and (v in s ) and (h[u,v]=0)

then begin

  h[u,v]:=i;

  if i<nsq

  then begin

  try(i+1,u,v,q1);

  if not(q1) then h[u,v]:=0;

  end

else q1:=true

end

until q1 or (k=8);

q:=q1; 

end; 

begin 

*******************************************

  О с н о в н а п р о г р а м а 

******************************************* 

}

s:=[1..n];

a[1]:=2;  b[1]:=1;

a[2]:=1;  b[2]:=2;

a[3]:=-1;  b[3]:=2;

a[4]:=-2;  b[4]:=1;

a[5]:=-2;  b[5]:=-1;

a[6]:=-1;  b[6]:=-2;

a[7]:=1;  b[7]:=-2;

a[8]:=2;  b[8]:=-1;

for i:=1 to n do

  for j:=1 to n do h[i,j]:=0;

clrscr;

write(’Введіть індекси початкової клітини :’);

read(x1,y1);

clrscr;

h[x1,y1]:=1;

gettime(t1,t2,t3,t4);

try(2,x1,y1,q);

gettime(t5,t6,t7,t8);

writeln;

writeln;

if q then

for i:=1 to n do

begin

  for j:=1 to n do

  write(h[i,j]:4);

  writeln

end

else writeln(’немає розв”язку’);

h1:=(t2*60+t3)*100+t4;

h2:=(t6*60+t7)*100+t8;

sec1:=(h2-h1) div 100;

msec:=(h2-h1) mod 100;

min:=sec1 div 60;

sec:=sec1 mod 60;

writeln;

writeln(Час роботи програми ',min,' мин ',sec,' сек ',msec,'  мсек');

repeat until keypressed;

end.

Для дошки розмірності 5х5 за умови початкової клітини з координатами (1,1) обхід буде мати такий вигляд:

  1  6  15  10  21

  14  9  20  5  16

  19  2  7  22  11

  8  13  24  17  4

  25  18  3  12  23

Якщо для цієї ж дошки обхід розпочати з клітини з координатами (1,2), то розв’язок буде відсутнім.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 
25 26 27 28  Наверх ↑