Sunteți pe pagina 1din 34

+

Algoritmi și tehnici de
programare

Cursul 8
+

◼ Backtracking – căutare completă


+ Backtracking – căutare completă

◼ Metoda Backtracking este o metodă de elaborare a algoritmilor. Ea se aplică


problemelor în care soluţia se poate reprezenta sub forma unui vector,
X=(x1,x2,...xm), care aparţine lui S=S1xS2x...Sm

- S=S1xS2x...Sm se numeşte spaţiul soluţiilor posibile

- Pentru fiecare problemă în parte se dau anumite condiţii între


componentele vectorului soluţie care se numesc condiţii interne

- Soluţiile posibile care verifică condiţiile interne se numesc soluţii


rezultat

- Metoda Backtracking îşi propune să genereze toate soluţiile rezultat


+ Backtracking – căutare completă

◼ In Metoda Backtracking elementele vectorului x primesc pe rând valori în


sensul că lui xk i se atribuie o valoare doar dacă componentele din faţa sa x1,
x2,...xk-1 au primit valori.

◼ Dacă lui xk i s-a atribuit o valoare, nu se trece direct la atribuirea de valori lui
xk+1, ci se verifică daca sunt indeplinite condiţii de continuare, referitoare
la x1, x2,...xk-1 xk.

◼ Dacă condiţiile de continuare au fost satisfăcute, se trece la calculul lui xk+1.


Neîndeplinirea lor exprimă faptul că oricum s-ar alege xk+1,...,xn, nu se va
ajunge la o soluţie rezultat.

◼ Evident, că în cazul neîndeplinirii condiţiilor de continuare va trebui să se facă


o altă alegere pentru xk. Sau dacă Sk a fost epuizat, să se micşoreze k cu o
unitate, încercând să se facă o nouă alegere pentru xk.
+ Backtracking – căutare completă

◼ Metodă lentă, costisitoare, complexitate mare


◼ Verificarea (aproape) tuturor elementelor spațiului soluțiilor

◼ 𝑋 = 𝑥1 , 𝑥2 , … , 𝑥𝑛 , 𝑥𝑖 ∈ 𝑆𝑖 , 𝑖 = 1, 𝑛
◼ 𝜑 𝑥 = 𝑑𝑎
◼ 𝜑𝑘 - condiții de continuare

◼ 𝑋 se construiește element cu element


◼ 𝑥𝑖 primește o valoare din 𝑆𝑖 numai după toate elementele anterioare (𝑥1 ,
𝑥2 , … , 𝑥𝑖−1 )
◼ 𝜑𝑖 𝑥1 , 𝑥2 , … , 𝑥𝑖 = 𝑑𝑎 ⇒ se trece la elementul 𝑖 + 1
◼ altfel se alege altă valoare pentru 𝑥𝑖
◼ valoarea aleasă pentru 𝑥𝑖 e consumată ⇒ 𝐶𝑖 mulțimea valorilor consumate
◼ dacă nu mai sînt valori disponibile se revine la elementul anterior
◼ 𝜑𝑖 𝑥1 , 𝑥2 , … , 𝑥𝑖 = 𝑑𝑎 nu garantează obținerea unei soluții rezultat
+
Backtracking - reprezentare date

◼ Configurație curentă
𝒗𝟏 … 𝒗𝒊−𝟏 𝒙𝒊 … 𝒙𝒏
𝑪𝟏 … 𝑪𝒊−𝟏 𝑪𝒊 ∅ ∅

◼ Configurație inițială
𝒙 … 𝒙𝒏
ቤ 𝟏
∅ ∅ ∅

◼ Configurație soluție
𝒗𝟏 … 𝒗𝒏
𝑪𝟏 … 𝑪𝒏 ฬ

◼ Configurație finală
𝒙𝟏 … 𝒙𝒏
ቤ𝑺 ∅ ∅
𝟏

◼ Rezolvare problemă:
◼ Configurație inițială → configurație soluție →…→configurație soluție → configurație finală
+
Backtracking - operații
◼ Atribuie și avansează
… 𝑣𝑖−1 𝑥𝑖 𝑥𝑖+1 … … 𝑣𝑖−1 𝑣𝑖 𝑥𝑖+1 …
… 𝐶𝑖−1 𝐶𝑖 ∅ … → … 𝐶𝑖−1 𝐶𝑖 ∪ 𝑣𝑖 ∅ …

◼ Încercare eșuată
… 𝑣𝑖−1 𝑥𝑖 𝑥𝑖+1 … … 𝑣𝑖−1 𝑥𝑖 𝑥𝑖+1 …
… 𝐶𝑖−1 𝐶𝑖 ∅ … === … 𝐶𝑖−1 𝐶𝑖 ∪ 𝑣𝑖 ∅ …

