31
Noțiuni introductive În mod neriguros, o mulțime este o colecţie bine definită de obiecte, considerată ca un întreg. Obiectele dintr-o mulţime sunt numite elemente. Elementele unei mulţimi pot fi de orice natură: numere, persoane, litere ale alfabetului, alte mulţimi, etc. Prin convenţie mulţimile sunt notate cu majuscule cursive: A, B, C etc. Mulţimile nu se pot defini, dar pot fi descrise. Se descrie noţiunea de mulţime ca fiind datș de mai multe elemente de același tip. În cadrul unei mulţimi un element apare o singură dată . De exemplu, {3, 5, 3, 2} nu este mulţime pentru că elementul 3 apare de două ori. Două mulţimi A și B se numesc egale, și aceasta se notează A = B, dacă deţin (sunt formate din) aceleaşi elemente. A citi o mulţime, înseamnă a introduce elementele care o alcătuiesc. Acestea sunt memorate într-o variabilă de tip vector. Presupunem că elementele introduse sunt distincte.

Operatii Cu Multimi Atestat

Embed Size (px)

Citation preview

Page 1: Operatii Cu Multimi Atestat

Noțiuni introductive

În mod neriguros, o mulţime este o colecţie bine definită de obiecte, considerată ca un

întreg. Obiectele dintr-o mulţime sunt numite elemente. Elementele unei mulţimi pot fi de

orice natură: numere, persoane, litere ale alfabetului, alte mulţimi, etc. Prin convenţie

mulţimile sunt notate cu majuscule cursive: A, B, C etc.

Mulțimile nu se pot defini, dar pot fi descrise. Se descrie noțiunea de mulțime ca fiind

datș de mai multe elemente de același tip. În cadrul unei mulțimi un element apare o singură

dată . De exemplu, {3, 5, 3, 2} nu este mulțime pentru că elementul 3 apare de două ori.

Două mulţimi A şi B se numesc egale, şi aceasta se notează A = B, dacă deţin (sunt

formate din) aceleaşi elemente.

A citi o mulțime, înseamnă a introduce elementele care o alcătuiesc. Acestea sunt

memorate într-o variabilă de tip vector. Presupunem că elementele introduse sunt distincte.

Page 2: Operatii Cu Multimi Atestat

Generarea unei mulțimi

Se consideră mulțimea A, constituită numai din elemente obținute după

următoarele reguli:

1.1ϵA.

2.Dacă xϵA, atunci 2*x+1ϵA.

3.Dacă xϵA, atunci 3*x+1ϵA.

Să se scrie un program care să genereze cele mai mici n elemente ale

mulțimii A.

Se generează în ordine crescătoare elementele mulțimii într-un vector A și se

inițializează primul element al vectorului A cu 1. Plecănd de la 1 se generează elementele

2*1+1=3 si 3*1+1=4.

Deci, după primul pas, elementele vectorului A sunt {1,3,4}.

Aplicând regulile 2 și 3 pentru ultimele elemente generate, obținem elementele 2*3+1,

3*3+1, 2*4+1 si 3*4+1. Prin urmare, componentul vectorului A la pasul 2 devine

{1,3,4,7,10,9,13}.

La pasul următor se aplică regulile 2 și 3 ultimelor elemente generate și se obțin

elementele 2*7+1, 3*7+1, 2*10+1, 3*10+1, 2*9+1, 3*9+1, 2*13+1, 3*13+1.Vectorul A

conține {1,3,4,7,10,9,13,15,22,21,31,19,28,27,40}.

La pasul x se aplică regulile 2 și 3 ultimelor elemente generate la pasul x-1.Prin

urmare, la pasul x se obțin 2x elemente noi. Procedeul se repetă până când în vector obținem n

elemente.

Generând elementele în acest mod, este posibil să obținem elemente care se repetă,

prin urmare, ar trebui ca la fiecare element nou generat să fie verificat dacă nu cumva există

deja în vector elementele generate nu sunt în ordine crescătoare, deci la sfârșit trebuie ca

vectorul să fie sortat și apoi să fie afișat.

Algoritmul prezentat mai sus este relativ dificil de implementat și, în plus, este și

