Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекції на ОП та алг. мови.doc
Скачиваний:
13
Добавлен:
03.11.2018
Размер:
785.92 Кб
Скачать

Алгоритми з поверненням. Розв’язок задачі про рух коня

Особливо інтригує область програмування — задачі так називаного «штучного інтелекту». Тут ми маємо справу з алгоритмами, що шукають рішення не за заданими правилами обчислень, а шляхом проб і помилок. Звичайно процес проб і помилок розділяється на окремі задачі. Часто ці задачі найбільше природно виражаються в термінах рекурсії і вимагають дослідження кінцевого числа підзадач. У загальному вигляді весь процес можна мислити як процес пошуку, що будує (і обрізає) дерево підзадач. У багатьох проблемах таке дерево пошуку росте дуже швидко, ріст залежить від параметрів задачі і часто буває експонентним. Відповідно збільшується і вартість пошуку. Іноді, використовуючи деякі евристики, дерево пошуку вдається скоротити і тим самим звести витрати на обчислення до розумних меж.

Почнемо з демонстрації основних методів на добре відомому прикладі— задачі про хід коня.

Дано дошку розміром n×n, тобто таку, що містить n2 полів. Спочатку на поле з координатами х0, у0 ставлять коня — фігуру, що переміщується по звичайних шахових правилах. Задача полягає в пошуку послідовності ходів (якщо вона існує), при якій кінь точно один раз побуває на всіх полях дошки (обійде дошку), тобто потрібно обчислити n2 — 1 ходів.

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

PROCEDURE TryNextMovet

BEGIN

ініціалізація вибору ходу;

REPEAT

вибір чергового кандидата зі списку ходів;

IF підходить THEN

BEGIN

запис ходу

IF дошка не заповнена Тhеn

BEGIN

TryNextMove;

IF невдача Then знищення попереднього ходу

END

END

UNTIL (був удалий хід) OR(кандидатів більше немає)

ENDTtyNextMove

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

CONST razmer=100

VAR h: ARRAY [1..razmer, 1..razmer] OF INTEGER

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

h[x, у] = 0: поле (x, y) ще не відвідувалося

h[x,y] = i поле (x,y) відвідувалося на i-му ході.

Тепер потрібно вибрати відповідні параметри. Вони повинні визначати початкові умови наступного ходу і результат (якщо хід зроблений). У першому випадку досить задавати координати :поля :(x,y), звідки роблять хід, і число яке вказує номер ходу (для фіксації). Для результату ж потрібно булевий параметр; якщо він істиний, то хід був можливий.

Які оператори можна уточнити на основі прийнятих рішень? Очевидно, умову «дошка не заповнена» можна переписати як i < n2. Крім того, якщо ввести дві локальні змінні u і v для можливого ходу, обумовленого відповідно до правил «стрибка» коня, то предикат «допустимо» можна представити як логічну кон’юнкцію умов, що нове поле знаходиться в межах дошки (l<=u<=n і 1<=v<=n) і ще не відвідувалося (huv=0).

Фіксація припустимого ходу виконується за допомогою присвоювання huv := i, а скасування — за допомогою huv=0. Якщо ввести локальну змінну ql і використовувати її як параметр-результат, при рекурсивних звертаннях до цього алгоритму то ql можна підставити замість «є хід». Так ми приходимо до варіанта

PROCEDURE TRY(I,X,Y:INTEGER; VAR Q:BOOLEAN);

VAR U,V:INTEGER; Q1:BOOLEAN;

BEGIN

ініціалізація вибору ходу;

REPEAT

<U,V> — координати наступного ходу;

IF (U>=1) AND (U<=N) AND (V>=1) AND (V<=N) AND (H[U,V]=0)

THEN

BEGIN

H[U,V]:=I

IF I<N*N THEN

BEGIN

IF NOT Q1

THEN H[U,V]:=0

ELSE Q1:= TRUE

END

END;

UNTIL Q1 OR нема інших кандидатів;

Q:=Q1

END.

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

Якщо задана початкова пара координат x, у, то для наступного ходу u, v існує вісім можливих кандидатів. На малюнку вони пронумеровані від 1 до 8. Одержувати u, v з x, у дуже просто, досить до останнього додавати різниці між координатами, що зберігаються або в масиві різниць, або в двох масивах, що зберігають окремі різниці. Позначимо ці масиви через dx і dy і будемо вважати, що вони відповідним чином ініційовані. Для нумерації чергового ходу-кандидата можна використовувати індекс k. Подробиці показані в nроrрамі. Перший раз до рекурсивної процедури звертаються з параметрами x і y — координатами поля, з якого починається обхід. Цьому полю повинне бути присвоєне значення 1, інші поля маркіруються як вільні:

h [x0, y0] := 1; try (2, x0, y0, q)

Не можна упускати ще одну деталь. Змінна H[u,v] існує лише в тому випадку коли і u i v лежать в діапазоні індексів 1..n. Тому в умові важливо щоб складова H[u,v] =0 була останньою.

program kings_tour;

uses crt,dos;

const razmer=100;

var i,j,n,nsqr:integer;

q:boolean;

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

h:array[1..razmer,1..razmer] of integer;

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

var k,u,v:integer;

q1:boolean;

begin

k:=0;

repeat

k:=k+1;q1:=false;

u:=x+dx[k];v:=y+dy[k];

if (u>=1)and (u<=n) and(v>=1) and(v<=n)and(h[u,v]=0) then

begin

h[u,v]:=i;

if i<nsqr 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

clrscr;

write('Razmer->');readln(n);

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

dx[5]:=-2;dx[6]:=-1;dx[7]:=1;dx[8]:=2;

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

dy[5]:=-1;dy[6]:=-2;dy[7]:=-2;dy[8]:=-1;

for i:=1 to n do

for j:=1 to n do

h[i,j]:=0;

write('i->');readln(i);

write('j->');readln(j);

nsqr:=n*n;h[i,j]:=1;

try(2,i,j,q);

if q then begin

for i:=1 to n do

begin

for j:=1 to n do

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

writeln;

end;

end

else write('goo!');

end.

Характерною особливістю таких алгоритмів є те, що в них виконуються кроки в напрямку загального розв’язку, і всі ці кроки фіксуються таким чином, щоб пізнше можна було повернутись “всліпу” відкидаючи ті кроки, що не ведуть до загального розв’язку. Такий процес називають відкатом або поверненням (backtraking).