◼ Revenire
… 𝑣𝑖−1 𝑥𝑖 𝑥𝑖+1 … … 𝑣𝑖−2 𝑥𝑖−1 𝑥𝑖 …
… 𝐶𝑖−1 𝑆𝑖 ∅ … ← … 𝐶𝑖−2 𝐶𝑖−1 ∅ …

◼ Revenire după construirea unei soluții


… 𝑣𝑛 … 𝑣𝑛−1 𝑥𝑛
… 𝐶𝑛 ฬ ⋘ … 𝐶𝑛−1 𝐶𝑛
+ Backtracking – căutare completă
Forma generală a algoritmului
◼ inițializare 𝑺𝟏 , 𝑺𝟐 , … , 𝑺𝒏
◼ 𝑪𝒌 = ∅, 𝒌 = 𝟏, 𝒏 //construire configurație inițială
◼ k= 𝟏
◼ cît timp 𝐤 > 𝟎 //cît timp configurația nu e finală
◼ Dacă k== 𝒏 + 𝟏 //configurație de tip soluție?
◼ reține soluția 𝑺𝒐𝒍 = 𝒗𝟏 , 𝒗𝟐 , … , 𝒗𝒏
◼ k= 𝒌 − 𝟏 //revenire după construirea unei soluții
◼ altfel
◼ dacă 𝑪𝒌 ≠ 𝑺𝒌 //mai sînt valori neconsumate
◼ alege o valoare 𝒙𝒌 ∈ 𝑺𝒌 − 𝑪𝒌
◼ 𝑪𝒌 = 𝑪𝒌 ∪ 𝒙𝒌
◼ dacă (𝒙𝟏 , 𝒙𝟐 , … , 𝒙𝒌−𝟏 , 𝒙𝒌 ) satisfac condițiile de continuare
◼ 𝒗𝒌 = 𝒙𝒌 //atribuie și avansează
◼ 𝒌=𝒌+𝟏
◼ altfel //revenire
◼ 𝑪𝒌 = ∅
◼ 𝒌=𝒌−𝟏
+ Backtracking – căutare completă
Algoritm modificat
◼ 𝒌=𝟏
◼ 𝒙𝒌 = 𝒂𝒌 − 𝒓𝒌 //primul element minus rația, de multe ori 0=1-1
◼ cît timp 𝒌 > 𝟎
◼ 𝒗𝒃 = 𝟎 //variabila de impas
◼ cît timp 𝒙𝒌 < 𝒔𝒌 și 𝒗𝒃 == 𝟎 //alegerea unei noi valori pentru 𝒙𝒊
◼ 𝒙𝒌 = 𝒙𝒌 + 𝒓𝒌 //următorul termen în progresie
◼ posibil(𝒙𝒌 ,vb) //sînt îndeplinite condițiile de continuare
◼ dacă 𝒗𝒃 == 𝟎 //impas => revenire
◼ 𝒌=𝒌−𝟏
◼ altfel
◼ dacă 𝒌 == 𝒏
◼ dacă condiții_finale(x) //configurație soluție?
◼ reține_soluția 𝑺𝒐𝒍 = 𝒙𝟏 , 𝒙𝟐 , … , 𝒙𝒏
◼ altfel //avans
◼ 𝒌=𝒌+𝟏
◼ 𝒙𝒌 = 𝒂𝒌 − 𝒓𝒌
+ Backtracking – căutare completă
Implementarea recursivă

◼ Metoda este ușor de implementat recursiv

◼ Forma recursivă generală


backtracking(i)
{ dacă (i==n+1)
tipar();
altfel
x[i]=init(i);
cît timp ( următor(i) )
dacă (valid(i) ) backtracking(i+1);
}
+ Exemplu

◼ Presupunem că dorim să ne îmbrăcăm de la un magazin pentru o


festivitate şi dorim să cumpărăm: pantofi, ciorapi, pantaloni, cămaşă
şi cravata astfel încât acestea să se asorteze între ele, să se genereze
toate modalităţile de a ne îmbrăca.

Magazinul are:
5 etaje
◼ La etajul 1 are 10 raioane cu pantofi
◼ La etajul 2 are 10 raioane cu ciorapi
◼ La etajul 3 are 10 raioane cu pantaloni
◼ La etajul 4 are 10 raioane cu cămăși
◼ La etajul 5 are 10 raioane cu cravate
+ Exemplu

◼ Deoarece soluţia are mai multe componente, 5 – câte etaje are magazinul,
putem folosi metoda Backtracking. Pentru rezolvare vom folosi:

◼ k : variabilă întreagă care reprezintă etajul pe care ne găsim