1

Page 3: Operatii Cu Multimi Atestat

neeficient ( execută aproximativ n2operații).

Generarea de la început a elementelor vectorului în ordine crescătoare este o idee mai bună.

Pentru aceasta, la pasul x nu se plasează în vector 2x elemente, ci se va plasa doar un singur

element(cel mai mic dintre cele care urmează să fie generate). Pentru a determina cel mai mic

element dintre cele care urmează a fi generate nu este necesar să fie determinate toate, apoi se

calculează minimul. Este suficient să se rețină permanent două elemente: următorul element

generat pe baza regulii 2 și următorul element generat pe baza regulii 3. Minimul va fi selectat

dintre aceste două elemente.Pentru aceasta vor și necesari doi indici (poz2 și poz3) cu

semnificația poz2=poziția următorul element pentru care se aplică regula 2, iar poz3=poziția

următorului element pentru care se aplică regula 3.

#include<iostream.h>

void main()

{

int n, poz2, poz3, A[1000], i, min;

cout<<"n=";cin>>n;

A[0]=1;

poz2=poz3=0;

for(i=1;i<n;i++)

{

min=2*A[poz2]+1;

if(min>3*A[poz3]+1) min=3*A[poz3]+1;

A[i]=min;

if(min==2*A[poz2]+1) poz2++;

if(min==3*A[poz3]+1) poz3++;

}

for(i=0;i<n;i++) cout<<A[i]<<" ";

cout<<endl;

}

Principalele operații cu mulțimi2

Page 4: Operatii Cu Multimi Atestat

A.Testul de apartenentă

Se citește o mulțime A de numere întregi. Se citește un număr întreg a. Să se

decidă dacă aϵA.

Se va proceda într-un mod clasic. O variabilă găsit va reține, inițial, valoarea 0. Apoi

se testează fiecare element al mulțimii A dacă este sau nu egal cu numărul reținut de e. În caz

de egalitate variabila găsit va reține 1. La sfârșit, se va da răspunsul în funcție de conținutul

variabilei găsit.

#include<iostream.h>

void main()

{

int a[100],n,i,e,gasit;

cout<<"numarul de elemente ale multimii ";cin>>n;

for(i=1;i<=n;i++)

{ cout<<"a["<<i<<"]=";cin>>a[i];}

cout<<"elementul considerat ";cin>>e;

gasit=0;

for(i=1;i<=n;i++)

if(a[i]==e) gasit=1;

if(gasit) cout<<"elementul apartine multimii"<<endl;

else cout<<"elementul nu apartine multimii"<<endl;

}

B. Diferența a două mulțimi

Se citesc două mulțimi A și B. Se cere să se afișeze mulțimea C=A-B.

Se cunoaște modul în care se definește diferența a două mulțimi:

C=A-B={x∈ A∨x∉ B} ,

adică elementele care aparțin mulțimii A și nu mulțimii B.

De aici rezultă algoritmul: pentru fiecare element al mulțimii A, se face testul dacă

aparține sau nu mulțimii B. În caz de neapartenența, acesta este adăugat unei mulțimi C,

3

Page 5: Operatii Cu Multimi Atestat

inițial vide. O variabilă k (cu valoare inițială 0) reține indicele componentei din c care va

memora următorul element ce se adaugă mulțimii diferența. În final, se tipărește c.

#include<iostream.h>

void main()

{

int a[100],b[100],c[100],n,m,i,j,k,gasit;

cout<<"numarul de elemente al multimii A ";cin>>n;

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

{cout<<"a["<<i+1<<"]=";cin>>a[i];}

cout<<"numarul de elemente al multimii B ";cin>>m;

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

{cout<<"b["<<i+1<<"]=";cin>>b[i];}

k=0;

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

{

gasit=0;

for(j=0;j<m && !gasit;j++)

if(a[i]==b[j]) gasit=1;

if(!gasit) c[k++]=a[i];

}

if(k!=0) {cout<<"A-B"<<endl;

for(i=0;i<k;i++) cout<<c[i]<<" "<<endl;}

else cout<<"A-B este vida"<<endl;

}

C. Reuniunea a două mulțimi

