Sunteți pe pagina 1din 9

Note de laborator Specializare

Algoritmi si structuri de date 2 Info 1

Laborator 1
Descriere: Backtracking

1. Prezentarea metodei

Problemele care se rezolvă prin backtracking nu impun generarea tuturor soluţiilor posibile, ci
doar generarea acelor soluţii care îndeplinesc anumite condiţii, specifice problemei, denumite
condiţii interne. Soluţiile posibile care respectă condiţiile interne sunt denumite soluţii
rezultat.

Unele probleme impun obţinerea soluţiei optime, acea soluţie rezultat care îndeplineşte o
anumită condiţie de optim.

Metoda backtracking se foloseşte la rezolvarea problemelor care îndeplinesc următoarele


condiţii:
• Soluţia poate fi pusă sub forma unui vector X = x1, x2, ... , xn, cu x1 din A1, x2 din A2, x3
din A3, ... , xn din An;
• Mulţimile A1, A2, ..., An sunt mulţimi finite, iar elementele lor se consideră că se află
într-o ordine bine stabilită;
• x1, x2 …, xn pot fi la rândul lor vectori;
• Mulţimile A1, A2 …, An pot coincide;
• Nu există o altă metodă de rezolvare, mai eficientă sau mai rapidă.

Tehnica Backtracking are la bază următorul principiu:


• Se construieşte soluţia, pas cu pas: x1, x2 …, xn
• Dacă se constată că, pentru o valoare aleasă, nu se poate ajunge la soluţie, se renunţă la
acea valoare şi se reia căutarea din punctul în care s-a rămas.
Pentru a evita generarea tuturor soluţiilor posibile, metoda backtracking atribuie pe rând valori
elementelor vectorului X: componenta xk primeşte o valoare numai în cazul în care
componentele x1, x2, . . . , xk-1 au primit deja valori. În acest caz, componentei xk i se atribuie pe
rând acele valori posibile (valori din mulţimea Ak) care îndeplinesc condiţiile de continuare.
Condiţiile de continuare sunt condiţii derivate din condiţiile interne, care stabilesc dacă pentru
o anumită valoare pentru xk, are sau nu sens să continuăm construcţia soluţiei. Se spune că o
valoare a lui xk nu îndeplineşte condiţiile interne dacă oricum s-ar atribui valori componentelor
xk+1, . . . , xn nu se obţine o soluţie rezultat (soluţie care să respecte condiţiile interne).
Revenirea se realizează pe baza observaţiei că, la fiecare apel recursiv, se memorează în stivă
valorile variabilelor locale şi valoarea parametrului. La încheierea unui apel recursiv, se
eliberează zona de memorie alocată pe stivă şi se revine la apelul precedent.
ASD

Algoritm :
• Presupunând generate elementele x1, x2, x3, ... , xk aparţinând mulţimilor A1, A2, A3, ... , Ak se
alege pentru xk+1 (dacă acesta există), primul element disponibil din mulţimea Ak+1. Apar
două posibilităţi:
o Nu a fost găsit un astfel de element, caz în care se reia căutarea considerând
generate elementele x1, x2, x3, ... , xk-1 şi se atribuie lui xk următorul element
disponibil din mulţimea Ak şi algoritmul este reluat.
o A fost găsit xk+1, caz în care se testează dacă acesta îndeplineşte condiţiile de
continuare. Există două posibilităţi:
ƒ Sunt îndeplinite condiţiile, caz în care se testează dacă s-a ajuns la soluţie
şi apar din nou, două posibilităţi:
• S-a ajuns la soluţie, se tipăreşte soluţia şi se reia algoritmul
considerând generate elementele x1, x2, x3, ... , xk (se caută în
continuare, un alt element al mulţimii Ak+1 rămas netestat).
• Nu s-a ajuns la soluţie, caz în care se reia algoritmul, considerând
generate elementele x1, x2, ... , xk+1 şi se caută un prim element xk+2
aparţinând mulţimii Ak+2.
ƒ Nu sunt îndeplinite condiţiile, caz în care se reia algoritmul considerând
generate elementele x1, x2, ... , xk, iar elementul xk+1 se caută printre
elementele mulţimii Ak+1 rămase netestate.

Algoritmul se termină atunci când nu mai există nici un element x1 netestat, aparţinând lui A1.

