operatii cu multimi atestat
TRANSCRIPT
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.
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
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
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
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
#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
{
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
{
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
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
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
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
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
{
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
}
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
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
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
{
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
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
}
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
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
cout<<"nr. de elem. al multimii "<<i<<"= ";
cin>>a[i];
}
prod_cartezian(1,n);
}
20
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
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