Se citesc două mulțimi de numere întregi A și B. Se cere să se afișeze mulțimea C=

A∪B.

Pentru rezolvare, se rezervă trei variabile de tip tablou cu component care rețin numere întregi

(A, B ,C). După citirea celor două mulțimi A și B, se listează mulțimea B, apoi A-B: de fapt,

aplicăm formula: C=A∪B=B∪ ( A−B ) .

4

Page 6: Operatii Cu Multimi Atestat

#include<iostream.h>

void main()

{

int a[100],b[100],c[200],n,m,i,j,k,gasit;

cout<<"numarul de elemente al multimii A ";cin>>n;

for(i=1;i<=n;i++)

{ cout<<"a["<<i<<"]=";cin>>a[i];}

cout<<"numarul de elemente al multimii B ";cin>>m;

for(i=1;i<=m;i++)

{ cout<<"b["<<i<<"]=";cin>>b[i];}

k=0;

for(i=1;i<=n;i++)

{

gasit=0;

for(j=1;j<=m && !gasit;j++)

if(a[i]==b[j]) gasit=1;

if(!gasit) c[k++]=a[i];

}

cout<<"A reunit cu B "<<endl;

for(i=1;i<=m;i++)cout<<b[i]<<" "<<endl;

for(i=1;i<=k-1;i++) cout<<c[i]<<" "<<endl;

}

D.Intersecția a două mulțimi

Se citesc două mulțimi de numere întregi A și B. Se cere să se afișeze mulțimea

C=A ∩ B .

Definiția intersecției a două mulțimi:

C=A ∩ B= {xϵA|xϵB }

Pornind de la definiție, se deduce imediat algoritmul: pentru fiecare element al

mulțimii A se face testul de apartenentă la mulțimea B, iar în caz afirmativ, este adăugat la o

mulțime C, inițial vidă.

#include<iostream.h>

void main()

5

Page 7: Operatii Cu Multimi Atestat

{

int a[100],b[100],c[100],n,m,i,j,k,gasit;

cout<<"numarul de elemente al multimii A ";cin>>n;

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

{ cout<<"a["<<i+1<<"]=";cin>>a[i];}

cout<<"numarul de elemente al multimii B ";cin>>m;

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

{ cout<<"b["<<i+1<<"]=";cin>>b[i];}

k=0;

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

{

gasit=0;

for(j=0;i<m && !gasit;j++)

if(a[i]==b[i]) gasit=1;

if(gasit) c[k++]=a[i];

}

if(k!=0) {cout<<"A intersectat cu B"<<endl;

for(i=0;i<k;i++) cout<<c[i]<<" "<<endl;}

else cout<<"intersectia multimilor este vida"<<endl;

}

E.Produsul cartezian dintre două mulțimi

1.Fie mulțimile A={1,2,3,4,…n} și B={1,2,3…m} (m și n se citesc). Se cere să se

afișeze mulțimea C=A× B.

Se cunoaște relația: C=A× B={( x , y )|xϵA , yϵB } .Exemplu: A={1,2}, B={1,2,3}. C= A × B={

(1,1), (1,2), (1,3), (2,1), (2,2), (2,3)}. Dacă se urmărește cu atenție, se observă că trebuie

afișate toate perechile de numere (x,y) cu x={1,2…n} și y={1,2…m}. Aceasta se realizează

foarte ușor cu două cicluri for implicate.

#include<iostream.h>

void main()

6

Page 8: Operatii Cu Multimi Atestat

{

int m,n,i,j;

cout<<"numarul de elemente al multimii A ";cin>>n;

cout<<"numarul de elemente al multimii B ";cin>>m;

for(i=1;i<=n;i++)

for(j=1;j<=m;j++)

cout<<i<<" "<<j<<endl;

}

Problema este rezolvată pentru un caz particular (mulțimea A este formată din

numerele naturale cuprinse între 1 și n, iar mulțimea B este formată din numerele naturale

între 1 și m).

Pentru cazul general, pentru cazul în care A este o mulțime formată din n caractere,

iar B este o mulțime formată din m caractere. Se citesc elementele celor două mulțimi.