Algoritmul general backtracking, pentru cazul în care soluţia este reprezentată cu ajutorul unui
vector X, este următorul:

// initializarea primului element din stiva


k=1;
stiva[k]=prima valoare posibila-1;

while (k>0) // cat timp exista elemente in stiva


{
do
{
if (stiva[k] < ultima valoare posibila)
// daca mai exista si alte elemente netestate
{
stiva[k] = element succesor elementului actual;
succesor=true;
}
else succesor=false;
if (succesor)
// daca exista succesor se testeaza validitatea sa
{
valid=true;
if (stiva[k] nu este o valoare corecta) valid=false;
}
}
while ( succesor && !valid );
// ne oprim daca nu mai avem succesor
// sau daca avem succesor si e valid
ASD

if (succesor)
// daca avem succesor ( implicit este si valid )
{
if ( avem solutie ) // daca s-a atins solutia
afisare solutie;
else // daca nu s-a atins solutia se mai urca un
// nivel in stiva
{
// se initializeaza elementul de pe pozitia
// urmatoare in stiva
k++;
stiva[k]=prima valoare posibila-1;
}
}
else k--; // se coboara un nivel in stiva
}

2. Backtracking iterativ vs. Backtracking recursiv

2.1 Iterativ
for(k=1;k<=n;k++) x[k]=gol; initiarizarea vectorului solutie
k=1; pozitionare pe prima componenta
while (k>0) cat timp exista componente de analizat
if (k==n+1) daca x este solutie
{afis(x);
–k;} atunci: afisare solutie si pas stanga
else altfel:
{ if(x[k]<max[k]) daca exista elemente de ıncercat
if(posibil(1+x[k])) daca 1+x[k] este valida
++x[k++]; atunci maresc si fac pas dreapta
else ++x[k]; altfel maresc si raman pe pozitie
else x[k–]=gol; altfel golesc si fac pas dreapta

Vectorul solutie x contine indicii din multimile A1,A2, ...,An. Mai precis, x[k] = i ınseamna ca pe
pozitia k din solutia x se afla ai din Ak.

2.2 Recursiv

Varianta recursiva a algoritmului backtracking poate fi realizata dupa o schema asemanatoare cu


varianta recursiva. Principiul de functionare al functiei f (primul apel este f(1)) corespunzator unui
nivel k este urmatorul:
− ın situatia ın care avem o solutie, o afisam si revenim pe nivelul anterior;
− ın caz contrar, se initializeaza nivelul si se cauta un succesor;
− cand am gasit un succesor, verificam daca este valid. In caz afirmativ, procedura se autoapeleaza
pentru k + 1; ın caz contrar urmand a se continua cautarea succesorului;
− daca nu avem succesor, se trece la nivelul inferior k − 1 prin iesirea din procedura recursiva f.

3. Generarea aranjamentelor

Reprezentarea solutiilor: un vector cu n componente.

Multimile Ak: {1, 2, ...,m}.


ASD

Restrictiile si conditiile de continuare: Daca x = (x1, x2, ..., xn) este o solutie ea trebuie sa respecte
restrictiile: xi ≠ xj pentru oricare i ≠ j. Un vector cu k elemente (x1, x2, ..., xk) poate conduce la o
solutie numai daca satisface conditiile xi ≠ xj pentru oricare i ≠ j.

Conditia de gasire a unei solutii: Orice vector cu n componente care respecta restrictiile este o
solutie. Cand k = n + 1 a fost gasita o solutie.

Prelucrarea solutiilor: Fiecare solutie obtinuta este afisata.

class GenAranjIterativ1{
static int n=2, min=1,max=4, gol=min-1;
static int[] a=new int[n+1];

static void afis(int[] a){


for(int i=1;i<=n;i++)
System.out.print(a[i]);
System.out.println();
}

static boolean gasit(int val, int pozmax){


for(int i=1;i<=pozmax;i++)
if (a[i]==val) return true;
return false;
}

public static void main (String[] args){


int i, k;
for(i=1;i<=n;i++) a[i]=gol;
k=1;

while (k>0)
if (k==n+1) {
afis(a);
--k;
}
else{
if(a[k]<max)
if(!gasit(1+a[k],k-1)) ++a[k++]; // maresc si pas dreapta
else ++a[k]; // maresc si raman pe pozitie
else a[k--]=gol;
}
}
}

class GenAranjIterativ2{

static int n=2, min=1,max=4;


static int gol=min-1;
static int[] a=new int[n+1];

static void afis(){


for(int i=1;i<=n;i++)
System.out.print(a[i]);
System.out.println();
}

static boolean posibil(int k){


for(int i=1;i<k;i++)
if (a[i]==a[k]) return false;
return true;
}
ASD

public static void main (String[] args){


int i, k;
boolean ok;
for(i=1;i<=n;i++) a[i]=gol;
k=1;

while (k>0){
ok=false;

while (a[k] < max) // caut o valoare posibila


{
++a[k];
ok=posibil(k);
if(ok) break;
}
if(!ok) k--;
else if (k == n) afis();
else a[++k]=0;
}
}
}

class GenAranjRecursiv{
static int n=2, m=4, nsol=0;
static int[] a=new int[n+1];

static void afis(){


System.out.print(++nsol+" : ");
for(int i=1;i<=n;i++) System.out.print(a[i]+" ");
System.out.println();
}

static void f(int k){


int i,j;
boolean gasit;
for(i=1; i<=m; i++){

if(k>1) // nu este necesar !


{
gasit=false;
for(j=1;j<=k-1;j++)
if(i==a[j]){
gasit=true;
break; // in for j
}

if(gasit) continue; // in for i


}

a[k]=i;
if(k<n) f(k+1);
else afis();
}
}

public static void main(String[] args){


f(1);
}
}// class
ASD

4. Problema celor n regine

Fiind dată o tablă de şah, de dimensiune n x n, se cer toate soluţiile de aranjare a n regine, astfel
încât să nu se afle două din ele pe aceeaşi linie, coloană sau diagonală, în sensul ca reginele „să
nu se atace” reciproc.

Pentru reprezentarea unei soluţii se poate folosi un vector X, cu n componente (având în vedere
că pe fiecare linie se găseşte o singură regină), care poate fi asimilat unei stive. În general
X(i)=k semnifică faptul că pe linia i regina cu acelaşi număr ocupă poziţia k.

Două regine se găsesc pe aceeaşi diagonală dacă şi numai dacă este îndeplinită condiţia: |X (i)-
X(j)|=|i-j| ( diferenţa, în modul, între linii şi coloane este aceeaşi). Deoarece două regine nu se
pot găsi pe aceeaşi coloană, rezultă că o soluţie este sub formă de permutare. Algoritmul se
încheie atunci când stiva este vidă.

Exemplu: Problema celor 8 regine – Generează soluţiile de aşezare a 8 regine pe tabla de şah
astfel încât acestea „să nu se atace” reciproc:

import java.util.*;

public class Regine{


Vector st;
static int n;
static boolean as,ev;
static int nrSol = 0;

private void initS(){


st.addElement(new Integer(0));
}

private boolean succesor(){


boolean as=false;
Integer el = (Integer)st.elementAt(st.size()-1);

if(el.intValue() < n){


as=true;
el = new Integer(el.intValue()+1);
st.setElementAt(el,st.size()-1);
}
return as;
}
private boolean valid(){
boolean ev=true;
Integer el0 = (Integer)st.elementAt(st.size()-1);

for( int i=0; i < st.size()-1; i++ ){


Integer el = (Integer)st.elementAt(i);

if( (el0.intValue()==el.intValue()) ||
(Math.abs(el0.intValue()-el.intValue()))==(Math.abs(st.size()-1-i)))
{
ev=false;
break;
}
}
return ev;
}
ASD

private boolean solutie(){


return ( st.size()==n );
}

private void tipar(){


System.out.print(++nrSol+":\t");

for( int i=0;i < n;i++ ){


Integer el = (Integer)st.elementAt(i);
System.out.print(( el.intValue()+" "));
}
System.out.println("\n");
}

private void backtracking(){


st = new Vector();
initS();
while( st.size()!=0 ){
do{
as=succesor();
if(as) ev=valid();
}
while((as)&&(!ev));

if(as){
if(solutie()) tipar();
else initS();
}
else st.removeElementAt( st.size()-1 );
}
System.out.println("Sfarsit generare");
}

public static void main(String []args){


n=8;
Regine r= new Regine();
r.backtracking();
}
}

5. Problema labirintului

Se dă un labirint sub forma unei matrici cu m linii şi n coloane. Fiecare element al matricei
reprezintă o cameră. Într-una din camerele labirintului se găseşte un om. Se cere să se afle toate
soluţiile de ieşire din labirint, fără să treacă de două ori prin aceeaşi cameră. Fiecare cameră are
pereţi proprii şi se poate trece dintr-o cameră în alta, numai dacă între cele două camere nu există
perete. Deplasarea prin labirint se poate face doar mergând în sus, în jos, la stânga sau la dreapta,
nu şi în diagonală.

Exemplu: Fie un labirint 4 x 7, codificat printr-o matrice (1 = cale, 0 = zid). Se cere să se


determine toate posibilităţile de ieşire din labirint, pornind dintr-un punct dat (i,j).

public class Labirint {

private int[][] lab = {{0,0,1,1,0,0,0},


{0,0,0,1,0,1,0},
{0,0,0,1,1,1,0},
{0,1,1,1,0,0,0}};
ASD

private int [][] sol;


private int [] vert = {-1,0,1,0};
private int [] oriz = {0,1,0,-1};
private int n = 4,m = 7, nrSol = 0;

public Labirint(int i, int j){


sol = new int[n][m];
sol[i][j] = 1;
traseu(i,j,2);
}

private void traseu(int i, int j, int pas){


int iNou, jNou;

for (int dir = 0; dir<4; dir++){


iNou = i + vert[dir];
jNou = j + oriz[dir];
if (posibil(iNou,jNou)){
sol[iNou][jNou] = pas;
if (iNou == 0 || iNou == n-1 ||
jNou == 0 || jNou == m-1) scrie();
traseu(iNou,jNou,pas+1);
sol[iNou][jNou] = 0;
}
}
}

private void scrie(){


System.out.println("Solutia:"+ ++nrSol);
for (int i = 0; i<n; i++){
for (int j = 0;j<m; j++)
System.out.print(sol[i][j]+"\t");
System.out.println();
}
}

private boolean posibil(int lin, int col){


return (lin>=0 && lin<n &&
col>=0 && col<m &&
lab[lin][col]==1 && sol[lin][col] == 0);
}

public static void main (String []args){


int i=0;
int j=2;
Labirint labirint=new Labirint(i,j);
}
}

6. Probleme

1. Generarea partitiilor unui numar natural


Sa se afiseze toate modurile de descompunere a unui numar natural n ca suma de numere naturale.

2. Generarea combinarilor
ASD

3. Fiind data o harta cu n tari, se cer toate posibilitatile de a colora o harta, utilizand 4 culori
(rosu,galben,verde,albastru) a.i, doua tari vecine sa nu fie colorate la fel. De exemplu, avem
urmatoarea harta cu 7 tari si o solutie posibila de colorare.

0 1
4
2 3
6
5

Harta este reprezentata cu ajutorul unei matrici patratice. Pentru fiecare linie i (reprezentand tara i)
avem j coloane (1 = daca se invecineaza cu tara j, 0 = daca nu se invecineaza cu tara j). Pentru
exemplul de mai sus avem urmatoarea matrice (va fi salvata intr-un fisier harta.in).

0,1,1,0,1,1,0
1,0,0,0,1,1,1
1,1,0,1,1,1,1
0,0,1,0,1,0,1
1,1,1,1,0,0,1
1,1,1,0,0,0,1
0,1,1,1,1,1,0

Rezultatul va fi salvat intr-un fisier harta.out si va fi codificat sub forma unui vector in care fiecare
element i va avea valoarea 1 (rosu), 2 (galben), 3(verde) sau 4 (albastru).

4. Realizati un program care rezolva problema rucsacului, sau problema selectiei optime. Fiind date
n obiecte, fiecare cu greutatea g[i] si valoarea v[i] si un rucsac cu capacitatea totala gt, sa se
determine ce obiecte trebuie selectate pentru a fi luate in rucsac astfel incat greutatea lor totala sa
nu depaseasca gt si valoarea lor sa fie maxima. Datele de intrare vor fi preluate dintr-un fisier
rucsac.in

5. Se dau suma s si n tipuri de monede si bancnote , avand valori de v1,v2,.....,vn € . Se cer toate
modalitatile de plata a sumei s utilizand aceste monede si bancnote.

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