Metóda prehľadávania s návratom (backtrack).
Gabriela Andrejková
Prírodovedecká fakulta, PF UPJŠ, Košice
e-mail:
andrejk@kosice.upjs.sk
Abstrakt.
Metóda s návratom je jednou z možnosti systématického prehľadávania priestoru, v ktorom sa riešenie môže nachádzať. Hlavnou myšlienkou tejto metódy je postupná konštrukcia riešenia určitého problému, tak aby, pokiaľ je to možné, boli ušetrené zbytočné kroky, ktoré nepovedú k riešeniu daného problému.
Úvod
Predpokladajme, že riešením problému, označme h
o P, má byť vektor a[n] = (a[1], a[2], ..., a[n]), ktorý splňuje určitú vlastnosť, označme ju A. Vo všeobecnosti platí, že i-tá položka tohoto vektora bola vybratá z množiny povolených hodnôt V[i], t.j. a[i] Î V[i] pre i= 1, 2, ..., n. Vektor a[n] má n, pre n=1, 2, ..., prvkov a je postupne predlžovaný, t.j. pre n=1 má jeden prvok, pre n=2 dva prvky, atď. Triviálna metóda riešenia by znamenala vyskúšať všetky možnosti (t.j. použiť hrubú silu ), ale to by viedlo k prevereniu |V[1]|*|V[2]|*... *|V[n]| vektorov. Metóda prehľadávania s návratom umožňuje ušetriť skúmanie niektorých vektorov, pretože vektor riešenia je konštruovaný postupne a predlžovaný v súlade s vlastnosťou A. Predĺženie je vždy najprv skontrolované a je možné len vtedy, ak nebude porušená vlastnosť A.Jedna z verzií algoritmu pre túto metódu môže byť zaznamenaná nasledovne:
Táto metóda je mnohokrát používaná v rekurzívnom zápise, čo je veľmi prehľadné a jednoduché. Rekurzívna verzia tejto metódy:
procedure
Skus_dalsie_predlzenie;BEGIN
Inicializácia výberu ďalšieho predĺženia;
REPEAT
Vyber ďalšieho kandidáta z množiny možných kandidátov;IF
prijateľný THENBEGIN
Zaznamenaj predĺženie;
IF
riešenie nie je úplné THENBEGIN
Skus_dalsie_predlzenie;
IF neúspešný THEN Vymaž predchádzajúce predĺženie
END
END
UNTIL
(nie sú ďalší kandidáti) alebo (riešenie je nájdené)END
Problém ôsmich dám
Problém ôsmich dám je jedným zo známych problémov, pri ktorých riešení je možné aplikovať metódu prehľadávania s návratom. Preskúmal ho C. F. Gauss v roku 1850, ale jeho riešenie nedoviedol do úspešného konca. Nepodarilo sa mu nájsť analytické vyjadrenie riešenia a preskúmať všetky možnosti bolo vtedy veľmi náročné na čas a pracné.
Tento problém možno sformulovať takto:
Osem dám je treba rozmiestniť na šachovnici takým spôsobom, aby sa navzájom neohrozovali (v zmysle šachových pravidiel). Príklad takého rozloženia je uvedený na Obr. 1.
D |
|||||||
D |
|||||||
D |
|||||||
D |
|||||||
D |
|||||||
D |
|||||||
D |
|||||||
D |
Obr 1. Uloženie dám tak, že sa navzájom neohrozujú
Riešenie:
Prípravné kroky k vytvoreniu programu, ktorý nájde riešenie.
Sformulujeme rekurzívnu procedúru v hrubých rysoch nasledovne:
procedure Skus_dalsiu_damu (i: integer; var q: boolean);
BEGIN
Inicializácia výberu pozícií pre i-tú dámu;
REPEAT
Urobiť výber ďalšej pozí
cie; q:=false;IF
Umiestniteľná THENBEGIN
Ulož dámu;
IF
i<8 THENBEGIN
Skus_dalsiu_damu(i+1, q);
IF Neúspešný THEN Odstráň dámu
END
END
UNTIL
(Nie sú ďalší kandidáti) alebo (Úspešný)END
; {Skus_dalsiu_damu}Teraz vyjadríme jednotlivé nepresné formulácie v tejto procedúre a vytvoríme program, ktorý nájde jedno riešenie.
Prv než urobíme upresnenie uvedených formulácií, určíme dátové typy, s ktorými budeme pracovať, t.j. dátové typy, ktoré budú reprezentovať uloženie dámy a obsadenie riadkov a uhlopriečok.
Pr
etože vieme, že v každom stĺpci má byť uložená práve jedna dáma, nie je nutné používať na ukladanie dvojrozmerné pole, postačí jednorozmerné pole, v ktorom i-tý prvok bude vyjadrovať číslo riadku v i-tom stĺpci, kde je postavená i-tá dáma. Teda deklarujemex: array [1..8] of integer;
Obsadenie j-tého riadku budeme zaznamenávať v poli a, kde j-tý prvok nadobudne hodnotu true, ak je riadok voľný a inak nadobudne hodnotu false. Teda deklarujeme
a: array [1..8] of boolean;
Ak zavedieme číslovanie riadkov zhora nadol 1.. 8 a číslovanie stĺpcov zľava napravo 1..8 na Obr. 1, môžeme jednotlivé uhlopriečky označiť číslami i+j , t.j. uhlopriečky 2..16 smerujúce nadol doprava a uhlopriečky smerujúce sprava doľava nadol i -j , tieto uhlopriečky majú čísla –7..7. Teda
deklarujemeb: array [2..16] of boolean;
c: array [-7..7] of boolean;
Ak bude uhlopriečka voľná prvok poľa, ktorý ju reprezentuje nadobudne hodnotu true, inak nadobudne hodnotu false.
Použitím týchto definícií je možné vyjadriť nepresné formulácie presn
ejšie takto:a[j]:= true; b[i+j]:= true; c[i-j]:=true;
Program v jazyku Turbo Pascal môže byť zapísaný nasledovne:
program
Osem_dam;{Najde jedno riesenie problemu 8 dam}var i: integer; {poradove cislo damy}
q: boolean; {q=true, ak je riesenie najdene}
a: array [1..8] of boolean; {vektor urcujuci obsadenie riadkov}
b: array [2..16] of boolean; {vektor urcujuci obsadenie diagonal}
c: array [-7..7] of boolean; {vektor urcujuci obsadenie diagonal}
x: array [1..8] of integer; {vektor urcujuci ulozenie dam}
procedure Skus_dalsiu_damu( i: integer; var q: boolean);
{Rekurzivna metoda prehladavania s navratom pre postupne ukladanie dam}
var j: integer; {pozicia i-tej damy v riadku}
begin
j:= 0;
repeat
j:= j+1; q:= false;
if a[j] and b[i+j] and c[i-j] then
begin {je mozne ulozenie v riadku j}
x[i]:= j; {ulozenie i-tej damy v j-tom riadku}
a[j]:= false;
b[i+j]:= false; c[i-j]:= false;
if i<8 then
begin {pocet doposial ulozenych dam je
mensi ako 8}
Skus_dalsiu_damu(i+1, q);
if not q then
begin {posledna dama nemoze byt ulozena}
a[j]:= true; {v j_tom riadku}
b[i+j]:= true; c[i-j]:= true;
end
end else q:= true
end
until q or (j=8);
end; {Skus_dalsiu_damu}
begin {hlavny program}
for i:= 1 to 8 do a[i]:= true;
for i:= 2 to 16 do b[i]:= true;
for i:=-7 to 7 do c[i]:= true;
Skus_dalsiu_damu(1,q);
if q then
begin
write(' Riesenie problemu 8 dam: ');
for i:= 1 to 8 do write(x[i]:3); writeln
end else writeln('Riesenie neexistuje.');
end.
Program nájde jediné riešenie: 1, 5, 8, 6, 3, 7, 2, 4.
Problém jazdca na šachovnici
Tento problém je tiež typickým problémom, pri ktorého riešení je možné použiť metódu prehľadávania s návratom. Problém je možné sformulovať nasledovne:
Jazdec sa po šachovnici typu n x n
pohybuje v zmysle pravidiel šachu. Je potrebné nájsť jeho cestu po šachovnici takú, že prejde celú šachovnicu a každé políčko navštívi práve jedenkrát.V tomto prípade algoritmus pracuje s dvojrozmerným poľom, do ktorého zapisuje poradové čísla vykonaných skokov. Ak ďalší skok nie je možný, vráti sa naspäť, číslo vykonaného skoku vymaže a pokračuje po inej ces
te. Teda deklarujemeh: array [index, index] of integer;
Z pozícií v strede šachovnice má jazdec 8 možností skokov, na okraji a v rohoch menej, preto je potrebné kontrolovať, či indexy prvku poľa ďalšieho skoku sú na šachovnici. Uvedené možnosti skokov sú preskúmavané systematicky, rovnako pri každom skoku. Zmeny pozícií pri vykonaní ďalšieho skoku sú stanovené v poliach
a: array[1..8] of integer = (2, 1, -1, -2, -2, -1, 1, 2);
b: array[1..8] of integer = (1, 2, 2, 1, -1, -2, -2, -1);
To znamená, ak bol jazdec v pozícii [x, y] a vykoná k-tý skok, k=1, 2, .... 8, tak jeho nová pozícia bude [x+a[k], y+b[k].
Uvedieme výsledný program pre riešenie tohto problému, ktorý má nasledujúci tvar:
program Pohyb_jazdca;{Pokrytie sachovnice skokmi jazdca tak, aby kazde policko bolo navstivene prave jedenkrat a bola prejdena cela sachovnica.}const n=5; nsq=n*n; {Velkost sachovnice je 5}a: array[1..8] of integer = (2, 1, -1, -2, -2, -1, 1, 2);
b: array[1..8] of integer = (1, 2, 2, 1, -1, -2, -2, -1);
{ a, b -- pomocne polia vyjadrujuce zmenu suradnic jazdca z danej
pozicie}
type index=1..n;
var i, j: integer; {Riadiace premenne cyklov}
s: set of index; {Mnozina pozicii v jednom smere}
q: boolean; {Vyjadruje, ci riesenie najdene}
h: array [index, index] of integer; {Sachovnica}
procedure vyskusaj(i: integer; x, y: index; var q: boolean);
{Vykona i-ty skok z pozicie (x, y) a rekuzivne pokracuje dalej;
q nadobudne hodnotu true, ak je riesenie najdene}
var k, u, v: integer;
q1: boolean;
begin
k:=0; {Inicializacia pre moznosti skokov}
repeat
k:= k+1; {Prechod na skumanie dalsej moznosti}
q1:= false; { pre i-ty skok}
u:= x+ a[k]; v:= y+ b[k];
{Vypocet novej pozicie pri k-tej moznosti}
if (u in s) and (v in s) then
if h[u, v]=0 then
begin {Skok je vykonatelny}
h[u, v]:= i;
if i<nsq then {Sachovnica este nie je pokryta}
begin {Prechod na vykonanie dalsieho skoku}
vyskusaj(i+1, u, v, q1);
if not q1 then h[u, v]:= 0;
{Zrusenie posledneho skoku}
end else q1:=true {Riesenie je najdene}
end
until q1 or (k=8);
q:= q1;
end; {vyskusaj}
begin {hlavny program}
s:= [];
for i:= 1 to n do s:= s + [i];
for i:= 1 to n do
for j:=1 to n do h[i, j]:= 0;
h[1, 1]:= 1; {Jazdec zacina na policku (1,1)}
vyskusaj(2, 1, 1, q);
if q then
for i:= 1 to n do
begin for j:= 1 to n do write(h[i, j]:3);
writeln
end else writeln(' Riesenie neexistuje ')
end.
Riešenie, ktoré je nájdené pre n=5:
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 |
Literatúra:
ex. slovenský preklad v roku 1987