Exemplu A={a,b} și B={c,d,e}. Mulțimea A are două elemente, iar mulțimea B are 3.

Algoritmul este același ca și în cazul anterior, pentru mulțimile {1,2} și {1,2,3}. Diferența

este că, la afișare, în loc să afișăm perechea (i,j), afișăm perechea (A[i],B[j]). Astfel, în loc să

afișăm (1,1), afișăm(A[1],B[1]) adică (a,c) ș.a.m.d.

#include<iostream.h>

void main()

{

char multa[9],multb[9];

int n,m,i,j;

cout<<"numarul de elemente al multimii A ";cin>>n;

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

{ cout<<"mult["<<i+1<<"]=";cin>>multa[i];}

cout<<"numarul de elemente al multimii B ";cin>>m;

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

{ cout<<"mult["<<i+1<<"]=";cin>>multb[i];}

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

for(j=0;j<m;j++)

cout<<multa[i]<<" "<<multb[j]<<endl;

}

7

Page 9: Operatii Cu Multimi Atestat

2. Fie n un număr natural (n>1) si L=(l1 ,l2 , …,ln ) o mulțime de n numere naturale

nenule. Să se determine în ordine lexicografică toate elementele produsului cartezian

{1,2,…l1}×{1,2 ,…l2 }×…×{1,2,3…ln}.