◼ st : vector care are 5 componente întregi, adică exact câte etaje are
magazinul cu proprietatea că st[k]reprezintă numărul raionului de la care s-
a cumpărat pe etajul k. În cazul de faţă st[k] {1,...,10} unde k{1,...,5}
◼ as este o variabilă întreagă care primeşte valoarea 1 dacă pe etajul k mai
sunt raioane nevizitate şi primeşte valoarea 0 dacă pe etajul k nu mai
sunt raioane nevizitate.
◼ ev este o variabilă întreagă care primeşte valoarea 1 dacă ce am gasit la
raionul st[k] convine şi primeşte valoarea 0 dacă ce este la raionul st[k] nu
convine.
+ Exemplu

se pleacă de la primul etaj


din faţa uşii
atâta timp cât încă ne aflăm la un etaj , k
dacă le-am luat pe toate
atunci
se afişează
se coboară la etajul de jos

altfel
ne întrebăm dacă mai sunt raioane pe etajul k
dacă da, atunci
se verifică dacă ne convine ce conţine raionul
dacă am găsit atunci se merge la etajul următor
altfel
se coboară la etajul de jos
+ Exemplu
k=1; (se pleacă de la primul etaj)
st[k]=0; (din faţa uşii)
while (k>0) (atâta timp cât încă ne aflăm la un etaj , k)
{
if(k==6) (dacă le-am luat pe toate atunci)
{
tipar() (se afişează)
k=k-1; (se coboară la etajul de jos)
}
altfel

{
if (st[k]<10) ne întrebăm dacă mai sunt raioane pe etajul k
dacă da, atunci
st[k]=st[k]+1;
if valid() (se verifică dacă ne convine ce conţine raionul care urmează)
k=k+1; (se merge la etajul următor)

else (altfel)
{
st[k]=0;
k=k-1; (se coboară la etajul de jos)
}
}
+
Realizarea funcţiei de initializare

void init()

for (i=0;i<5;i++)

st[i]=0;

}
+
Realizarea funcţiei de validare

int valid() int valid(int k, int st[])


{
{ int ev; int ev=1,i;
for (i = 0; i < k; i++)
ev=1;
if (st[i] == st[k]) ev = 0;
return ev; return ev;
}
}

// nu există condiţii
între componentele
vectorului
+
Realizarea funcţiei de afişare

void tipar(int k)

int i;

for(i=0;i<k;i++)

printf("\nst[%d]=%d\n", i, st[i]);

Afişarea rezultatului
+ Exemplu

Pentru realizarea programului se parcurg următoarele etape:

◼ construirea tipului sir


◼ declararea tuturor variabilelor care apar în cadrul programului
principal
◼ realizarea funcţiei initializare
◼ realizarea funcţiei valid
◼ realizarea funcţiei tipar
◼ programul principal care conţine rutina principală
+
Comentarii
1.
⚫ Testul pentru succesor se modifică
Dacă astfel:
la etajul 1 ar fi fost n[1] raioane if(st[k]<n[k])
la etajul 2 ar fi fost n[2] raioane {
st[k]=st[k]+1;
....... if valid() k=k+1;
}
la etajul k ar fi fost n[k] raioane
+
Comentarii

2. Dacă magazinul are m etaje atunci condiţia „dacă s-au


făcut toate cumpărăturile” sau „s-a ajuns la ultimul etaj” se
scrie astfel:

if(k==m)
+
Comentarii

3. Atunci când nu există condiţii între componentele vectorului


soluţie funcţia valid are forma:

int valid()
{
int ev;
ev=1;
return ev;
}
+
Comentarii

4. Atunci când componentele vectorului soluţie trebuie să fie distincte


trebuie arătat că x[k]x[i] pentru i=1...k-1 se procedează astfel:

- se presupune că x[k] este diferit de toate elementele din faţa sa


- se parcurg indicii 1...k-1 cu i
- dacă x[k] nu este diferit de x[i], atunci presupunerea este falsă

int valid(int k)
{
int i,ev;
ev=1;
for(i=0;i<=k-1;i++)
if(!(st[k]!=st[i]))
ev=0;
st[k] nu este diferit de st[i]
return ev;
}
+ Backtracking

◼ Exemple de probleme care se rezolvă prin metoda


backtracking:

◼ Generarea tuturor permutărilor.


◼ Generarea tuturor aranjamentelor.
◼ Generarea tuturor combinărilor.
◼ Problema colorării hărților.
◼ Problema celor 8 (n) regine.
◼ Plata unei sume (cu/fără bancnotă unitate).
◼ Problema cavalerilor mesei rotunde.
+ Backtracking
Permutările unei mulțimi

◼ O permutare a unei mulţimi cu n elemente este un şir de elemente obţinut


prin schimbarea ordinii elementelor mulţimii date sau chiar mulţimea
însăşi.

