Académique Documents
Professionnel Documents
Culture Documents
Cuprins:
- - Introducere - - ............................................................................................................ 5
- - Prezentare detaliată - -
1
I.6. Generarea aranjamentelor ......................................................................................... 11
- - Prezentarea aplicaţiei - - 46
7 65
9 65
2
66
- - Bibliografie - - ……………………………………………………………………... 75
77
78
-Introducere-
3
Motivul alegerii acestei lucrări este de a înţelege mai bine cum un algoritm matematic
de generare poate fi implementat în cadrul unui limbaj de programare.
Algoritmul propriu-zis este înglobat în cadrul unui program întreg ce este format din
anumite biblioteci pentru diferite tipuri de proceduri sau funcţii prestabilite, constante sau
variabile de date şi numeroase modalităti de prelucrare a datelor de intrare. Acestea pot fi
primite de la tastatura sau direct dintr-un anumit fişier.
4
În subcapitolul următor se prezintă determinarea tuturor modalităţilor de scriere a
unui numar n sub forma :
n= + + ... +
A= ∪ ∪…∪
unde cele k submulţimi , …, (numite clase) sunt nevide şi două câte două disjuncte.
Aplicaţia propriu-zisă este compusă din două programe rulate în Borland C++.
5
I.1. Reprezentarea mulţimilor şi generarea iterativă a
elementelor
(i’) - printr-un vector cu n componente, construit analog cu cel din reprezentarea (i)
cu deosebirea că elementele submulţimii sunt plasate la sfarşitul vectorului.
(ii) - prin vectorul caracteristic al submulţimii, adică acel vector c {0,1 definit
de:
c(i) = (1)
Submulţimile care vor fi generate în această lucrare vor fi de urmatoarele două tipuri:
6
Pentru submulţimile de al doilea tip, dacă folosim pentru ele una dintre reprezentările
(i) sau (i’), vom conveni că ordinea în care apar elementele să fie cea crescătoare; în acest
mod vom evita generarea de mai multe ori a aceleaşi submulţimi.
Toate generările ce vor fi prezentate vor avea un caracter iterativ, fiecare element
fiind dedus din anteriorul său. Algoritmul general de construire a unui nou element apare în
procedura GEN. El foloseşte pe langă un vector V de dimensiune n ( în care vor fi generate
succesiv submulţimile ) şi un indicator de generare IG. La apelarea procedurii, IG are fie
valoarea 0 ( dacă este vorba de prima apelare, deci trebuie făcuta o iniţializare ), fie valoarea
1 ( dacă trebuie construit succesorul vectorului V ). La terminarea fiecărei execuţii a
procedurii, IG are fie valoarea 1 ( dacă a fost generat un nou vector V ), fie valoarea 0 ( dacă
au fost generaţi toţi vectorii V ceruţi de problema concretă ).
array V(n)
IG 1; return
endif
else IG 0
endif
return
end
do
if IG = 0 then exit
endif
repeat
7
Procedura PREL asigură efectuarea unei anumite prelucrări pe baza vectorului V
curent, fără însa a modifica vreun element al sau.
În mulţi dintre algoritmii care vor fi prezentaţi, vectorii vor fi generaţi în ordine
lexicografică crescătoare. Este de remarcat faptul că aceasta corespunde strategiei
backtracking de construcţie a algoritmilor.
Fie date m mulţimi ,...., unde pentru fiecare i {1 , ... , m}, =[ ]={1,
2 , ... , }. Se pune problema generarii tuturor celor elemente ale produsului
cartezian x x ... x .
procedure PRODCART ( V , m , n , IG )
if IG = 0 then for i = 1, m
V(i) 1
repeat
IG 1 ; return
endif
8
for i = n , 1 , -1
else V(i) 1
endif
repeat
IG 0 ; return
end
(n - 1) = ( n - 1) + m=
=
N= + + + + ... +
9
unde 0 pentru orice i , iar 0 dacă N 0.
integer V(n)
10
if IG = 0 then for i = 1, n
V(i) 0
repeat
IG 1 ; return
endif
for i = n, 1, -1
else V(i) 0
endif
repeat
IG 0 ; return
end
11
Este evident că cel mai mic vector mai mare decât V conform ordinei lexicografice
este vectorul următor :
care se obţine din V mărind pe cu o unitate, iar pe fiecare dintre elementele aflate la
dreapta lui se determină din precedentul prin mărirea acestuia cu o unitate.
Dacă nu există un indice i satisfăcând relaţiile (5), înseamnă că v = (1, 2, ..., n) deci au
fost generate toate submulţimile.
integer V(n)
if IG = 0 then for i = 1, n :
V(i) 0
repeat
IG 1; return
endif
for i = n , 1, -1 :
for j = i + 1, N
V(j) V(j 1) +1
repeat
return
endif
repeat
IG 0; return
end
12
Nu vom analiza timpul cerut de acest algoritm.
Se numeşte cod Gray o secvenţă G(n) de vectori distincţi din {0, 1 , începând cu
( 0, ..., 0 ) şi astfel încât doi vectori succesivi diferă pe o singură poziţie. G(n) se reprezintă
sub forma matricii (7) cu linii şi n coloane, unde G(1) este dat de (8).
G(1) (8)
Codul G(n 1) se poate obţine din codul G(n) construind matricea corespunzătoare în
modul ilustrat în (9). După cum se recunoaşte uşor, se respectă condiţia ca două linii să difere
pe o singură poziţie.
Ştiind că prima linie din G(n) este (0, ..., 0) , codul G(n) este bine determinat de un
vector cu componente, unde pentru fiecare i {1, ..., }, este poziţia pe
care diferă liniile i şi i 1 din G(n), cu menţiunea că poziţiile ocupate de biţii 0 şi 1 sunt
numerotate de la dreapta la stânga. Din matricea (9) , interpretată ca relaţie între codurile
G(n) şi G(n ), rezultă că dacă ( , , ..., ), atunci :
13
(10)
Relaţiile (10) permit să deducem uşor prin inducţie după n că vectorul obţinut din
prin inversarea ordinei elementelor sale este tot . Ca urmare, relaţiile (10) se pot scrie
condensat sub forma :
(10’)
G(3) =
,1)
= (1, 2, 1, 3, 1, 2, 1)
3 3
2 2 2 2
1 1 1 1 1 1 1 1
Fig. I.1
14
i
i-1
i-2
Fig I.2
Se observă că acest arbore nu trebuie construit explicit pentru a-l parcurge în inordine.
Mai mult, iniţial putem introduce într-o stivă, în ordine de la bază la vârf, elementele
n, n − 1, ..., 1. La fiecare pas se va scoate un element din stivă ( dacă stiva este vidă, procesul
se încheie ), element care constituie poziţia ce trebuie modificată pentru a obţine următoarea
linie din G(n); dacă acest element este i >1, atunci se vor introduce în stivă pe rînd elementele
i − 1, i − 2, ..., 2, 1, în conformitate cu algoritmul de parcurgere în inordine şi cu figura I.2.
Mai observăm că putem reveni la numerotarea uzuală a poziţiilor ( de la stânga la dreapta ),
deoarece aceasta implică doar inversarea ordinii în care apar elementele liniilor din G(n).
Observaţiile de mai sus duc la procedura GRAY1, în care cei vectori căutaţi se
obţin succesiv în vectorul V.
procedure GRAY1 ( n, V, ς, IG )
integer V(n) ; stack ς
if IG = 0 then ς
for i = n, 1,-1
V(i) 0; i ς
repeat
IG ← 1; return
endif
if ς = then IG ← 0; return
endif
i ς; V(1) 1 V(i)
for j i 1, 1, 1
j ς
repeat
15
return
end
Procedura GRAY1 mai poate fi îmbunătaţită prin eliminarea ultimului ciclu for, ceea
ce va face ca trecerea de la un vector V la următorul să necesite un timp constant. În acest
scop, stiva ς va fi înlocuită cu un vector SUB cu n componente cu următorul conţinut:
- dacă elemetul i se află în stivă, atunci SUB(i) este elementul aflat în stivă imediat sub el
- dacă elementul i nu se află în stiva, atunci SUB(i) = i + 1, corespunzător faptului că în
momentul în care va fi introdus în stivă, sub el se va afla i + 1.
Drept urmare se obţine procedura GRAY2 în care i este elementul din vârful stivei.
repeat
i 1; IG 1; return
endif
if i = n + 1 then IG 0; return
endif
V(i) 1 V(i)
endif
return
end
Conţinutul ultimei instrucţiuni if din procedura GRAY2 poate fi uşor înteles dacă se
urmăreşte figura I.2.
O ultimă metodă foloşeste reprezentarea (i) a submulţimilor, păstrându-se în variabila
k numărul elementelor. Elementele submulţimii vor apărea în ordine crescătoare, adică
16
< < ...< . Iniţial k = 0. La pasul următor vom lua k = 1 şi = 1. În continuare,
vectorii vor fi generaţi conform ordinei lexicografice crescătoare. În consecinţă, trecerea de la
un vector V = ( ) la succesorul său se va face astfel:
- dacă = n, sunt posibile două situaţii : dacă k > 1, atunci succesorul căutat este (
+ 1 ); dacă k = 1, s-a ajuns la V = (n) şi deci au fost generate toate
submulţimile.
Spre deosebire de a doua metodă, submulţimile nu mai sunt generate în ordinea
crescătoare a numărului lor de elemente. Este însă important de observat că trecerea de la un
vector la succesorul său necesită timp constant. Procedura corespunzătoare metodei descrise
este procedura SUBM3.
if IG = 0 then k 0; IG 1; return
endif
endif
if k = 1 then IG 0; return
17
Fie A = [n] = {1,2, ...,n} şi m [n]. Vom prezenta problema generării tuturor celor
submulţimi ale lui A, cu proprietatea că oricare două submulţimi diferă prin natura
elementelor. O astfel de submulţime se numeşte m-combinare a n elemente.
O primă metodă foloseşte pentru submulţimi reprezentarea (i). Dat fiind că valoarea
m este cunoscută, se va folosi un vector V cu m componente, fiecare conţinând un element al
mulţimii A. Deoarece în cadrul submlţimii ordinea elementelor nu prezintă importanţă, se vor
genera ca de obicei toţi vectorii V = ( , ..., ) satisfăcâd relaţiile:
Vectorii vor fi generaţi în ordine lexicografică. Se pleacă de la vectorul (1,2, ..., m).
Fiind dat un vector V = ( , ..., ), vectorul care îi urmează imediat în ordine lexicografică
se determină astfel :
- se trece la vectorul
integer V(m)
if IG = 0 then for i = 1, m
18
V(i) ← 1
repeat
endif
for i = m, 1, 1
for j = i + 1, m
V(j) V(j 1) + 1
repeat
return
endif
repeat
IG 0; return
end
= + + ...+ (13)
Aplicând formula
+ + ...+ = (14)
19
Pentru a determina că suntem in situaţia (11) se fac comparari ale lui , , ...,
deci m i + 1 comparări. Instrucţiunea de atribuire v(i) ← v(i) + 1 împreună cu ciclul for
care îi urmează necesită m i + 1 atribuiri. Rezultă că ori de câte ori ajungem în situaţia (11)
sunt necesare 2( m i + 1) atribuiri + comparări referitoare la elementele vectorului V.
Ţinând cont de faptul că i poate lua oricare valoare 1, 2, ..., m, rezultă că algoritmul
prezentat necesită un număr de operaţii (comparări + atribuiri) referitoare la elementele lui V
egal cu 2N unde :
N = =m + (m 1) + ... + 2 +
= + + ... + + + + + ... +
+ ...........+ + +
Din nou vectorii V vor fi generaţi în ordine lexicografică, deci conform strategiei
generale backtracking. De aceea vom pleca de la vectorul (0, ..., 0, 1, ..., 1) în care primele
n m componente sunt egale cu 0, iar următoarele m elemente sunt egale cu 1. Fie acum V
un vector oarecare pentru care se cere să se determine succesorul. Se observă că este util să
punem în evidenţă ultima secvenţă de elementele egale cu 1 (fig I.3).
Fig I.3
20
B = poziţia ultimului element egal cu 1
α β
V=(....,0,1,1,....,1,0,....,0)
A B
V’ = (. . . . , 1 , 0 , 0 , . . . . , 0 , 1 , . . . . , 1 )
A β α-1 B
Fig I.4
Aceasta arată că V(A) şi V(A +1) primesc respectiv valorile 1 şi 0. În plus, ordinea
ultimelor + 1 elemente este inversată. Pentru ca acestă inversiune să fie realizată mai
rapid, se notează prin = min( 1, ); atunci este suficient ca primele elemente care
urmează lui V(A 1) să primească valoarea 0, iar ultimele elemente ale vectorului V să
primească valoarea 1. În plus, noile valori pentru A şi B sunt respectiv n + 1 + A B şi n.
V = (... 0, 1, 0, ..., 0)
V’ = (... 1, 0, 0, ..., 0)
integer V(n)
21
if IG = 0 then for i = 1, n m
V(i)←0
repeat
for i = n m + 1, n
V(i)←1
repeat
endif
endif
for i = 1,
repeat
while A>0
else A←A 1
endif
repeat
return
endif
22
I.5. Generarea permutărilor
Sunt prezentate mai multe metode de a genera cele n! permutări de grad n. Există o
foarte mare varietate de astfel de metode, problema în sine pretindându-se la rezolvări foarte
ingenioase. Din păcate, indiferent de algoritm, timpul necesar este cel puţin de ordinul O(n!)
ceea ce face ca timpul să crească vertiginos odată cu n şi drept urmare este indicat ca pentru
rezolvarea unei probleme, ce iniţial necesită generarea tuturor permutărilor de grad n, să
căutăm alte soluţii; un exemplu tipic îl constituie calculul unui determinant. Cum metodele
care vor fi prezentate nu au un nume bine precizat în literatura de specialitate, le vom
numerota pur şi simplu.
< (16)
Atunci este evident că trebuie modificat. Cum p’ urmează imediat după p, trebuie
înlocuit cu cel mai mic dintre elemente , ..., cu proprietatea că este mai mare decât ;
fie el . Schimbând pe cu se ajunge la vectorul
( ) (17)
23
integer p(n)
if IG = 0 then for i = 1, n
p(i)←i
repeat
IG←1; repeat
endif
i←n 1
while >
i←i 1
endif
repeat
k←n
while >
k←k 1
repeat
↔ ; m←
for j = 1, m
repeat
return
end
Pentru evaluarea timpului cerut de execuţia acestui algoritm, vom calcula numărul
de interschimbări şi numărul de comparări între elementele vectorului p.
24
Pentru fiecare dintre cele n valori posibile ale lui , se execută interschimbări în
vederea calculul celor (n 1)! permutări ale mulţimii [n] \ { }. În plus, fiecare dintre cele
n -1 treceri ale lui de la o valoare la valoarea imediat următoare se face printr-o
interschimbare ↔ urmată de încă interschimbări,adică interschimbări. Se
obţine astfel următoarea formulă de recurenţă :
(18)
Fie = + (19)
=n +n .
adică
= n( + ) cu = (20)
şi cu condiţia iniţială = 1.
S = n! (21)
Ţinând cont că
=( = 1,543.
rezultă că 1,543 n!
25
Pentru calculul valorilor { } se precedează analog. Pentru fiecare dintre cele n valori
ale lui , se execută comparări pentru calculul celor (n 1)! permutări ale lui [n] \ {
}. La fiecare dintre schimbările lui ,primul ciclu while necesită n comparări,
efectuându-se astfel (n 1 comparări. Când se transformă din i în i + 1, atunci (
) se termină cu secvenţa i + 1, i 1, ..., 1, ceea ce face ca al doilea ciclu while să necesite i
comparări; se efectuează astfel comparări. Se obţine formula de recurenţă
unde în ultima relaţie se ţine seama de faptul că în final, pentru a obţine i = 0, se mai
efectuează n 1 comparări.
Fie = +
(25)
D = k! = k!
= e 1
şi deci n! = 3,077 n!
Metoda descrisă mai sus este cea “naturală”, deşi nu cea mai eficientă.
26
Metoda 2. Metoda de generare a permutărilor se bazează pe reprezentarea
permutărilor prin vectorii de inversiune.
procedure PERINV(i, p, n)
for j = 1, n
VIZ(j)←0
27
repeat
for k = n, 1, 1
j←n
for m = 1,
while VIZ(j) = 1
j←j 1
repeat
j←j 1
repeat
while VIZ(j) ) = 1
j←j 1
repeat
←j; VIZ(j)←1
repeat
return
end
Pe baza celor de mai sus, generarea permutărilor de grad n poate fi făcută astfel : se
generează pe rând vectorii i ... cu = {0, 1, ..., k } şi din care fiecare
vector i se deduce permutarea p cu ajutorul procedurii PERINV. Dezavantajul constă în faptul
că timpul cerut de această procedură este de ordinul O( ).
28
1234 1
1234 1234 2
Fig. I.5
Arătăm prin inducţie că pe nivelul k [n] apar cele k! permutări ale lui n k + 1, ...,
n precedate fiecare de elementele 1, 2, ..., n k.
p p’
k+1
q q
Fig. I.6
29
Se pleacă de la permutarea identică. Să presupunem că am ajuns la o permutare p de
pe ultimul nivel. Pentru a obţine permutarea următoare, facem o permutare circulară a tuturor
celor n elemente. Dacă permutarea p’ obţinută are ≠ 1, atunci a fost obţinută o permutare
nouă. Dacă = 1, atunci s-au terminat de parcurs descendenţii unui vârf de pe nivelul n 1.
„Urcăm” în arbore şi facem o permutare p’’. Dacă ≠ 2 atunci p’’ este permutarea
căutată. Dacă = 2 , atunci s-au terminat de parcurs descendenţii unui vârf de pe nivelul n
2 ; “urcăm” pe nivelul superior în arbore etc. În figura I.5 este desenată punctat o astfel de
trecere de la o permutare de pe ultimul nivel la alta. Dacă s-a efectuat şi o permutare a
ultimelor 2 elemente şi s-a ajuns la o permutare q cu = n 1, atunci ne oprim.
integer p(n)
if IG = 0 then for i = 1, n
p(i)← i
repeat
IG← 1; return
endif
for k = n, 2, −1
call CIRCU(p, n, k)
if ≠ n – k + 1 then return
endif
repeat
IG← 0; return
end
30
- pe nivelul 1 apare drept rădăcină permutarea identică
1234 1
Fig. I.7
σ σ’ k+1
p p i+1
Fig. I.8
31
Pentru parcurgerea vârfurilor terminale, se pleacă de la permutarea identică. Fie p o
permutare oarecare şi E vectorul asociat descris mai sus. Este evident că pentru a determina
permutarea p’ care urmează lui p se poate proceda astfel : se permută pe rând ultimele 2, 3, ...
componentele ( actualizând corespunzător vectorul E ) până când în urma permutării
ultimelor i componente şi a actualizării lui E(i), valoarea acestuia este cel mult n + 1 – i.
Dacă nu există un astfel de i, generarea s-a încheiat.
Procedura asociată metodei descrise este procedura PERM4 în care procedura CIRCU
permută circular ultimele k elemente ale lui p.
if IG = 0 then for i = 1, n:
p(i)← i; E(i)← 1
repeat
IG ← 1; return
endif
for k = 2, n:
call CIRCU(p, n, k)
m← n + 1 – k ; E(m)← E(m) + 1
else E(m)← 1
endif
repeat
IG ← 0; return
end
32
Ca urmare, permutările din sunt vârfurile terminale ale unui arbore construit
astfel :
- rădăcina este etichetată cu 1
- descendenţii unei permutări p = ( , ..., ) situate pe nivelul k sunt cele k + 1
permutări (26) unde k = 1, 2, ..., n – 1.
Arborelui îi putem ataşa un arbore în care toate vârfurile sunt permutări de
grad n astfel : pentru fiecare nivel k, permutările de pe acest nivel sunt completate cu k +
1, ..., n.
Este evident că pe fiecare nivel k din apar toate cele k! permutări de grad k.
Exemplu. Pentru n = 3, arborii şi apar în figurile I.9 a şi I.9 b.
1 123
12 21 123 213
123 132 312 213 231 321 123 132 312 213 231 321
a b
Fig. I.9
integer p(n)
if IG = 0 then for i = 1, n
p(i) ← i
repeat
IG ← 1; return
endif
for i = n, 2, −1
33
m←1
while p(m) ≠ i
m←m+1
repeat
endif
repeat
IG ← 0 ; return
end
Metoda 6. Această metodă este înrudită cu cea precedentă şi diferă doar prin aceea că
cele k +1 permutări din obţinute dintr-o aceeaşi permutare p a lui sunt următoarele :
12 21 123 213
123 132 231 213 312 321 123 132 231 213 312 321
a b
Fig. I.10
Observaţie. Trecerea de la o valoare i la următoarea în (27) presupune doar
micşorarea ultimei componente cu o unitate şi marirea cu 1 a acelei componente care era
egală cu i − 1.
Se pleacă de la o permutare identică. Dacă = 1, se trece la tatăl permutării curente
micşorând cu o unitate primele n − 1 componente şi punând ← n ; apoi se reia
raţionamentul înlocuind pe n cu n − 1.
34
procedure PERM6(p, n, IG)
integer p(n)
if IG = 0 then for i = 1, n
p(i) ← 1
repeat
IG ← 1; return
endif
for i = n, 2, −1
j←j+1
repeat
endif
for j = 1, i − 1
p(i ) ← p(i ) − 1
repeat
p(i ) ← i
repeat
IG ← 0; return
end
Metoda 7. Metoda, mai eficientă, nu este de fapt o metodă nouă. Ea urmează aceeaşi
idee ca metoda 5, diferenţa constând în faptul că în arborele ataşat se modifică ordinea în care
apar descendenţii anumitor vârfuri, ceea ce permite eliminarea permutărilor circulare,
înlocuindu-le prin deplasări ale unei anumite componente , adică prin efectuarea unei
interschimbări ↔ ( dacă se deplasează spre dreapta ) sau ↔ ( dacă
se deplasează spre stânga ). În acest scop se introduce un vector d cu n componente în care
35
are valoarea − 1 sau valoarea 1 după cum urmează să se deplaseze spre stânga sau
dreapta.
Observaţie. Ideile prezentate mai sus pot fi aplicate şi celorlalte metode descrise
anterior.
Pentru a descrie arborele ataşat metodei, pe fiecare nivel k vom numerota
permutările de pe acest nivel în A−preordine cu 1, 2, ..., k !. Arborele se construieşte astfel :
- rădăcina este permutarea identică;
- dacă ( , ..., ) este permutarea cu numărul de ordine j de pe nivelul k , atunci
descendenţii săi sunt în ordine:
- - ( , ..., , k + 1), ( , ..., k + 1, ), ..., (k + 1, , ..., )
Pentru cazul particular n = 5, arborele ataşat metodei este prezentat în figura IV.11 dar
înlocuindu-se sensul stânga→dreapta cu sensul sus→jos, adică s-a considerat simetricul sau
faţă de diagonala stânga sus→dreapta jos a hârtiei. În dreptul permutărilor de pe fiecare nivel
este notată şi modalitatea în care se determină succesorul lor. De asemenea sunt puse în
evidenţă schimbările sensurilor de parcurgere a elementelor.
1234 ← 4
1243 ← 4
123 1423 ← 4
4123 ← 3 ( 4 îşi schimbă sensul )
12 4132 4→
132 1432 4→
1342 4→
1324 ← 3 ( 4 îşi schimbă sensul )
36
3124 ← 4
312 3142 ← 4
1 3412 ← 4
4312 ← 2 ( 4, apoi 3 îşi schimbă sensul )
4321 4 →
3421 4 →
321 3241 4 →
3214 3 → ( 4 îşi schimbă sensul )
21 2314 ← 4
231 2341 ← 4
2431 ← 4
4231 3 → ( 4 îşi schimbă sensul )
4213 ← 4
213 2413 ← 4
2143 ← 4
2134
Fig. I.11
if IG = 0 then for j = 1, n :
←j , ←j ; ← −1
37
repeat
IG ← 1; return
endif
for j = n, 2, −1
m← +
←j; ←m
return
endif
endif
←−
repeat
IG ← 0
return
end
Este evident că atribuirile care modifică lui p şi i se efectuează pentru fiecare vârf
terminal al arborelui cu excepţia celui iniţial; deci sunt necesare 4(n ! − 1) astfel de atribuiri.
De asemenea se arată uşor prin inducţie că numărul comparărilor < 1 este egal cu numărul
vârfurilor arborelui, adică . Obţinem succesiv :
= n ! + (n − 1)! ≤ n ! +
(n − 1)! = n ! ≈ n !
(27)
38
Corectitudinea metodei rezultă din corectitudinea metodei 5, deoarece în ambele
metode pe fiecare nivel în arborii ataşaţi apar aceleaşi permutări, dar în alta ordine.
if IG = 0 then for i = 1, m
← i; ← i; ←i
repeat
IG ← 1; IG1 ← 1; return
endif
repeat
39
if IG = 1 then for i = 1, m
←i; ←
repeat
endif
endif
return
end
if IG = 0 then for i = 1, m
←i; ←1
repeat
40
for i = m + 1, n
←0
repeat
IG ← 1; return
endif
for i = m, 1, −1
←0
for j = + 1, n
←j; ← 1; k ← 0
for l = i + 1, m
do
k←k+1
until =0
←k; ←1
repeat
return
endif
repeat
repeat
IG ← 0 ; return
end
41
Se numeşte m-combinare cu repetiţie sau m-selecţie a n elemente un vector a ∈ [n
satisfăcând relaţiile:
≤ ≤ ... ≤
(28)
În cazul particular când toate inegalităţile din relaţia (28) sunt stricte (ceea ce
implică m ≤ n ) se obţine definiţia m-combinărilor. Sunt prezentate trei metode de
generare a m-combinărilor cu repetiţie.
integer a(m)
if IG = 0 then for i = 1, m :
←1
repeat
IG ← 1 ; return
endif
for i = m, 1, −1
if ≠ n then x ← +1
for j = i, mi
←x
repeat
42
return
endif
repeat
IG ← 0 ; return
end
unde ≠ 0, ≠ 0 sunt ultimele elemente nenule ale vectorului ; indicele i poate lua şi
valoarea 0 şi anume atunci când = m. Atunci în succesorul r’ al lui r componentele care îşi
modifică valoarea sunt următoarele :
43
Metoda 3. Această metodă utilizează pentru memorarea mulţimii { , ..., }
reprezentarea (i) (subcap 1), adică un vector a cu m componente care în primele sale k
componente conţine elementele , ..., satisfăcând (29). Valorile , ..., sunt memorate
în ordine într-un vector r.
if IG = 0 then IG ← 1; k ← 1; ← 1; ← m; return
endif
k ← k + 1; ←1
else = +1
endif
return
endif
if > 1 then ← − 1; ← + 1; ← +1
else k ← k − 1; ← + 1; ← +1
endif
return
end
44
Problema generală ce va fi tratată este cea a determinării tuturor modalităţilor de
scriere a unui număr n sub forma
n= + + ... + (31)
unde numerele naturale nenule , ..., se numesc părţi ale lui n. Din această problemă
generală se desprind mai mult probleme ce se deosebesc între ele prin restricţii asupra
părţilor.
I. Problema compunerii. În cadrul acestei probleme se cere > 0, ∀i; în plus ordinea
elementelor în scrierea părţilor lui n prezintă importanţă.
= ; = − ; ... ; = − ; =n−
Compunerile lui n din exact k părţi sunt bine determinate de alegerea a k − 1 valori
distincte din mulţimea {1, … , n − 1}. Drept urmare există astfel de compuneri care se
deduc la fel ca mai sus din cele (k − 1)-combinări de n − 1 elemente.
a’ = ( , ... , , + 1, ... , + 1, )
45
cu =n− . Dacă nu există vreun I cu proprietatea menţionată, rezultă că suntem
în situaţia a = (1, 1, ... , 1), deci nu mai există o nouă descompunere. Algoritmul
corespunzător metodei descrise apare în procedura DESCOMP1.
integer a(n)
if IG = 0 then for i = 1, n − 1
←0
repeat
← n; IG ← 1; return
endif
s←
for i = n − 1, 1, − 1:
if +2≤ then m ← +1
for j = i, n − 1
←m
repeat
← s − (n − i − 1)m − 1; return
else s ← s +
46
endif
repeat
IG ← 0; return
end
Vom considera mai întâi reprezentarea unei selecţii de sumă n ca fiind un vector r cu
n componente aparţinând mulţimii {0, 1, …, n} vector care are proprietatea:
=n (32)
Primul şi ultimul vector generat vor fi deci vectorii (n, 0, ..., 0) respectiv (0, ..., 0, 1).
Fie r un vector satisfăcând (32), vector pentru care se caută succesorul său r’ în ordine
lexicografică inversă. Fie i cel mai mic indice cu >0. Conform condiţiei (32), niciuna dintre
primele i componente nu poate fi mărită cu o unitate, deci trebuie mărită o componentă de pe
celelalte poziţii. Deosebim următoarele situaţii:
( , 0, …, 0, , + 1, , …, )
- dacă = 1, se caută cel mai mic indice j > i cu > 0 (se observă că un astfel de
indice j nu există doar dacă i = n , deci dacă s-a încheiat generarea). Atunci vectorul
r’ are forma următoare:
( , 0, …, 0, = 0, 0, …, 0, = 0, + 1, , …, )
cu = i + *j − (j + 1).
47
Metoda descrisă mai sus se poate adopta uşor situaţiei în care pentru reprezentarea
unei selecţii se utilizează un vector p care în primele sale k poziţii conţine părţile distincte ale
descompunerii precum şi un vector r care în primele sale k poziţii conţine factorii de repetiţie
pentru părţile , …, . Se observă că este avantajoasă memorarea părţilor , …, în
ordine descrescătoare, deoarece modificările se efectuează asupra părţilor cele mai mici. Se
obţine procedura DESCOMP2.
if IG = 0 then k ← 1; ← 1; ←n
IG ← 1; return
endif
if IG = 0 then IG ← 0; return
endif
s← *
if = 1 then k ← k − 1; s ← s + *
endif
← +1
else + 1; ←1
endif
endif
return
end
48
Fie p(n, k) numărul descompunerilor lui n în k părţi. Propoziţia care urmează permite
determinarea recursivă a acestor numere.
Relaţiile (33) sunt evidente. Pentru a demontra relaţia (34), notăm prin �(n, k)
mulţimea descompunerilor lui n în k părţi, iar prin �(n, k) mulţimea descompunerilor lui n în
cel mult k părţi. Atunci sunt îndeplinite relaţiile:
unde secvenţa finală de valori egale cu 1 are lungimea k − i. Aplicaţia φ este evident
injectivă. Ea este şi surjectivă. Într-adevăr fie n + k = + + ... + o descompunere a lui
n + k în k părţi unde ≥ ≥ ... ≥ ≥ 1. Fie i cel mai mare indice cu > 1. Evident
i≤k. Atunci descompunerea n = ( − 1) + ... + ( − 1) este din �(n, k), iar imaginea sa
prin φ este tocmai descompunerea (36). În conseciinţă φ este bijectivă. Rezultă | �(n, k) | =
=| ) | ceea ce ,ţinând cont de (35), conduce la relaţia (34).
49
Fie stabilite valori pentru , ..., . Există mai multe modalităţide a alege condiţiile
de continuare care să elimine unele alegeri ale lui . Cele mai naturale par următoarele:
s= ,r= (39)
if IG = 0 then s ← 0; r = ; k ← 1; ←−1, IG ← 1
endif
while k > 0
v←−1
while <1
→ + 1; call POSS(A, x, n, k, r, s, M, v)
if v ≠ 0 then exit
endif
50
repeat
case v = − 1 : s ← s − ; k ← k − 1; r ← r +
v = 0 : k ← k − 1; r ← r +
v=1:r←r− ; k ← k + 1; ←−1
v = 2 : write , ..., ; k ← k − 1; r ← r +
return
endcase
repeat
IG ← 0; return
end
procedure POSS(A, x, n, k, r, s, M, v)
if = 0 then if s + r − ≥ M then v ← 1
else v ← 0
endif
s+ = M: v ← 2
s+ > M: v ← 0
endcase
endif
51
return
end
IV. Problema plaţii unei sume în bancnote de valoare dată. Fie S ∈ ℕ reprezentând
o sumă ce trebuie plătită. Fie , ..., valorile bancnotelor existente; se presupune că există
suficiente bancnote din fiecare valoare. Se cer modalităţi în care suma S poate fi plătită în
bancnotele existente, adică vectorii x ∈ satisfăcând relaţia:
S=
> >…>
if IG = 0 then for i = 2, n:
←0
repeat
IG ← 1; k ← 1; ← + 1;
SC ← S −
endif
SC ← SC + ;k←n−1
while k > 0
52
if > 0 then ← − 1; SC ← SC +
for j = k + 1, n:
← ; SC ← SC −
if SC = 0 then for m = j + 1, n
←0
repeat
return
endif
repeat
SC ← SC + ;k←n−1
else k ← k − 1
endif
repeat
IG ← 0; return
end
Fie A o mulţime finită. Vom presupune că A = {1, 2, …, n}. Fie R o relaţie binară pe
A, deci R ⊂ A × A.
53
reprezentarea grafurilor prin matricea adiacenţă induce reprezentarea relaţiilor prin matricea
adiacenţă M de ordin n în care
= (40)
procedure TRANZ(M, n)
integer M(n,n)
for j = 1, n:
for i = 1, n
if = 1 then for k = 1, n
if = 1 then ←1
endif
repeat
endif
repeat
repeat
return
54
end
raţionament se obţine că = = 1.
55
A= ∪ ∪…∪ (41)
unde cele k submulţimi , …, (numite clase) sunt nevide şi două câte două disjuncte.
V = (− 1, 6, − 2, 3, − 4,5)
c) fiecare componentă V a vectorului V desemnează numărul clasei din care face parte
elementul a, clasele fiind ordonate în modul descris la reprezentarea a). Pentru
exemplul considerat V = (1, 2, 2, 3, 3, 1).
d) fiecare componentă V a vectorului conţine un reprezentant al clasei din care face parte
a (de obicei cel mai mic element al clasei).
(42)
56
Aceasta sugerează metoda de generare a partiţiilor pe care o descriem în continuare.
Se pleacă de la partiţia . Apoi elementul n trece pe rând în clasele
precedente până ajunge în prima clasă. După aceea el va forma singur o clasă (revenindu-se
deci la o partiţie precedentă) şi se încearcă deplasarea lui n − 1 spre stânga, adică obţinerea
unei noi partiţii a lui [n − 1]; dacă aceasta reuşeşte, se reia procedeul (încercând deci să-l
deplasăm pe n la stânga), în caz contrar încercăndu-se deplasarea lui n − 2 spre stânga etc.
Procesul de generare se opreşte atunci când ajungem în situaţia de a-l deplasa pe 1 spre
stânga, ceea ce nu este posibil deoarece 1 apare totdeauna în prima clasă.
Din faptul că orice partiţie a lui [n] se obţine dintr-o partţie a lui [n − 1] în unul dintre
modurile care apar în (42), rezultă că metoda descrisă generează intr-adevăr toate partiţiile lui
[n].
Dacă se foloseşte pentru reprezentarea partiţiilor modalitatea d), metoda descrisă mai
sus este realizată de procedura PART.
integer V(n)
if IG = 0 then for i = 1, n
V(i) ← i
return
IG ← 1; return
endif
k←n
do
for i = 2, k − 1
endif
57
repeat
return
else V(k) ← k; k ← k − 1
endif
until k = 1
IG ← 0
return
end
În algoritmul PART, la căutarea clasei precedente celei în care apare k, s-a ţinut cont
de faptul că reprezentantul ei este mai mic decât k, precum şi faptul că V(1) = 1.
Observaţie. Dacă se notează prin S(n, k) numărul partiţiilor lui [n] în k clase, atunci
din (42) se deduce formula de recurenţă.
(44)
permite determinarea valorii S(n, k) pentru orice pereche de numere naturale (n, k).
PREZENTAREA APLICAŢIEI
1. Reprezentarea mulţimilor şi generarea iterativă a elementelor
58
1.1 Noţiuni Teoretice
(ii) - prin vectorul caracteristic al submulţimii, adică acel vector c {0,1 definit
de:
c(i) = (1)
Submulţimile care vor fi generate în această lucrare vor fi de urmatoarele două tipuri:
Pentru submulţimile de al doilea tip, dacă folosim pentru ele una dintre reprezentările
(i) sau (i’), vom conveni că ordinea în care apar elementele să fie cea crescătoare; în acest
mod vom evita generarea de mai multe ori a aceleaşi submulţimi.
Toate generările ce vor fi prezentate vor avea un caracter iterativ, fiecare element
fiind dedus din anteriorul său. Algoritmul general de construire a unui nou element apare în
procedura GEN. El foloseşte pe langă un vector V de dimensiune n ( în care vor fi generate
succesiv submulţimile ) şi un indicator de generare IG. La apelarea procedurii, IG are fie
59
valoarea 0 ( dacă este vorba de prima apelare, deci trebuie făcuta o iniţializare ), fie valoarea
1 ( dacă trebuie construit succesorul vectorului V ). La terminarea fiecărei execuţii a
procedurii , IG are fie valoarea 1 ( dacă a fost generat un nou vector V ), fie valoarea 0 ( dacă
au fost generaţi toţi vectorii V ceruţi de problema concretă ).
array V(n)
IG 1; return
endif
else IG 0
endif
return
end
IG 0
do
if IG = 0 then exit
endif
repeat
60
Procedura PREL asigură efectuarea unei anumite prelucrări pe baza vectorului V
curent, fără însa a modifica vreun element al sau.
În mulţi dintre algoritmii care vor fi prezentaţi, vectorii vor fi generaţi în ordine
lexicografică crescătoare. Este de remarcat faptul că aceasta corespunde strategiei
backtracking de contrucţie a algoritmilor.
Fie date m mulţimi ,...., unde pentru fiecare i {1 , ... , m}, =[ ]={1,
2 , ... , }. Se pune problema generarii tuturor celor elemente ale produsului
cartezian x x ... x .
Pentru a şti dacă vectorul este iniţializat sau nu, se va folosi o variabilă numită
iniţializat a cărei valoare este 0 dacă vectorul nu este iniţializat şi 1 în caz contrar. Aceasta ar
putea fi declarată ca variabilă globala, dar , cum ea nu prezintă interes decăt în cadrul acestei
proceduri, ar fi preferabil să fie declarată ca variabilă locală. Pentru că valoarea ei nu trebuie
să se piardă între 2 apeluri ale procedurii, variabilă iniţializat va fi declarată de tip static. Se
va conveni ca procedura să întoarcă valoarea 1 dacă s-a reuşit generarea unui element şi
valoarea 0 în cazul în care s-a încheiat.
61
În programul de mai jos procedura este apelată repetat până la generarea tuturor
elementelor produsului cartezian, care vor fi afişate la consolă.
procedure PRODCART ( V , m , n , IG )
if IG = 0 then for i = 1, m
V(i) 1
repeat
IG 1 ; return
endif
for i = n , 1 , -1
else V(i) 1
endif
repeat
IG 0 ; return
end
62
Pentru a obţine numărul de calcule efectuat de algoritmul PRODCART , observăm că
pentru , ... , fixate, componenta îşi schimbă valoarea (mărindu-se cu o unitate sau
devenind 1) de ori, deci în total îşi schimbă valoarea de * * ... * ori. Drept
urmare tinănd cont şi de iniţializare, numărul instrucţiunilor de atribuire referitoare la
elementele vectorului V este m + + + ... + *...* . În cazul particular = = ...
= = n numărul acestor operaţii de atribuire este m + n + + ... + =m+n =
= ( ).
(n - 1) = ( n - 1) + m=
=
N= + + + + ... +
63
(N) = ( + 1, ... , + 1) (4)
integer V(n)
if IG = 0 then for i = 1, n
V(i) 0
repeat
IG 1 ; return
endif
for i = n, 1, -1
else V(i) 0
64
endif
repeat
IG 0 ; return
end
Este evident că cel mai mic vector mai mare decât V conform ordinei lexicografice
este vectorul următor :
care se obţine din V mărind pe cu o unitate, iar pe fiecare dintre elementele aflate la
dreapta lui se determină din precedentul prin mărirea acestuia cu o unitate.
65
3.2.2 Algoritm de generare
integer V(n)
if IG = 0 then for i = 1, n :
V(i) 0
repeat
IG 1; return
endif
for i = n , 1, -1 :
for j = i + 1, N
V(j) V(j 1) +1
repeat
return
endif
repeat
IG 0; return
66
end
Se numeşte cod Gray o secvenţă G(n) de vectori distincţi din {0, 1 , începând cu
( 0, ..., 0 ) şi astfel încât doi vectori succesivi diferă pe o singură poziţie. G(n) se reprezintă
sub forma matricii (7) cu linii şi n coloane, unde G(1) este dat de (8).
G(1) (8)
Codul G(n 1) se poate obţine din codul G(n) construind matricea corespunzătoare
în modul ilustrat în (9). După cum se recunoaşte uşor, se respectă condiţia ca două linii să
difere pe o singură poziţie.
Ştiind că prima linie din G(n) este (0, ..., 0) , codul G(n) este bine determinat de un
vector cu componente, unde pentru fiecare i {1, ..., }, este poziţia pe
67
care diferă liniile i şi i 1 din G(n), cu menţiunea că poziţiile ocupate de biţii 0 şi 1 sunt
numerotate de la dreapta la stânga. Din matricea (9) , interpretată ca relaţie între codurile
G(n) şi G(n ), rezultă că dacă ( , , ..., ), atunci :
(10)
Relaţiile (10) permit să deducem uşor prin inducţie după n că vectorul obţinut din
prin inversarea ordinei elementelor sale este tot . Ca urmare, relaţiile (10) se pot scrie
condensat sub forma :
(10’)
G(3) =
,1)
= (1, 2, 1, 3, 1, 2, 1)
3 3
2 2 2 2
68
1 1 1 1 1 1 1 1
Fig. I.1
i-1
i-2
Fig I.2
Se observă că acest arbore nu trebuie construit explicit pentru a-l parcurge în inordine.
Mai mult, iniţial putem introduce într-o stivă, în ordine de la bază la vârf, elementele
n, n − 1, ..., 1. La fiecare pas se va scoate un element din stivă ( dacă stiva este vidă, procesul
se încheie ), element care constituie poziţia ce trebuie modificată pentru a obţine următoarea
linie din G(n); dacă acest element este i > 1, atunci se vor introduce în stivă pe rînd
elementele i − 1, i − 2, ..., 2, 1, în conformitate cu algoritmul de parcurgere în inordine şi
cu figura IV.2. Mai observăm că putem reveni la numerotarea uzuală a poziţiilor ( de la
stânga la dreapta ), deoarece aceasta implică doar inversarea ordinii în care apar elementele
liniilor din G(n).
Observaţiile de mai sus duc la procedura GRAY1, în care cei vectori căutaţi se
obţin succesiv în vectorul V.
procedure GRAY1 ( n, V, ς, IG )
integer V(n) ; stack ς
if IG = 0 then ς
for i = n, 1,-1
V(i) 0; i ς
repeat
IG ← 1; return
endif
69
if ς = then IG ← 0; return
endif
i ς; V(1) 1 V(i)
for j i 1, 1, 1
j ς
repeat
return
end
Procedura GRAY1 mai poate fi îmbunătaţită prin eliminarea ultimului ciclu for, ceea
ce va face ca trecerea de la un vector V la următorul să necesite un timp constant. În acest
scop, stiva ς va fi înlocuită cu un vector SUB cu n componente cu următorul conţinut:
- dacă elemetul i se află în stivă, atunci SUB(i) este elementul aflat în stivă imediat sub el
- dacă elemntul i nu se află în stiva, atunci SUB(i) = i + 1, corespunzător faptului că în
momentul în care va fi introdus în stivă, sub el se va afla i + 1.
Drept urmare se obţine procedura GRAY2 în care i este elementul din vârful stivei.
repeat
i 1; IG 1; return
endif
if i = n + 1 then IG 0; return
70
endif
V(i) 1 V(i)
endif
return
end
Conţinutul ultimei instrucţiuni if din procedura GRAY2 poate fi uşor înteles dacă se
urmăreşte figura I.2.
- dacă = n, sunt posibile două situaţii : dacă k > 1, atunci succesorul căutat este (
+ 1 ); dacă k = 1, s-a ajuns la V = (n) şi deci au fost generate toate
submulţimile.
Spre deosebire de a doua metodă, submulţimile nu mai sunt generate în ordinea
crescătoare a numărului lor de elemente. Este însă important de observat că trecerea de la un
vector la succesorul său necesită timp constant. Procedura corespunzătoare metodei descrise
este procedura SUBM3.
if IG = 0 then k 0; IG 1; return
71
endif
endif
if k = 1 then IG 0; return
4. Generarea aranjamentelor
procedure ARANJ1 ( k, n, m )
integer j, k
if k = -1 then afişează
else for i = 0, n
72
indici[k] ← j ;
call ARANJ1(k − 1)
repeat
endif
return
end
73
procedure ARANJ2(a, n, m, IG, DISP)
if IG = 0 then for i = 1, m
←i; ←1
repeat
for i = m + 1, n
←0
repeat
IG ← 1; return
endif
for i = m, 1, −1
←0
for j = + 1, n
←j; ← 1; k ← 0
for l = i + 1, m
do
k←k+1
until =0
←k; ←1
repeat
return
endif
repeat
repeat
74
IG ← 0 ; return
end
Concluzii Finale
În urma rulării primului program s-au obţinut elementele unui produs cartezian.
Elementele produsului cartezian au fost generate succesiv într-un vector V cu m componente.
75
În total au fost expuse zece mari modalităti de efectuare a diferite operaţii asupra
elementelor unor mulţimi. La fiecare generare au fost analizate atât algoritmul matematic, cât
şi cel din mediul de programare.
Bibliografie
- Alexandru Ionică, Michal Tuska, Elena Giorgiana Ionică, Cristian Alexandru Ionică,
“Recursivitate si sortare”, Vol I, Editura IVAN KRASKO, Nadlac-Arad-România, 2005
- http://www.einformatica.ro/
- http://www.infobytedb.ro/
76
77