Fie x=(x1 , x2 ,… xn ¿si y=( y1 , y2 , …. ym¿. Spunem că x precedă pe y din

punct de vedere lexicografic dacă există k astfel încât x i= y i, pentru orice i<k și xk< yk sau

k>n. De exemplu, pentru n=3 și L=(2,3,2), elementele produsului cartezian sunt: (1,1,1),

(1,1,2), (1,2,1), (1,2,2), (1,3,1), (1,3,2), (2,1,1), (2,1,2),(2,2,1), (2,2,2), (2,3,1), (2,3,2). Se

observă că produsul cartezian are l1∗l2∗….∗lnelemente. Se

reprezintă un element al produsului cartezian ca un vector E cu n elemente, unde E[i]ϵ {1,2,…

li}.

Pentru a genera toate elementele produsului în ordine lexicografică, se aplică un

algoritm de tip succesor:

Pas 1. Inițializam vectorul E cu 1 (cel mai mic element al produsului cartezian, din

punct de vedere lexicografic).

Pas 2. Cât timp este posibil ( mai există succesor),

-se afișează elementul curent;

-se generează elementul următor;în acest scop, se caută prima componentă

(începând din dreapta către stânga) care poate fi mărită (adică E[i]<L[i]); dacă se găsește o

astfel de componentă,se va mări și se repune pe 1 toate componentele următoare; dacă nu

se gasește o astfel de componentă, se deduce că generarea s-a încheiat, acesta a fost cel mai

mare element din punct de vedere lexicografic.

#include<fstream.h>

#define NMax 100

void main()

{

int n, L[NMax], E[NMax], i, gata=0;

ofstream fout("pc.out");

cout<<"n=";cin>>n;

for(i=0;i<n;i++) {cout<<"L["<<i+1<<"]=";cin>>L[i]; E[i]=1;}

while(!gata)

{

for(i=0;i<n;i++) fout<<E[i]<<" "; fout<<endl;

8

Page 10: Operatii Cu Multimi Atestat

for(i=n-1;i>=0 && E[i]==L[i];i--) E[i]=1;

if(i<0) gata=1;

else E[i]++;

}

fout.close();

return 0;

}

F.Submulțimi

Se citește n, număr natural. Se cere să se afișeze toate submulțimile mulțimii {1,2,3…

n}.

Exemplu n=3. Mulțimea {1,2,3} are următoarele submulțimi: {1,2,3}, {1,2}, {1,3},

{2,3}, {1}, {2}, {3} și ∅ (mulțimea vidă).

O submulțime se poate memora sub forma unei variabile de tip tablou cu n component, unde

fiecare component reține 0 sau 1. Componenta I ia valoarea 1 dacă elementul i aparține

submulțimii și 0 în caz contrar o astfel de reprezentare se numește reprezentare prin vector

caracteristic). Exemplu: Fie submulțimea {1,2} a mulțimii {1,2,3}.

1 1 0

A[1] A[2] A[3]

Avem A[1]=1, pentru că elementul 1 aparține submulțimii considerate, A[2]=1, pentru

că elementul 2 aparține submulțimii și A[3]=0, pentru că elementul 3 nu aparține submulțimii.

Generarea tuturor submulțimilor înseamnă generarea tuturor combinațiilor de 0 și 1 care pot

fi reținute de vectorul A. Astfel de combinație poate fi interpretată ca un numar natural scris

binar și pentru aceasta trebuie să generam toate numerele (în binar) care se pot reprezenta

utilizând n cifre. Pornind de la 00…000, se adună la fiecare pas 1, simulând adunarea în binar.

Astfel, obținem o nouă combinație de 0 și 1 care reprezintă numărul din pasul precedent la

care s-a adunat o unitate.

Algoritmul se oprește când a fost scrisă ultima combinație 111…11, care corespunde

celui mai mare număr care poate fi reținut în cele n componente. La fiecare pas se calculeaza

9

Page 11: Operatii Cu Multimi Atestat

suma numerelor reținute de cele n componente, iar în momentul în care aceasta este n,

înseamna că au fost generate toate numerele. Exemplu: se citește n=3 și se dorește afișarea

tuturor submulțimilor mulțimii {1,2,3}. Se pornește de la configurația:

A

Se adună 1 și se obține:

A

A[1] A[2] A[3]

Aceasta configurație reprezintă numărul 1 și reprezintă submulțimea {3}.

Se adună 1:

A

A[1] A[2] A[3]

Configurația reprezintă numărul 2 și reprezintă submulțimea{2}.

Adunăm 1 și obținem:

A

A[1] A[2] A[3]

Procedeul continuă până se obține configurația:

A

A[1] A[2] A[3]

Aceasta reprezintă submulțimea {1,2,3} ( una între submulțimile unei mulțimi este chiar

mulțimea propriu-zisă). Dacă ne gândim la modul în care am generat submulțimile unei

mulțimi cu n elemente, ajungem la concluzia că există 2n submulțimi ale ei (inclusiv multimea

vidă).

10

0 0 0

0 0 1

0 1 0

0 1 1

1 1 1

Page 12: Operatii Cu Multimi Atestat

Generarea submulțimilor se poate scrie în două moduri în c++.

1.#include<iostream.h>

void main()

{

int multa[9],n,i,s;

cout<<"numarul de elemente al multimii A "; cin>>n;

for(i=0;i<=n;) multa[i++]=0;

do

{

multa[n-1]++;

for(i=n-1;i>=1;i--)

if(multa[i]>1)

{

multa[i]-=2;

multa[i-1]+=1;

}

s=0;

for(i=0;i<n;i++) s+=multa[i];

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

if(multa[i]) cout<<i+1<<' ';

cout<<endl;

} while(s<n);

cout<<"multimea vida "<<endl;

}

2.#include<fstream.h>#define NMax 100

void main()

{

int n, S[NMax], i, gata=0;

ofstream fout("subm.out");

cout<<"n=";cin>>n;

for(i=0;i<n;i++) S[i]=0;

while(!gata)

11

Page 13: Operatii Cu Multimi Atestat

{

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

if(S[i]) fout<<i<<" "; fout<<endl;

for(i=0;i<n && S[i];i++) S[i]=0;

if(i==n) gata=1;

else S[i]=1;

}

fout.close();

}

G.Incluziunea a două mulțimi

Se citesc două mulțimi de numere întregi A și B. Se cere să se afișeze dacă între aceste

două mulțimi există o relație de incluziune.

#include<iostream.h>

void main()

{

int a[100],b[100],i,j,m,n,ok,x=0;

cout<<"n=";cin>>n;

cout<<"m=";cin>>m;

for(i=1;i<=n;i++)

{

cout<<"a["<<i<<"]=";

cin>>a[i];

}

for(i=1;i<=m;i++)

{

cout<<"b["<<i<<"]=";

cin>>b[i];

}

for(i=1;i<=n;i++)

{

ok=0;

for(j=1;j<=m;j++)

if(a[i]==b[j]) ok=1;

if(ok==1) x++;

12

Page 14: Operatii Cu Multimi Atestat

}

if(x==n) cout<<"Multimea A este inclusa in multimea B"<<endl;

else cout<<"Multimea A NU este inclusa in multimea B"<<endl;

}

Metoda backtracking

Metoda Backtracking se aplică problemelor în care soluţia poate fi reprezentată sub

forma unui vector – x = (x1 , x2 ,…. xk …xn) ϵS, unde S este mulţimea soluţiilor problemei şi

S =S1 × S2 ×…. × Sn , şi Si sunt mulţimi finite având s elemente și x i ∈ R si , (¥ )i = 1 , n.

Pentru fiecare problemă se dau relaţii între componentele vectorului x, care sunt

numite condiţii interne; soluţiile posibile care satisfac condiţiile interne se numesc soluţii

rezultat. Metoda de generare a tuturor soluţiilor posibile și apoi de determinare a soluţiilor

rezultat prin verificarea îndeplinirii condiţiilor interne necesită foarte mult timp.

Metoda backtracking evită această generare şi este mai eficientă. Elementele

vectorului x, primesc pe rând valori în ordinea crescătoare a indicilor, x[k] va primi o valoare

numai dacă au fost atribuite valori elementelor x1.. x[k-1]. La atribuirea valorii lui x[k] se

verifică îndeplinirea unor condiţii de continuare referitoare la x1…x[k-1]. Dacă aceste

condiţii nu sunt îndeplinite, la pasul k, acest lucru înseamnă că orice valori i-am atribui lui

x[k+1], x[k+1], .. x[n] nu se va ajunge la o soluţie rezultat.

Metoda backtracking construieşte un vector soluţie în mod progresiv începând cu

prima componentă a vectorului şi mergând spre ultima cu eventuale reveniri asupra

atribuirilor anterioare.

Metoda se aplică astfel :

1) se alege prima valoare S1 și I se atribuie lui x1 ;