◼ Ne gândim la generarea permutărilor atunci când se dă o mulţime cu n


elemente ca date de intrare iar soluţia este sub forma de vector, tot cu n
elemente, ale cărui componente sunt distincte şi aparţin mulţimii date.
◼ Exemplu: Fie A={1,2,3}. Permutările mulţimii A sunt: (1,2,3), (1,3,2),
(2,1,3), (2,3,1), (3,1,2), (3,2,1).

◼ Fie A={a1, a2,…,am} o mulţime cu elemente de tip întreg. Trebuie


determinate elementele mulţimii { y1, y2,…,ym }| ykA, k=1,2,...,m, pentru
yiyj pentru ij}.
◼ Deci, x=( x1, x2,…,xm) unde x{1,...,m}, elementele vectorului x trebuie să
fie distincte.
+ Backtracking
Permutările unei mulțimi
void back(int n, int st[], int *nrsol)
{int k;
init(n, st);
k = 0;
while (k >= 0)
if (k == n)
{(*nrsol)++;
printf("Solutia cu nr: %d \n", *nrsol);
tipar(n, st);
printf("\n");
k--;
}
else
if (st[k]<n)
{st[k] = st[k] + 1;
if (valid(k,st)) k++;
}
else
{st[k] = 0;
k--;
}
}
+ Backtracking
Permutările unei mulțimi
void init(int n, int st[]) void main()
{ {
int i; int st[10], nrsol = 0;
for (i = 0; i<n; i++) int n;
st[i] = 0; printf("n=");
} scanf_s("%d", &n);
back(n,st,&nrsol);
void tipar(int nr, int st[]) printf("\n");
{ printf("nrsol=%d", nrsol);
int i; }
for (i = 0; i<nr; i++)
printf("st[%d]=%d ", i, st[i]);
}
int valid(int k, int st[])
{
int ok=1,i;
for (i = 0; i < k; i++)
if (st[i] == st[k]) ok = 0;
return ok;
}
+ Backtracking
Permutările unei mulțimi
+ Backtracking
Aranjamente

void tipar(int nr, int st[])

for (i = 0; i<nr; i++)

printf("st[%d]=%d ", i, st[i]);

}
+ Backtracking
Aranjamente
void back(int m, int n, int st[], int *nrsol) else
{ int k; if (st[k]<n)
k=0; {
init(n,st); st[k]=st[k]+1;
while (k>=0) if (valid(k,st)) k++;
if (k==m)
}
{
else
(*nrsol)++;
{
printf("Solutia cu nr: %d \n",*nrsol);
st[k]=0;
tipar(m,st);
k--;
printf("\n");
k--; }
} }
+ Backtracking
Combinari

int valid(int k, int st[])


{
int ok = 1,i;
if (k >= 1)
if (st[k] < st[k - 1]) ok = 0;
for (int i = 0; (i < k) && (ok == 1); i++)
if (st[i] == st[k]) ok = 0;
return ok;
}
+ Backtracking
Combinari
void back( int m, int n, int st[], int *nrsol) else
{int k; if (st[k]<n)
k=0; {
init(n,st); st[k]=st[k]+1;
while (k>=0) if (valid(k,st)) k++;
if (k==m) }
{ else
(*nrsol)++; {
printf("Solutia cu nr: %d \n",*nrsol);
st[k]=0;
tipar(m,st);
k--;
printf("\n");
}
k--;
}
}
+ Backtracking - Harta

int valid(int k, int st[], int a[][10])


{
for (int i = 0; i<k; i++)
for (int j = i + 1; j <= k; j++)
if ((st[i] == st[j]) && (a[i][j] == 1))
return 0;
return 1;
}
+ Backtracking
harta
void back(int n, int st[], int a[][10], int c, int *nrsol)
{ int k;
k = 0;
init(n,st);
while (k >= 0)
if (k == n)
{
(*nrsol)++;
printf("Solutia cu nr: %d \n",*nrsol);
tipar(n,st); printf("\n");
k--;
}
else
if (st[k]<c)
{
st[k] = st[k] + 1;
if (valid(k,st,a)) k++;
}
else
{
st[k] = 0;
k--;
}
}
+
Bibliografie

◼ I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C.


Uscatu, Programarea calculatoarelor. Ştiinţa învăţării
unui limbaj de programare, Teorie şi aplicaţii, Ed. ASE,
2003

◼ I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C.


Uscatu, M. Mircea, Programarea calculatoarelor.
Algoritmi în programare, Ed. ASE Bucureşti, 2007

◼ C. Uscatu, M. Popa, L. Bătăgan, C. Silvestru, Programarea


Calculatoarelor. Aplicații, Ed. ASE, 2012

S-ar putea să vă placă și