Documente Academic
Documente Profesional
Documente Cultură
L10 Backtracking PDF
L10 Backtracking PDF
1 Consideraii teoretice
1.1 Aplicabilitatea algoritmilor de tip Backtracking
Algoritmii de tip backtracking se aplic la problemele unde spaiul soluiilor S este de forma unui
produs cartezian S = S0 S1 ... Sn-1. Orice element x din spaiul soluiilor S va fi un vector de
forma x = (x0, x1, ..., xn-1), cu xi Si, 0<i<n.
Nu toate elementele xS sunt soluii valide ale problemei. Doar acele elemente x care
satisfac anumite condiii impuse de problem vor fi soluii valide. Definim condiiile care trebuie
satisfcute sub forma unei funcii booleene Solutie(x0,x1,...,xn-1). Un element x=(x0, x1, ..., xn-1) S
este soluie a problemei dac funcia Solutie aplicat componentelor lui x va returna valoarea true.
Scopul este de a gsi acei vectori xS pentru care funcia Solutie returneaz true.
Modul de lucru al algoritmului este urmtorul: se alege un element x 00 din S0, apoi un element x10 din
S1, apoi un element x20 din S2, .a.m.d, pn cnd se va fi ales un element xn-10 din Sn-1. n acest
moment vom avea un vector x = (x 00, x10, ..., xn-20, xn-10) S. Evalum rezultatul funciei Solutie
pentru acest vector. Dac obinem rezultatul true atunci avem o soluie corect a problemei. Dac
obinem rezultatul false, soluia nu este corect i continum cutarea.
Aplicm principiul revenirii pe cale, adic ne ntoarcem cu un pas n urm, nainte de
alegerea elementului xn-10 Sn-1. De aici continum alegnd un alt element xn-11 Sn-1, xn-11 xn-10.
Vom obine un nou vector x' = (x 00,x10,...,xn-20,xn-11) S asupra cruia aplicm din nou funcia Solutie.
Dac rezultatul este false, revenim din nou cu un pas n urm i continum cu alegerea unui alt
element xn-12 Sn-1, xn-12 xn-10 i xn-12 xn-11. Repetm aceti pai pn la epuizarea tuturor
elementelor din Sn-1.
Dup ce am epuizat toate elementele mulimii S n-1, vom fi la faza n care am ales elementele
x0 , x1 , ..., xn-20. Ar trebui s alegem un element din Sn-1, dar cum toate elementele din Sn-1 au fost deja
0 0
alese, revenim cu nc un pas n urm pe cale, i alegem un alt element x n-21 Sn-2. Pe urm
rencepem alegerea elementelor din Sn-1 cu primul dintre ele, xn-10 Sn-1. Vom avea vectorul x = (x00,
x10,..., xn-30, xn-21, xn-10).
Procednd n acest mod, vom ajunge practic s construim toi vectorii xS, adic vom
explora ntreg spaiul soluiilor posibile. Exist n principiu dou categorii de probleme: unele care
cer gsirea unei singure soluii i unele care cer gsirea tuturor soluiilor. Atunci cnd se cere gsirea
unei singure soluii, putem opri cutarea dup gsirea primului vector x pentru care funcia Solutie
returneaz valoarea true. La cealalt categorie de probleme este nevoie s parcurgem ntreg spaiul
soluiilor pentru a gsi toate soluiile.
Parcurgerea ntregului spaiu al soluiilor posibile este foarte mare consumatoare de timp. n
general se poate accelera aceast parcurgere prin urmtoarea optimizare: pornind de la funcia
Solutie(x0, x1, ..., xn-1), definim o funcie Continuare(x0, x1, ..., xk) care s ne spun dac, avnd alese
la un moment dat elementele x0, x1, ..., xk exist o ans de a se ajunge la o soluie. Aceast
optimizare este foarte util deoarece de multe ori dup alegerea ctorva elemente x 0, x1, ..., xk ne
putem da seama c nu vom putea gsi nici o soluie valid care conine elementele respective. n
asemenea situaii nu mai are rost s continum alegerea de elemente pn la x n-1, ci putem direct s
revenim cu un pas n urm pe cale i s ncercm cu un alt element xk'.
2 Implementare
Algoritmul backtracking redactat sub form de pseudocod arat n felul urmtor:
k = 0;
while (k >= 0)
{
do
{
* alege urmatorul x[k] din multimea S[k]
* evalueaza Continuare(x[1], x[2], ..., x[k])
}
while ( !Continuare(x[1], x[2], ..., x[k]) &&
(* mai sunt elemente de ales din multimea S[k]) )
Dac analizm modul de funcionare al algoritmului backtracking, vom vedea ca la orice moment de
timp ne este suficient un tablou de n elemente pentru a memora elementele x 0, x1, ..., xn-1 alese. Ca
urmare n implementare declarm un tablou x de dimensiune n. Tipul de date al tabloului depinde de
problem, de tipul mulimilor S.
Variabila k ne indic indicele mulimii din care urmeaz s alegem un element. La nceput de
tot trebuie s alegem un element din mulimea S 0, de aceea l iniializm pe k cu valoarea 0. Pe urm
k se modific n funcie de modul n care avansm sau revenim pe calea de cutare.
Pentru alegerea urmtorului xk din mulimea Sk, ne bazm pe faptul c ntre elementele
fiecrei mulimi Sk exist o relaie de ordine. Dac nc nu s-a ales nici un element din mulimea S k
atunci l alegem pe primul conform relaiei de ordine. Dac deja s-a ales cel puin un element, atunci
l alegem pe urmtorul neales, conform relaiei de ordine.
3 Exemple
3.1 Sritura calului pe tabla de ah
Enun Avem o tabl de ah de dimensiune 8x8. S se gaseasc toate modalitile de a deplasa un cal
pe aceast tabl, astfel nct calul s treac prin toate csuele de pe tabl exact o dat.
Rezolvare Pentru a parcurge fiecare csu de pe tabla de ah exact o dat, calul va trebui s fac
exact 8 8 = 64 de pai. La fiecare pas el poate alege oricare din cele 64 de csue de pe tabl. S
codificm csuele de pe tabla de ah n modul urmtor: csua de la linia i i coloana j o notm prin
perechea (i,j). S notm mulimea tuturor csuelor de pe tabl cu C: C = {(0,0), (0,1), ..., (0,7), (1,0),
..., (7,7)}.
O soluie a problemei o putem nota printr-un vector x = (x 0, x1, ..., x63), unde xS = C C
C ... C (produs cartezian n care mulimea C apare de 64 de ori), iar xi C, i {0, 1, ..., 63}.
Cu aceste elemente putem vedea c se poate aplica o rezolvare de tip backtracking. Funcia
Solutie va verifica s nu existe dou elemente c i i cj care au aceeai valoare, deoarece asta ar
nsemna c s-a trecut de dou ori prin aceeai csu. n plus funcia mai trebuie s verifice faptul c
i {0, 1, ..., 61, 62} calul poate sri de la csua c i la csua ci+1. Asta nseamn c fie ci i ci+1 se
afl la dou linii distan i la o coloan distan, fie ele se afl la o linie distan i la dou coloane
distan.
Funcia Continuare trebuie s fac exact aceleai verificri ca i funcia Solutie, dar nu pentru
toate 64 de csue ci pentru cele k csue care au fost alese pn la un moment dat.
Cod surs n continuare prezentm codul surs pentru rezolvarea acestei probleme.
#include <stdio.h>
#include <stdlib.h>
#define INVALID -1
int main(void)
{
/* Pentru o tabla de dimensiune N vom memora
solutiile intr-un vector de dimensiune N*N.
Fiecare element din vector va fi la randul lui
un vector cu doua elemente, primul element va
memora linia de pe tabla, iar al doilea element
va memora coloana de pe tabla. */
int c[N*N][2];
int k, i;
int pe_tabla, continuare;
int delta_l, delta_c;
k = 0;
while (k >= 0)
{
/* Incercam sa plasam mutarea "k" a
calului in fiecare casuta a tablei
de joc, pe rand. Evaluam la fiecare
alegere functia "Continuare". Ne
oprim fie atunci cand am incercat
toate casutele de pe tabla, fie
atunci cand gasim o casuta unde
functia "Continuare" returneaza
"true". */
do
{
/* Aici alegem urmatorul element
din multimea "C[k]". Daca elementul
"c[k]" este setat pe INVALID,
inseamna ca inca nu am ales nici
un element din multimea curenta,
deci alegem primul element (plasam
calul in casuta de la linia 0 si
coloana 0). */
if (c[k][0] == INVALID)
{
c[k][0] = 0;
c[k][1] = 0;
pe_tabla = 1;
}
/* Daca elementul "c[k]" nu este setat
pe invalid, inseamna ca deja am ales
o casuta din multimea "C[k]". Acum
alegem urmatoarea casuta de pe tabla.
Cu alte cuvinte incercam sa plasam
calul in urmatoarea casuta. Daca este
posibil incercam sa ramanem pe aceeasi
linie si sa ne deplasam cu o coloana
spre dreapta. */
else if (c[k][1] < N-1)
{
c[k][1]++;
pe_tabla = 1;
}
/* Daca cumva eram chiar la ultima casuta
din linie, atunci alegem prima casuta
din linia urmatoare. Ne asiguram ca nu
eram cumva si pe ultima linie a
tablei, caz in care am epuizat toate
casutele. */
else if (c[k][0] < N-1)
{
c[k][1] = 0;
c[k][0]++;
pe_tabla = 1;
}
/* Daca eram pe ultima linie a tablei,
atunci am epuizat toate casutele.
Marcam acest lucru setand variabila
"pe_tabla" pe 0. */
else
{
pe_tabla = 0;
}
/* Daca casuta "c[k]" aleasa este valida
(se afla pe tabla de joc), atunci
trecem la calculul functiei
"Continuare". */
if (pe_tabla)
{
/* Daca suntem la prima mutare a
calului, atunci mutarea este
valida oriunde ar fi ea pe
tabla. */
if (k == 0)
continuare = 1;
/* Daca nu suntem la prima mutare,
atunci trebuie sa facem o serie
de verificari. */
else
{
/* In primul rand verificam daca
de la pozitia precedenta a
calului pe tabla ("c[k-1]")
se poate ajunge in pozitia
aleasa acum printr-o mutare.
*/
delta_l = abs(c[k-1][0]-c[k][0]);
delta_c = abs(c[k-1][1]-c[k][1]);
continuare = (((delta_l == 1) &&
(delta_c == 2)) ||
((delta_l == 2) &&
(delta_c == 1)));
#define N 5
int c[N*N][2];
int count = 0;
if (continuare)
back(pas+1);
}
}
}
}
int main(void)
{
int i,j;
for (i=0; i<N; i++)
for (j=0; j<N; j++)
{
c[0][0] = i;
c[0][1] = j;
back(1);
}
printf("%d solutii\n", count);
return 0;
}
4 Probleme propuse
4.1 Ieirea din labirint
Configuraia labirintului se citete din fiierul text labirint.dat. Labirintul are forma unei matrici de
dimensiune NxM. Un element al matricii poate fi perete sau spaiu liber. n fiierul de intrare
labirintul este descris pe N linii de cte M caractere. Peretele se codific prin litera 'P', iar spaiul
liber prin caracterul '.'.
PPPP.PPP
P....PPP
PP..PPSP
PP.PP..P
P..PP.PP
P.PPP..P
P...PP.P
PPP.PP.P
PPP....P
PPPPPPPP
n afar de caracterele 'P' i '.', n fiierul de intrare va mai apare i caracterul 'S', o singur
dat. Caracterul 'S' reprezint un spaiu liber n labirint i marcheaz punctul de unde ncepem
cutarea ieirilor.
Deplasarea se poate face doar pe vertical i pe orizontal ntre oricare dou celule nvecinate, cu
condiia ca ambele celule s fie spaii libere. Nu este permis s se treac de mai multe ori prin aceeai
celul.
Pentru fiecare cale de ieire gsit, programul trebuie s afieze aceast cale pe ecran, dup care va
atepta apsarea unei taste. Dup apsarea unei taste se va trece la urmtoarea cale de ieire gsit,
.a.m.d. Pentru afiarea unei ci de ieire se va folosi aceeai codificare ca i cea din fiierul de
intrare, cu diferena c se va marca drumul de ieire prin caracterul 'x'.
Spre exemplu un drum de ieire posibil pentru labirintul de mai sus este:
PPPPxPPP
P.xxxPPP
PPx.PPSP
PPxPPxxP
PxxPPxPP
PxPPPxxP
PxxxPPxP
PPPxPPxP
PPPxxxxP
PPPPPPPP
Dac nu exist nici un drum de ieire din labirint, programul va afia mesajul Nu avem nici un drum
de ieire.
Un labirint va avea maxim 24 de linii i 80 de coloane. Valorile pentru N i M nu sunt date explicit,
va trebui s le deducei din structura fiierului de intrare. Se garanteaz c datele de intrare sunt
corecte.
Pentru problema anterioar s se determine i s se afieze pe ecran doar cel mai scurt drum de ieire
din labirint.
4.3 Problema celor 8 regine
Avem o tabl de ah de dimensiune 8x8. S se aeze pe aceast tabl de ah 8 regine astfel nct s
nu existe dou regine care se atac ntre ele.