2) se presupun generate elementele x1…x[k-1], cu valori din S1..S[k-1]; pentru

generarea lui x[k] se alege primul element din S[k] disponibil și pentru valoarea aleasă se

testează îndeplinirea condiţiilor de continuare.

Pot apărea următoarele situaţii :

a) x[k] îndeplineşte condiţiile de continuare. Dacă s-a ajuns la soluţia finală (k =

n) atunci se afişează soluţia obţinută. Dacă nu s-a ajuns la soluţia finală se trece la generarea

elementului următor – x [k-1];

13

Page 15: Operatii Cu Multimi Atestat

b) x[k] nu îndeplineşte condiţiile de continuare. Se încearcă următoarea valoare

disponibilă din S[k]. Dacă nu se găseşte nicio valoare în S[k] care să îndeplinească condiţiile

de continuare, se revine la elementul x[k-1] şi se reia algoritmul pentru o nouă valoare a

acestuia. Algoritmul se încheie când au fost luate în considerare toate elementele lui S1.

A. Backtrackingul nerecursiv este o tehnică de programare aplicabilă algoritmilor

care oferă mai multe soluţii şi are ca rezultat obţinerea tuturor soluţiilor problemei. Fiecare

soluţie se memorează într-o structura de date de tip stivă implementată cu ajutorul unui

vector. Deci fiecare soluţie poate fi pusă sub forma unui vector.

Într-un algoritm backtracking ne interesează toate soluţiile posibile. Pentru a obţine

fiecare soluţie finală se completează stiva nivel cu nivel trecând astfel prin nişte soluţii

parţiale. Astfel soluţiile finale cât şi cele parţiale pentru a fi luate în considerare trebuie să

îndeplinească anumite condiţii numite condiţii de validare. O soluţie care îndeplineşte o astfel

de condiţie se numeşte soluţie validă.

Toate configuraţiile stivei ce reprezintă soluţii finale sunt alcătuite din elementele

aceleiaşi mulţimi bine definite pe care o numim mulţimea soluţiilor. Fiecare nouă soluţie

parţială se obţine prin completarea soluţiei parţiale precedente cu încă o nivel pe stivă. La

fiecare nivel se pun valori din mulţimea soluţiilor care nu au fost încercate până când se

obţine o soluţie validă. În acest moment se trece la nivelul următor în stivă pentru a completa

mai departe soluţia reluând încercările pe noul nivel.

La un moment dat pe un anumit nivel nu mai există nici o valoare neîncercată din

mulţimea valorilor problemei. În acest caz se face un pas înapoi în stivă la nivelul anterior şi

se reia căutarea cu valorile rămase neîncercate pe acest nivel anterior.

Respectivul nivel a mai fost vizitat dar l-am abandonat după ce am pus o valoare care

a generat o soluţie validă. Deci este posibil să fi rămas aici valori neîncercate. Dacă nici pe

acest nivel nu mai avem valori neîncercate mai facem un pas înapoi în stivă. Mecanismul

revenirilor a determinat denumirea de metoda backtracking.

Plecând de la nivelul 1 şi repetând algoritmul până când pe toate nivelele au fost

încercate toate valorile din mulţimea valorilor se obţin soluţii finale care se tipăresc.

Vom implementa metoda backtracking iterativ folosind o rutină unică aplicabilă

oricărei probleme. Rutina va apela proceduri şi funcţii care au întotdeauna acelaşi nume şi

14

Page 16: Operatii Cu Multimi Atestat

parametri şi care din punct de vedere al metodei realizează acelaşi lucru.

Sarcina rezolvatorului este să scrie explicit - pentru fiecare problemă - procedurile şi

funcţiile aplicate pe rutină. Astfel găsirea următorului element netestat de pe un nivel k al

stivei St se face cu procedura succesor (as,St,k)

Odată ales un element testarea condiţiilor de validare se face cu procedura valid

(ev,St,k).Testul dacă s-a ajuns sau nu la o soluţie finală se face cu funcţia soluţie (k) Soluţia se

tipăreşte cu procedura tipar.

De asemenea fiecare nivel al stivei trebuie iniţializat cu o valoare aflată înaintea

tuturor valorilor posibile din mulţimea soluţiilor. Această afişare se face cu procedura init

(k,St).

Un exemplu de problema care se rezolva cu ajutorul acestei metode este generarea

partitiilor unei multimi cu n elemente date.

Definiţie : Fiind dată o mulţime A , submulţimile A1 , A2 ,…, Ak constituie o partiţie a

acesteia dacă sunt îndeplinite simultan condiţiile:

1. mulţimile sunt disjuncte între ele ( Ai∩A j=φ , i≠ j );

2.reuniunea tuturor acestor mulţimi este mulţimea A ( ¿

i=1

kAi=A

).

Exemplu:

n=3 {1,2,3 } ;

{1,2 } ; {3 } ;

{1,3 } ; {2 } ;

{1 } ; {2,3 };

{1 } ; {2 } ; {3 } .

#include<iostream.h>

int st[10],n,k;

void init()

{

st[k]=0;

}

int am_succesor()

15

Page 17: Operatii Cu Multimi Atestat

{

int i, max;

if(k==1) max=1;

else {

max=st[1];

for(i=2; i<=k-1; i++)

if(max<st[i]) max=st[i];

}

if(st[k]<max+1 && st[k]<k)

{

st[k]++;

return 1;

}

else return 0;

}

int e_valid()

{

return 1;

}

void afisare()

{

int i,j,max;

max=st[1];

for(i=2; i<=n; i++)

if(max<st[i]) max=st[i];

for(i=1; i<=max; i++)

{

for(j=1; j<=n; j++)

if(st[j]==i) cout<<j;

cout<<" ";

}

cout<<endl;

}

void back()

{

int as;

k=1; init();

16

Page 18: Operatii Cu Multimi Atestat

while(k>0)

{

do{}while((as=am_succesor()) && !e_valid());

if(as)

if(k==n) afisare();

else { k++; init(); }

else k--;

}

}

void main()

{

cout<<"n= "; cin>>n;

back();

}

Un alt exemplu de problemă care se rezolvă folosind backtrackingul nerecursiv este generarea mulțimii produsului cartezian.

#include<iostream.h>

#include<conio.h>

int st[10],n,k,card[10];

void init()

{

st[k]=0;

}

int am_succesor()

{

if(st[k]<card[k])

{

st[k]++;

return 1;

}

else return 0;

}

int e_valid()

{

return 1;

17

Page 19: Operatii Cu Multimi Atestat

}

void afisare()

{

for(int i=1; i<=k; i++)

cout<<st[i];

cout<<endl;

}

void back()

{

int as;

k=1; init();

while(k>0)

{

do{}while((as=am_succesor()) && !e_valid());

if(as)

if(k==n) afisare();

else { k++; init(); }

else k--;

}

}

void main()

{

int i;

cout<<"n= "; cin>>n;

for(i=1; i<=n; i++)

{

cout<<"card["<<i<<"]= ";

cin>>card[i];

}

back();

getch();

}

Backtrackingul recursiv este o tehnică de programare care găseşte o soluţie,

membră a produsului cartezian a n mulţimi (dacă punem un X pe o stivă de h elemente,

18

Page 20: Operatii Cu Multimi Atestat

evident n mulţimi corespunde la o stivă de h elemente, unde n=h). Dacă mulţimea X are m

elemente corespunde la o lăţime l a stivei unde h=m. I de pe stivă se plimbă de la 1 la m, sau

de la 1 la l. Pentru a parcurge stiva de la 1 la h se apelează recursiv funcţia back_r, o funcţie

recursivă. Pentru a parcurge în lăţime mulţimea de pe nivelul k al stivei, avem câte un ciclu

for pentru fiecare apelare a funcţiei recursive back_r. Aceste cicluri trebuie parcurse

independent, de aceea contorul (în sursele noastre i) trebuie declarat local în funcţia back_r, şi

se alocă pe stivă. Dacă i este declarat global, buclele for nu vor funcţiona independent.

Cel mai simplu exemplu este produsul cartezian:Se consideră n mulţimi M 1 , M 2 ,…, M n .

Mulţimile au un număr finit de elemente: c1=card (M 1 ) , c2=card ( M 2) , … ,

cn=card ( M n) . Să se genereze mulţimile produsului cartezian M 1×M2×…×M n .

#include<iostream.h>

int a[10],p[10],n,i;

void afiseaza()

{

for(int i=1; i<=n; i++)

cout<<p[i];

cout<<endl;

}

void prod_cartezian(int pas, int n)

{

int i;

if(pas==n+1) afiseaza();

else

for(i=1; i<=a[pas]; i++)

{

p[pas]=i;

prod_cartezian(pas+1,n);

}

}

void main()

{

cout<<"numarul de multimi= "; cin>>n;

for(i=1; i<=n; i++)

{

19

Page 21: Operatii Cu Multimi Atestat

cout<<"nr. de elem. al multimii "<<i<<"= ";

cin>>a[i];

}

prod_cartezian(1,n);

}

20

Page 22: Operatii Cu Multimi Atestat

Bibliografie

Emanula Cerchez, Marinel Şerban – Programarea în limbajul C++ pentru liceu (partea I), Editura Polirom, 2005

George Daniel Mateescu, Pavel Florin Moraru -Informatica pentru liceu i bac .Editura Donaris.2005

Tudor Sorin – Manual pentru clasa a X-a.Editura L&S INFORMAT.

Doina Logofatu - Algoritmi fundamentali in C++. Editura Polirom

21

Page 23: Operatii Cu Multimi Atestat

Cuprins

1.Noțiuni introductive……………………………12.Generarea unei mulțimi………………………..23.Principalele operații cu mulțimi……………….6 A.Testul de apartenență ………………………………….4 B.Diferența a două mulțimi………………………………4 C.Reuniunea a două mulțimi…………………………….5 D.Intersecția a două mulțimi…………………………….6 E.Produsul cartezian dintre două mulțimi…………….7

F.Submulțimi………………………………………………10 G.Incluziunea a două mulțimi…………………………..13

4.Metoda backtracking…………………………..14 A.Backtrackingul nerecursiv……………………………15 B.Backtrackingul recursiv………………………………..21

5.Bibliografie……………………………………...22

22