60
CAPITOLUL 5 METODE DE SORTARE ŞI CĂUTARE 5.1 SORTARE 5.1.1 Generalităţi despre sortare În general se defineşte sortarea ca un proces de separare şi aranjare a lucrărilor după clase şi tip. În programare acest termen este utilizat într-un sens mult mai special: de sortare a lucrărilor într-o ordine ascendentă sau descendentă. Vom utiliza termenul de sortare în sensul sortării într-o anumită ordine. Cele mai importante aplicaţii ale sortării sunt: a) soluţionarea problemei legate de punerea împreună a tuturor articolelor care au aceeaşi identitate; b) dacă două sau mai multe fişiere au fost sortate în aceeaşi ordine, devine posibilă găsirea intrărilor identice printr-o singură trecere prin ele, fără întoarcerea înapoi; c) sortarea este, de asemenea, un mijloc ajutător pentru căutare. 5.1.2 Definirea unei probleme de sortare Se dau N articole 1 2 , ,..., N R R R care urmează să fie sortate. Le vom numi înregistrări. Întreaga colecţie de înregistrări va fi numită fişier. Fiecare înregistrare j R are o cheie j K care guvernează procesul de sortare. Poate exista şi o altă informaţie de înregistrare, în afara cheii; această „informaţie satelit” nu va avea nici un efect asupra procesului de sortare, în afară de faptul că ea va rămâne în înregistrarea respectivă. Definiţia 5.1.2.1. O relaţie „< ” care satisface condiţiile de mai jos se numeşte relaţie de ordine totală: 1) numai dacă una din posibilităţile , , a b a b a b < = > este adevărată (Legea trihotomiei); 2) şi a b b c a c < < < (Legea tranzitivităţii). Scopul sortării este de a determina o permutare ( ) ( ) ( ) 1, 2 ,..., p p pN a înregistrărilor, care va pune cheile într-o ordine crescătoare: () () ( ) 1 2 ... . p p pN K K K Sortarea va fi stabilă dacă vom cere ca toate înregistrările cu chei egale să-şi menţină ordinea lor relativă originală, adică:

Cursul_6 Sortare-cautare

Embed Size (px)

Citation preview

Page 1: Cursul_6 Sortare-cautare

CAPITOLUL 5

METODE DE SORTARE ŞI CĂUTARE

5.1 SORTARE

5.1.1 Generalităţi despre sortare În general se defineşte sortarea ca un proces de separare şi aranjare a lucrărilor după clase şi tip. În programare acest termen este utilizat într-un sens mult mai special: de sortare a lucrărilor într-o ordine ascendentă sau descendentă. Vom utiliza termenul de sortare în sensul sortării într-o anumită ordine. Cele mai importante aplicaţii ale sortării sunt: a) soluţionarea problemei legate de punerea împreună a tuturor articolelor care au aceeaşi identitate; b) dacă două sau mai multe fişiere au fost sortate în aceeaşi ordine, devine posibilă găsirea intrărilor identice printr-o singură trecere prin ele, fără întoarcerea înapoi; c) sortarea este, de asemenea, un mijloc ajutător pentru căutare.

5.1.2 Definirea unei probleme de sortare Se dau N articole 1 2, ,..., NR R R care urmează să fie sortate. Le vom numi înregistrări. Întreaga colecţie de înregistrări va fi numită fişier. Fiecare înregistrare jR are o cheie jK care guvernează procesul de sortare. Poate exista şi o altă informaţie de înregistrare, în afara cheii; această „informaţie satelit” nu va avea nici un efect asupra procesului de sortare, în afară de faptul că ea va rămâne în înregistrarea respectivă.

Definiţia 5.1.2.1. O relaţie „< ” care satisface condiţiile de mai jos se numeşte relaţie de ordine totală: 1) numai dacă una din posibilităţile , ,a b a b a b< = > este adevărată (Legea trihotomiei); 2) şia b b c a c< < ⇒ < (Legea tranzitivităţii). Scopul sortării este de a determina o permutare ( ) ( ) ( )1 , 2 ,...,p p p N a înregistrărilor, care va pune cheile într-o ordine crescătoare:

( ) ( ) ( )1 2 ... .p p p NK K K≤ ≤ ≤

Sortarea va fi stabilă dacă vom cere ca toate înregistrările cu chei egale să-şi menţină ordinea lor relativă originală, adică:

Page 2: Cursul_6 Sortare-cautare

( ) ( )p i q j< pentru orice ( ) ( ) şip i p jK K i j= < .

În unele cazuri vom dori ca înregistrările să fie rearanjate fizic în memorie, astfel încât cheile să fie în ordine, în timp ce în alte cazuri va fi suficient să avem un tabel auxiliar în care să se specifice într-un mod oarecare permutările, astfel încât accesul la înregistrări să se facă în ordinea cheilor. Sortarea poate fi clasificată în: a) sortare internă, când înregistrările sunt păstrate în memoria internă a calculatorului; b) sortare externă, când există mai multe înregistrări decât cele care pot fi păstrate în memoria internă. Dacă înregistrările şi/sau cheile necesită fiecare un număr destul de mare de cuvinte ale memoriei calculatorului, atunci se recomandă construirea unui tabel de adrese de legătură în loc de înregistrările voluminoase. Această metodă nu necesită sortare prin tabel de adrese. Un exemplu este dat în figura. 5.1.

Figura 5.1. Tabel auxiliar cu adrese de legătură

Dacă cheia este scurtă, dar informaţia satelit a înregistrării este lungă, cheia poate fi pusă împreună cu adresele de legătură, pentru a obţine o viteză de lucru mai mare. Această metodă poartă numele de sortare prin chei. Alte tehnici de sortare utilizează un câmp auxiliar de legătură care este inclus în fiecare înregistrare. Aceste legături sunt astfel manipulate, încât în rezultatele finale înregistrările sunt legate astfel încât să se formeze o listă liniară cu fiecare legătură indicând înregistrarea următoare. Această metodă este denumită sortarea prin liste.

Page 3: Cursul_6 Sortare-cautare

5.1.3 Sortarea prin numărare Această metodă se bazează pe ideea că, în secvenţa finală sortată a " "j a− cheie este mai mare decât a " 1"j a− − din celelalte chei. Metoda constă în compararea fiecărei perechi de chei, numărând câte vor fi mai mici decât fiecare cheie particulară. Calea evidentă de a face comparaţiile constă în: compară jK cu iK , pentru 1, şi 1,j N i N= = . Se observă însă că peste jumătate din aceste comparaţii sunt redundante, nefiind necesar a se compara o cheie cu ea însăşi şi apoi nu e necesar să se compare cu şi cua b b aK K K K . Vom avea nevoie numai de a compara jK

cu iK , pentru 1,j i= şi 1,i N= . Aceasta conduce la următorul algoritm: Algoritmul 5.1.3.1. Sortarea prin numărare Acest algoritm va sorta 1 2, ,..., NR R R după cheile 1 2, ,..., NK K K , prin intermediul unui tabel auxiliar COUNT(1),..., COUNT(n), pentru a număra câte chei sunt mai mici decât o cheie dată. După terminarea algoritmului, COUNT(j)+1 va specifica poziţia finală a înregistrării ( )R j . Pasul P1: (şterge COUNT-urile): pune COUNT(1) până la COUNT(N) pe zero. Pasul P2: (ciclează după i): execută pasul P3 pentru , 1,...,2i N N= − apoi termină algoritmul. Pasul P3: (ciclează după j): execută pasul P4 pentru 1,...,2,1j i= − . Pasul P4: (compară jK cu iK ):

Dacă i jK K< incrementează COUNT(j) cu 1; altfel incrementează COUNT(i) cu 1.

Algoritmul este similar cu o sortare prin tabel de adrese şi nu compară nici o deplasare de înregistrări.

Acest algoritm furnizează rezultatul corect, indiferent de numărul de chei egale care sunt prezentate. Pseudocodul corespunzător acestui algoritm este:

Page 4: Cursul_6 Sortare-cautare

* procedura SORTNUM (n, k, COUNT) este pentru i =1,2,…, N execută COUNT(i) = 0 pentru i =N,…, 2 execută pentru 1−= ij , …,2,1 execută dacă ( ) ( )K i K j< atunci COUNT(j) = COUNT (j) + 1 altfel COUNT(i) = COUNT (i) + 1

scrie (COUNT(i), Ni ,1= ) sfârşit. În continuare este prezentată analiza stocastică a algoritmului. Pe schema logică din figura 5.2, ce corespunde algoritmului, s-a notat numărul de execuţii al fiecărei operaţii. Dacă vom considera că pentru execuţia fiecărei operaţii este necesară o unitate de timp calculator, atunci timpul total de rulare al acestui program va fi:

( )1 3 1 4 1 4 7 4 2T N N A N A= + + + − + = + − u.t. Se observă că în această relaţie nu intervine variabila aleatoare B, pentru că există două ramuri paralele care execută una de B ori, iar cealaltă de A–B ori, deci timpul necesar este A. A este egal cu numărul de posibilităţi de a alege două obiecte din N, deci:

( )2 1C

2N

N NA

−= = .

Din relaţia de mai sus rezultă: ( ) 21

7 4 2 2 5 22

N NT N N N

−= + ⋅ − = + − .

Factorul N care apare în relaţia de mai sus arată că algoritmul de sortare prin numărare nu este o cale eficientă în cazul în care N este mare. Deoarece metoda solicită compararea tuturor perechilor de chei distincte ( ),i jK K , nu există aparent nici o posibilitate de a reduce timpul de rulare. Totuşi, algoritmul prezintă interes prin simplitatea sa, şi nu prin eficienţă.

Există însă o altă posibilitate de sortare prin numărare, care nu este foarte importantă din punctul de vedere al eficacităţii. Acesta se aplică mai ales în cazurile în care există multe chei egale şi când toate cheile sunt cuprinse în domeniul u k v≤ ≤ , unde v u− este mic. Aceste condiţii par restrictive, dar de fapt există multe aplicaţii ale acestei idei.

Page 5: Cursul_6 Sortare-cautare

Figura 5.2 Schema logică a sortării prin numărare (atenţie: pentru i=1

numărul de execuţie este 1 şi nu A) De exemplu, dacă vom aplica această metodă numai anumitor biţi din prima parte a cheii, în loc de a o aplica la toată cheia, fişierul va fi parţial sortat şi va fi comparativ simplu de a termina sortarea.

Page 6: Cursul_6 Sortare-cautare

Numărarea distribuţiilor

Algoritmul 5.1.3.2. Numărarea distribuţiilor. Considerăm că toate cheile sunt întregi în domeniul , 1, .iu k v i N≤ ≤ = Acest algoritm va sorta înregistrările 1,..., NR R , utilizând un tabel auxiliar: COUNT(u),..., COUNT(v). La terminarea algoritmului înregistrările sunt deplasate într-o zonă de ieşire 1,..., NS S în ordinea dorită. Pasul P1: (şterge COUNT-urile): pune COUNT(u) până la COUNT(v) pe zero. Pasul P2: (ciclează după j): se execută pasul P3 pentru 1,i N= apoi se merge la pasul P4. Pasul P3: (măreşte COUNT( jK )): se măreşte valoarea lui COUNT( jK ) cu 1. Pasul P4: (acumulează):

(în acest moment COUNT(i) va fi egal cu numărul de chei ce sunt egale cu i) pune ( )← −COUNT( ) COUNT( )+COUNT 1i i i pentru

= − 1,..., .i u v Pasul P5: (ciclează după j):

(în acest moment COUNT(i) va fi egal cu numărul de chei ≤ i ; în particular, COUNT(v) = N).

execută pasul P6 pentru = ,...,1j N . Pasul P6: (ieşirea jR ):

pune ( )← COUNT ji K i jS R← ( ) ← −COUNT 1jK i .

5.1.4 Sortarea prin inserţie Înainte de a examina înregistrarea jR vom considera că înregistrările precedente 1 1,..., jR R − au fost deja sortate, apoi vom insera jR în locul ce-i revine între înregistrările sortate anterior.

Inserţia directă

Sortarea cea mai simplă prin inserţie este şi cea mai evidentă. Se consideră 1 j N< ≤ şi înregistrările precedente 1 1,..., jR R − aranjate astfel încât:

Page 7: Cursul_6 Sortare-cautare

1 1... jK K −≤ ≤ . Vom compara pe rând noua cheie jK cu 1 2, ,...j jK K− − , până vom descoperi că jK trebuie inserat între înregistrările 1şii iR R + ; apoi deplasăm înregistrările 1 1,...,i jR R+ − cu un spaţiu şi introducem noua înregistrare în poziţia

1iR + . Din cele descrise anterior obţinem: Algoritmul 5.1.4.1. Sortarea prin inserţie directă Înregistrările 1,..., nR R sunt rearanjate pe locurile lor; după sortare, cheile vor fi în ordinea 1 ... nK K≤ ≤ . Pasul P1: (ciclează după j): Execută paşii P2-P5 pentru j = 2,...,N, apoi termină algoritmul. Pasul P2: (Fixează i, k, r): Pune

.

1

j

j

RR

KKji

←−←

În paşii următori vom încerca să inserăm R în poziţia corectă, comparând K cu iK pentru valori descrescătoare ale lui i.

Pasul P3: (Compară K, iK ): Dacă iKK > mergi la pasul P5 (s-a găsit poziţia corespunzătoare pentru R).

Pasul P4: (deplasează iR , descreşte i): Pune 1i iR R+ ← , apoi 1−← ii şi mergi la pasul P3.

(Dacă i = 0, atunci K va fi cea mai mică cheie găsită până acum, deci R va trebui plasată pe poziţia 1).

Pasul P5: (R în iR ): Pune 1iR R+ ← . Schema logică a algoritmului este prezentată în figura 5.3. Pseudocodul corespunzător algoritmului de mai sus este prezentat în continuare:

Page 8: Cursul_6 Sortare-cautare

procedura sortinsdir (N,K) este pentru j = 2,…, N execută scrie ( )( ), 1,...,K L L j= ; ;1−= ji ( )jKK = ; repetă dacă ( )iKK < atunci ( ) ( )iKiK =+1 ; 1−= ii ; până când i = 0 sau ( )iKK ≥ ; ( ) KiK =+1 ; sfârşit.

Analiza stocastică a algoritmului Presupunând că pentru fiecare operaţie este necesară o unitate de timp calculator, timpul total de rulare a acestei rutine este:

( )1 6 1 3 1 7 4 6T N B B N A N B A= + − + + + − − = + − − u.t., unde: A = numărul de cazuri în care „ i ” descreşte la zero;

B = numărul de deplasări ale înregistrării cu un pas; B este egal cu numărul de inversiuni ale permutării:

1 2

1 2 ...... N

NK K K

.

Vom avea deci: min 0A = ;

1max −= NA ;

1

1 ; n

nk

Hk=

=∑

med 1NA H= − ; 2dis NN HHA −= ;

NKKB ,...,pentru0min 1= ordonate crescător;

( )2

1

1max C pentru ,...,

2N N

N NB K K

−= = ordonate crescător;

( )

41med −

=NNB ;

( )( )72

521dis +−=

NNNB .

Page 9: Cursul_6 Sortare-cautare

Figura 5.3 Sortarea prin inserţie directă

Page 10: Cursul_6 Sortare-cautare

Rezultă că timpul total T este ( ) 21

7 4 1 6 6 54 N N

N NT N H N N H

−= + − + − = + − − .

Pentru n suficient de mare putem aproxima: ( )NNNT ln262 −+≅ .

Dacă înregistrările sunt parţial ordonate, creşte NH şi deci timpul de

rulare scade. Termenul 2N este însă dominant. Totuşi, algoritmul de sortare prin inserţie directă este mult mai eficient decât algoritmul de sortare prin numărare, datorită faptului că nu există compararea tuturor perechilor diferite de chei. Algoritmul prezintă interes pentru eficienţa sa raportată la simplitatea sa deosebită. Iată şi câteva exemple de programe pentru problemele tratate până aici:

// Programul 1 - Sortarea prin numărare #include<stdio.h> int a[15001],i,n,j; int count[15001]; int main(void) {

printf("dati un n"); scanf("%d",&n); for(i=1;i<=n;i++)

{ printf("\ndati a[%d]\n",i); scanf("%d",&a[i]); }

for(i=1;i<=n;i++) count[i]=0; for(i=n;i>1;i--) for(j=i-1;j>=1;j--) if(a[j]>a[i])

count[j]++; else

count[i]++; for(i=1;i<=n;i++) printf("%d ",count[i]); return 0;

}

Page 11: Cursul_6 Sortare-cautare

// Programul 2 - Numărarea distribuţiilor #include<stdio.h> int s[15001],a[15001],i,n,j,min=32000,max=-32000; int count[15001]; int main(void) {

printf("dati un n"); scanf("%d",&n); for(i=1;i<=n;i++) { printf("\ndati a[%d]\n",i); scanf("%d",&a[i]); if(a[i]<min) min=a[i]; if(a[i]>max) max=a[i]; } for(j=1;j<=n;j++) {

count[a[j]]++; } for(i=min-1;i<=max;i++) count[i]=count[i]+count[i-1]; for(j=n;j>=1;j--) {

i=count[a[j]]; s[i]=a[j]; count[a[j]]=i-1;

} return 0;

}

// Programul 3 - Sortarea prin inserţie #include<stdio.h> int a[15001],i,n,j,k,r; int main(void) {

printf("dati un n"); scanf("%d",&n); for(i=1;i<=n;i++) { printf("\ndati a[%d]\n",i); scanf("%d",&a[i]);

Page 12: Cursul_6 Sortare-cautare

} for(j=n;j>=2;j--) {

i=j-1; k=a[j]; do if(k<a[i]) {

a[i+1]=a[i]; i=i-1;

} while(!(i==0||k>=a[i])); a[i+1]=k;

} for(j=1;j<=n;j++) printf("%d ",a[j]); return 0;

}

Metoda lui Shell Dacă vrem să îmbunătăţim substanţial inserţia directă, avem nevoie de un mecanism prin intermediul căruia înregistrările să efectueze salturi lungi, în loc de paşi mici. O asemenea metodă a fost propusă de Donald L. Shell în 1959 şi va fi denumită sortare cu micşorarea incrementului.

Exemplu numeric

Tabelul de mai jos ilustrează ideea generală ce stă la baza metodei: - la început se împart cele 16 înregistrări în 8 grupe a câte două, adică ( ) ( ) ( )1 9 2 10 8 16, , , ,..., ,R R R R R R ; - sortând fiecare grupă separat, vom obţine o a doua linie a tabelului de mai jos; am realizat astfel „prima trecere”; - acum se împart înregistrările în 4 grupe de câte 4: ( )1 5 9 13, , ,R R R R ,..., ( )4 8 12 16, , ,R R R R şi din nou fiecare grupă este sortată în parte; am realizat a 2-a trecere; - a 3-a trecere va sorta 2 grupe de 8 înregistrări, apoi a 4-a trecere termină algoritmul, sortând cele 16 înregistrări. Observaţii: 1) Fiecare din procesele intermediare comportă fie un fişier relativ scurt, fie un fişier bine ordonat, astfel că inserţia directă poate fi utilizată pentru fiecare operaţie de sortare.

Page 13: Cursul_6 Sortare-cautare

2) Secvenţa de incrementare 8, 4, 2, 1 nu este unică; poate fi utilizată orice secvenţă 1 1, ,...,t th h h− , cu condiţia ca ultimul increment să fie egal cu 1. R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16503 087 512 061 908 170 897 275 653 426 154 509 612 677 785 703 503 087 154 061 612 170 765 275 653 426 512 509 908 677 897 703 154 061 503 087 512 170 612 275 653 426 765 509 897 677 908 703 503 087 154 061 612 170 512 275 653 426 765 509 908 677 897 703 061 087 154 170 275 426 503 509 512 612 653 677 703 765 897 908

Pe baza exemplului de mai sus se poate enunţa următorul algoritm:

Algoritmul 5.1.4.2. Metoda lui Shell: - înregistrările 1,..., NR R sunt rearanjate pe loc; - după terminarea sortării cheile lor vor fi în ordinea 1 2 ... NK K K≤ ≤ ≤ ; - o secvenţă auxiliară de incremenţi 1,...,th h va fi utilizată pentru controlul procesului de sortare, unde 1 1h = . Pasul P1: (ciclează după s): Execută pasul P2 pentru 1,...,1, −= tts , apoi termină algoritmul. Pasul P2: (ciclează după j): Pune shh← şi execută paşii P3÷P6 pentru Nhj ,...,= .

(Vom utiliza metoda inserţiei directe pentru a sorta elementele ce sunt la distanţa de h poziţii, astfel încât hii KK −≤ , pentru

hNi −≤≤1 . Paşii de la P3 la P6 sunt aceiaşi cu paşii de la P2 la P5 din algoritmul anterior).

Pasul P3: (fixează i, K, R): Atribuie

j

j

RR

KKhji

←−←

Pasul P4: (compară iKK , ): Dacă iKK ≥ , atunci mergi la pasul P6. Pasul P5: (deplasează iR , descreşte i): Atribuie

.

1hiiRR ii

−←←+

Dacă 0>i , mergi la P4.

Page 14: Cursul_6 Sortare-cautare

Pasul P6: (R în hiR + ): Pune RR hi ←+ .

Inserţii prin liste Aceasta este o metodă de îmbunătăţire a inserţiei directe. Inserţia directă implică două operaţii de bază:

1) parcurgerea unui fişier ordonat pentru a găsi cea mai mare cheie, mai mare sau egală cu o cheie dată;

2) inserarea unei înregistrări noi într-o anumită parte a fişierului ordonat. Fişierul este, evident, o listă liniară şi algoritmul de sortare prin inserţie directă (algoritmul 5.1.4.1) va manipula această listă utilizând alocarea secvenţială. În concluzie, structura adecvată de date pentru inserţie directă este o listă liniară cu legături, unidirecţională. Este convenabil să revedem algoritmul menţionat mai sus, astfel ca lista să fie parcursă în ordine crescătoare. Astfel, vom obţine algoritmul următor:

Algoritmul 5.1.4.3. Inserţii de liste Se consideră că înregistrările 1,..., NR R conţin cheile 1,..., NK K şi „câmpurile de legătură” NLL ,...,1 , capabile de a conţine numere de 0 la n. Există un câmp de legătură 0L într-o înregistrare 0K , artificială, la începutul fişierului. Algoritmul va fixa câmpurile de legătură, astfel încât înregistrările să fie legate în ordine ascendentă. Astfel, dacă ( ) ( )1 ,...,p p N este o permutare „stabilă” care ordonează ( ) ( )1 ,...,p pK K N , acest algoritm va da:

( )( ) ( )( )

0 1

1 , 1, 1

0.p

p

L p

L i p i i N

L N

= = + = − =

Pasul P1: (ciclează după j): Atribuie

0

0←←

NLNL

( 0L acţionează în calitate de „cap” al listei, iar 0 în calitate de „legătura zero”, deci lista este circulară). Execută paşii de la P2 la P5 pentru 1,...,1−= Nj , apoi termină algoritmul.

Pasul P2: (fixează p, q, K): Atribuie

Page 15: Cursul_6 Sortare-cautare

jKKq

Lp

←←←

00

(În paşii ce urmează vom insera jR la locul potrivit în lista legată, comparând K cu cheile anterioare în ordine ascendentă. Variabilele p şi q au rol de indicatoare spre locul cunoscut în listă, cu qLp = , astfel că variabila q este cu un pas în urma lui p).

Pasul P3: (compară pKK , ): Dacă pKK ≤ , mergi la pasul P5.

(Am găsit poziţia dorită pentru înregistrarea R, între pq RR şi , în listă).

Pasul P4: Atribuie pq ← şi qLp ← . Dacă 0>p mergi înapoi la pasul P3

(Dacă 0=p , K va fi cheia cea mai mare găsită până în prezent, deci înregistrarea R aparţine sfârşitului listei, între 0şi RRq ).

Pasul P5: (inserează în listă): Atribuie

.pj

jLq

Acest algoritm apare des ca o componentă a altor algoritmi de

prelucrare a listelor.

5.1.5 Sortarea prin interschimbare

Această metodă interschimbă perechea de elemente care nu este în ordine, până când nu mai există nici o astfel de pereche. Procesul de inserţie directă poate fi vizualizat ca o metodă de interschimbare: vom lua fiecare înregistrare Rj şi-o vom interschimba cu vecinii săi din stânga, până când ajunge la locul potrivit.

Probabil că cea mai evidentă cale de sortare prin interschimbări constă în compararea lui K1 cu K2 şi interschimbarea lui R1 cu R2, dacă cheile nu sunt în ordine, apoi se va face acelaşi lucru pentru înregistrările R2 şi R3, R3 şi R4 etc. În timpul acestei secvenţe de operaţii, înregistrările cu chei mari tind să se deplaseze spre dreapta, şi de fapt înregistrarea cu cheia cea mai mare va avansa pentru a devenii Rn. Repetarea acestui proces va pune înregistrările potrivite în

Page 16: Cursul_6 Sortare-cautare

poziţiile Rn-1, Rn-2 etc., astfel încât toate înregistrările vor fi sortate. Algoritmul corespunzător acestei metode este următorul:

Algoritmul 5.1.5.1: Înregistrările 1,..., NR R sunt rearanjate după terminarea sortării, cheile vor fi în ordine, 1 ... NK K≤ ≤ . Pasul P1: (iniţializează BOUND): Stabileşte N←BOUND (BOUND este indice). Pasul P2: (ciclează după j): Stabileşte 0←t .

Efectuează pasul P3 pentru = −1,..., BOUND 1j şi apoi mergi la pasul P4.

Pasul P3: (compară/interschimbă 1cu +jj RR ): Dacă 1+> jj KK interschimbă 1cu +jj RR şi stabileşte jt ← . Pasul P4: (sunt interschimbări?): Dacă t = 0, algoritmul se termină. În caz contrar, stabileşte t←BOUND şi revino la pasul P2. Iată o schemă bloc de funcţionare a algoritmului:

Organigrama pentru sortare prin metoda bulelor

Algoritmul prezentat mai sus poate fi realizat şi într-o altă variantă, în care la pasul P2 ciclarea se execută pentru = −1,..., 1j N şi se schimbă variabila BOUND.

Page 17: Cursul_6 Sortare-cautare

Această a doua variantă consumă însă un timp de lucru mai mare decât prima variantă, deoarece necesită efectuarea unor operaţii de comparare suplimentare în subşiruri deja ordonate.

Desi s-au incercat unele perfectionari ale metodei de mai sus , se observa ca ele nu conduc la un algoritm mai bun decat insertia directa .

5.1.6 Sortarea prin selecţie O altă familie importantă de tehnici de sortare este bazată pe idea selecţiei

repetate. Cea mai simplă metodă de selecţie este probabil următoarea: i) Aflaţi cheia cea mai mică; transferaţi înregistrarea corespunzătoare în

aria de ieşire; apoi înlocuiţi cheia prin valoarea ”infinit” (care se presupune că este mai mare decât orice cheie actuală).

ii) Repetaţi pasul (i). De această dată va fi selectată următoarea cheie cu valoarea cea mai mică, deoarece cea mai mică valoare a fost înlocuită prin “infinit”.

(iii) Continuaţi repetarea pasului (i) până când cele N înregistrări au fost selectate.

De notat ca o astfel de metodă de selecţie necesită ca toate elementele de intrare să fie prezentate înainte ca sortarea să poată începe şi ea generează datele finale de ieşire, una după alta, în secvenţe. Aceasta, în esenţă se deosebeşte de inserţie, unde intrările se primesc secvenţial, dar nu se ştie nimic despre ieşiri, până când sortarea este completă. Metoda de mai sus implică N-1 comparaţii de fiecare dată când o nouă înregistrare este selectată şi de asemenea, necesită un spaţiu separat de memorie pentru datele de ieşire. Există un mod evident pentru ameliorarea acestei situaţii, evitând utilizarea lui “infinit”. Se poate lua valoarea selectată şi muta într-o poziţie adecvată ei, prin interschimbarea cu înregistrarea curentă care ocupa acea poziţie. Apoi nu mai este necesar să considerăm această poziţie în selecţiile viitoare. Această idee conduce la următorul algoritm de sortare prin selecţie:

Algoritmul 5.1.6.1: Înregistrările NRRR ,...,, 21 sunt de sortat; după ce sortarea este completă, cheile acestor înregistrări vor fi în ordinea NKKK ≤≤≤ ...21 . Sortarea se bazează pe faptul că se selectează la început elementul cel mai mare, apoi al 2-lea element cu valoarea cea mai mare etc. Pasul P1: (buclează pe j): Se execută paşii P2 şi P3 pentru 2,...,1, −= NNj . Pasul P2: (găseşte: 1max ( ,..., ) )j iK K K= :

Page 18: Cursul_6 Sortare-cautare

Se caută pentru cheile 11,...,, KKK jj − cea mai mare dintre ele (fie aceasta iK ). Pasul P3: (interschimbă): Se interschimbă înregistrările ji RR şi (Acum înregistrările Nj RR ,..., sunt în poziţia lor finală).

Sortare prin selecţie directă

Exemplu numeric

876542876524876524872564842567642587

5.1.7 Sortarea prin interclasare

Interclasarea (sau colaţionarea) înseamnă combinarea a două sau mai multe fişiere într-un singur fişier ordonat. Observaţie. Când unul din fişiere este epuizat este necesară ceva mai multă atenţie.

De exemplu putem interclasa două fişiere : "503 703 765""087 512 677"

pentru a obţine: “ 087 503 512 677 703 765 “.

Page 19: Cursul_6 Sortare-cautare

Observaţie. Un mod simplu pentru realizarea acestui deziderat constă în a compara elementele cele mai mici, de a extrage pe cel mai mic etc. Algoritmul de sortare prin interclasare este prezentat mai jos:

Algoritmul 5.1.7.1: Acest algoritm interclasează fişierele ordonate mxxx ≤≤≤ ...21 şi

nyyy ≤≤≤ ...21 într-un singur fişier nmzzz +≤≤≤ ...21 . Pasul P1: (iniţializare): Stabileşte

.1

11

←←←

kji

Pasul P2: (găseşte pe cel mai mic): Dacă ji yx ≤ , treci la pasul P3; altfel treci la pasul P5. Pasul P3: (extrage ix ): Stabileşte

.1

1+←+←

iiKKxz ik

Dacă mi ≤ , treci la pasul P2. Pasul P4: (transmite nmk zz +,..., ): Stabileşte ( ) ( )njnmk yyzz ,...,,..., ←+ şi termină algoritmul. Pasul P5: (extrage jy ): Stabileşte

.11

+←+←

jjKK

yz jk

Dacă nj ≤ , treci la pasul P2. Pasul P6: (transmite mi xx ,..., ): Stabileşte ( ) ( )minmk xxzz ,...,,..., ←+ şi termină algoritmul.

Observaţie. Interclasarea necesită o muncă mai simplă decât sortarea. Cantitatea totală de muncă implicată în acest algoritm este de fapt proporţională cu m+n, astfel este clar că interclasarea este o problemă mai simplă decât sortarea. Mai mullt, putem reduce problema de sortare la interclasare şi în acest

Page 20: Cursul_6 Sortare-cautare

caz este posibil să se continue interclasarea subfişierelor, din ce în ce mai lungi până când toate elementele sunt sortate; inserţia unui nou element într-un fişier sortat reprezintă cazul special de interclasare când n=1.

Interclasare X1<=…<=Xm cu Y1<=…<=Yn

Dacă dorim să facem procesul de inserţie mai rapid, putem considera câteva elemente inserate la un moment dat, interclasate, şi aceasta conduce natural la idea generală de sortare prin interclasare. Din punct de vedere istoric, sortarea prin interclasare este una din primele metode propuse pentru sortarea cu calculatorul; aceasta a fost sugerată de John von Neumann înainte de anul 1945.

Programele în limbajul C care rezolvă problematica sortările enunţate anterior:

// Programul 1 - metoda bulelor #include <stdio.h> #include <conio.h> #define nmax 100 /* ALGORITMI DE SORTARE */ void citire_vector(int n,float a[nmax],float b[nmax]) /* CITIRE ELEMENTE VECTOR */ { int i;

printf("\nIntroduceti elementele vectorului de sortat\n");

Page 21: Cursul_6 Sortare-cautare

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

printf("a[%d]=",i); scanf("%f",&a[i]); b[i]=a[i];

} }

void afisare(int n,float a[nmax]) /* AFISARE VECTOR */ { int i; for(i=0;i<n;i++) { printf("%8.2f",a[i]); if(((i+1) % 10)==0) printf("\n"); } } void sort_metoda_bulelor(int n,float a[nmax]) /* SORTAREA PRIN INTERSCHIMBARE-METODA BULELOR */ { int i,j,gata; float x; j=0; do

{ gata=1; j=j+1; for(i=0;i<n-j;i++) if(a[i]>a[i+1])

{ gata=0; x=a[i];a[i]=a[i+1];a[i+1]=x;

}; }while(gata==0); }

void main(void) {

int i,n; float a[nmax],b[nmax]; clrscr(); printf("\nIntroduceti nr.elementelor n=");

Page 22: Cursul_6 Sortare-cautare

scanf("%d",&n); citire_vector(n,a,b); printf("\nVECTORUL NESORTAT\n"); afisare(n,a); printf("\nVECTORUL SORTAT PRIN METODA BULELOR\n"); sort_metoda_bulelor(n,a); afisare(n,a); getch();

} // Programul 2-sortare prin selectie #include <stdio.h> #include <conio.h> #define nmax 100 /* ALGORITMI DE SORTARE */ void citire_vector(int n,float a[nmax],float b[nmax]) /* CITIRE ELEMENTE VECTOR */ { int i;

printf("\nIntroduceti elementele vectorului de sortat\n");

for(i=0;i<n;i++) { printf("a[%d]=",i); scanf("%f",&a[i]); b[i]=a[i]; } } void afisare(int n,float a[nmax]) /* AFISARE VECTOR */ { int i; for(i=0;i<n;i++) { printf("%8.2f",a[i]); if(((i+1) % 10)==0)

printf("\n"); } }

Page 23: Cursul_6 Sortare-cautare

void sort_selectie(int n,float a[nmax]) /* SORTAREA PRIN SELECTIE */ {

int i,j,poz; float x; for(i=0;i<n-1;i++) {

x=a[i];poz=i; for(j=i+1;j<n;j++) if(a[j]<x)

{ x=a[j]; poz=j; } a[poz]=a[i];a[i]=x; } } void main(void) {

int i,n; float a[nmax],b[nmax]; clrscr(); printf("\nIntroduceti nr.elementelor n=");

scanf("%d",&n); citire_vector(n,a,b); printf("\nVECTORUL NESORTAT\n"); afisare(n,a);

printf("\nVECTORUL SORTAT PRIN METODA SELECTIEI DIRECTE\n"); sort_selectie(n,a);

afisare(n,a); getch(); }

// Programul 3-sortare prin interclasare #include<stdio.h> #include<dos.h> #include<conio.h> int a[15001],i,n; void inter(int l1i,int l1s,int l2i,int l2s) {

int i,j,k, b[15001];

Page 24: Cursul_6 Sortare-cautare

i=l1i; j=l2i; k=0; while((i<=l1s)&&(j<=l2s)) {

k++; if(a[i]<a[j])

{ b[k]=a[i]; i++;

} else

{ b[k]=a[j]; j++;

} } if(i<=l1s)

for(j=i;j<=l1s;j++) {

k++; b[k]=a[j];

} else

for(i=j;i<l2s;i++) {

k++; b[k]=a[i];

} for(i=l1i;i<=l1i+k-1;i++) a[i]=b[i-l1i+1];

} void sort(int li,int ls) {

if(ls>li) { int m; m=(li+ls)/2; sort(li,m); sort(m+1,ls); inter(li,m,m+1,ls);

}

Page 25: Cursul_6 Sortare-cautare

} int main(void) {

clrscr (); printf("dati nr de elemente care vor fi sortate"); scanf("%d",&n); printf("\nDati elementele"); for(i=1;i<=n;i++)

{ printf("a[%d]=",i); scanf("%d",&a[i]);

} printf("Sirul neordonat este : ");

for (i=1;i<=n;i++) printf("%d ",a[i]); sort(1,n); printf("\nSirul ordonat este : "); for (i=1;i<=n;i++) printf("%d ",a[i]);

return 0; }

5.2 CĂUTARE

5.2.1 Introducere În general, se va presupune că s-a memorat o mulţime N de înregistrări, iar problema care se pune este de a localiza înregistrarea care se doreşte. Ca şi în cazul sortării se presupune că fiecare înregistrare conţine un câmp special numit cheia înregistrării. În general, se va presupune că cele N chei sunt distincte, astfel încât fiecare cheie să identifice unic înregistrarea căreia îi aparţine. Colecţia tuturor înregistrărilor poartă numele de TABEL sau de FIŞIER, denumirea de „tabel” fiind de obicei folosită pentru a indica un fişier de dimensiune mică, iar denumirea de „fişier”, pentru a indica un tabel de dimensiune mare. Un fişier mare sau o grupă de fişiere formează o bază de date. Algoritmii de căutare presupun un aşa-numit argument K, iar problema este de a găsi care înregistrare are drept cheie pe K. După terminarea căutării sunt posibile două situaţii: - căutarea a avut succes, fiind localizată unica înregistrare ce conţine pe K; - căutarea a fost fără succes, constatându-se că nu există nici o înregistrare cu cheia K.

Page 26: Cursul_6 Sortare-cautare

Uneori se doreşte ca după o căutare fără succes să se insereze în tabel o nouă înregistrare care să conţină pe K, procedura care realizează această operaţie numindu-se „algoritm de căutare şi inserare”. Metodele de căutare pot fi clasificate în mai multe categorii asupra cărora nu se va insista.

5.2.2 Căutarea secvenţială Mulţi dintre algoritmii complecşi se bazează pe căutarea secvenţială. Algoritmul 5.2.2.1: Fiind dat un tabel de înregistrări NRR ,...,1 cu cheile corespunzătoare,

NKK ,...,1 , acest algoritm caută înregistrarea cu cheia K. Se presupune că 1≥N . Pasul P1: (iniţializare): Se stabileşte 1←i . Pasul P2: (comparare):

Dacă iKK = , algoritmul ia sfârşit cu rezultat pozitiv. Pasul P3: (avansare): Se creşte i cu 1. Pasul P4: (sfârşit fişier):

Dacă Ni ≤ , se revine la pasul P2. În caz contrar, algoritmul ia sfârşit cu rezultat negativ.

Schema logică ce corespunde acestui algoritm este:

Figura 5.4 Schema logică a căutării secvenţiale

Când căutarea nu a avut succes, numărul maxim de comparaţii C are valoarea: NC =max .

i=1

K=Ki

i=N

DA (rezultat negativ)

(rezultat pozitiv)

DA

i=i+1

Page 27: Cursul_6 Sortare-cautare

Dacă toate cheile căutate apar echiprobabile, atunci valoarea medie a lui C este:

med

1 2 ... 12

N NCN

+ + + += = şi 0,3c Nσ ≅ ⋅ (pentru N mare).

Acest algoritm nu reprezintă întotdeauna cea mai bună cale de căutare secvenţială. O modificare directă face ca algoritmul să fie mai rapid dacă lista de înregistrări nu este prea mică, şi anume:

Algoritmul 5.2.2.2. Căutarea secvenţială rapidă Acest algoritm este asemănător celui precedent, cu excepţia că presupune prezenţa unei înregistrări oarbe, 1+NR , la sfârşitul fişierului: Pasul P1: (iniţializare): Se stabileşte KKi N ←← +1şi1 . Pasul P2: (comparare): Dacă iKK = , se trece la pasul P4. Pasul P3: (avansare): Se creşte i cu 1 şi se revine la pasul P2. Pasul P4: (sfârşit fişier):

Dacă Ni ≤ , algoritmul se sfârşeşte cu succes; în caz contrar, se termină cu eşec.

Schema logică ce corespunde acestui algoritm este dată în figura 5.5.

Figura 5.5. Schema logică a căutării secvenţiale rapide

Page 28: Cursul_6 Sortare-cautare

Observaţie. La algoritmul 5.2.2.1 se făcea comparaţia Ni ≤ , de fiecare dată când

iKK ≠ şi de aceea era necesar un număr mare de calcule.

Algoritmul 5.2.2.3. Căutarea secvenţială într-un tabel ordonat Fiind dat un tabel de înregistrări NRR ,...,1 ale căror chei sunt ordonate crescător NKKK <<< ...21 , acest algoritm caută înregistrarea cu o anumită cheie K. Pentru uşurinţă şi pentru reducerea timpului de căutare, se presupune existenţa unei înregistrări „oarbe” 1+NR cu cheia KKN >∞=+1 . Pasul P1: (iniţializare): Se stabileşte 1←i . Pasul P2: (comparare): Dacă iKK ≤ , se trece la pasul P4. Pasul P3: (avansare): Se creşte i cu 1 şi se revine la P2. Pasul P4:

Dacă iKK = , algoritmul se sfârşeşte cu succes; în caz contrar, se termină cu eşec.

Observaţie. În fiecare din algoritmii de mai sus am folosit indici pentru a reprezenta poziţiile din tabel. Se pot transmite aceşti algoritmi pentru cazul în care tabelele sunt reprezentate de structuri înlănţuite.

5.2.3 Frecvenţa acceselor Până acum am presupus că fiecare argument al căutării apare în aceeaşi probabilitate ca oricare alt argument. Aceasta nu este întotdeauna o metodă realistă; într-o situaţie generală cheia iK apare cu probabilitatea ip , unde

11

=∑=

N

iip .

Timpul necesar realizării unei căutări cu succes este de fapt proporţional cu numărul de comparaţii C, care în acest caz are valoarea medie:

NN pNppC ⋅+++= ...2 21 . Dacă există posibilitatea ca înregistrările să fie aşezate în orice ordine, mărimea NC va avea valoarea minimă în cazul:

1 2 ... Np p p≥ ≥ ≥ ,

Page 29: Cursul_6 Sortare-cautare

adică atunci când cele mai frecvent căutate înregistrări se găsesc aproape de începutul tabelului. Să considerăm câteva distribuţii de probabilităţi, pentru a aprecia cât se economiseşte atunci când înregistrările sunt aranjate în ordinea „optimă”:

1). Dacă N

ppp N1...21 ==== , atunci

21 1 şi 0,289 (pentru N mare).2 2N N

N NC K Nσ+ − = = − ≅ ⋅

2). Dacă 12

1...,,41,

21

21−

=== NNppp (distribuţia binară), atunci

NNNN CCC −

− −=⇒+= 11 221

21 .

Pentru această distribuţie numărul mediu de comparaţii este mai mic decât 2, dacă înregistrările apar în tabel în ordinea corespunzătoare. 3). Dacă ( ) cpcNpcNp N =−== • ...,,1, 21 , unde:

( )12+

=NN

c ,

atunci:

( )3

211

+=+−⋅= ∑

=

NKNKcCN

KN .

Distribuţiile de mai sus sunt destul de artificiale şi ele au o importanţă mai mult teoretică. 4). O reprezentare mai utilizată este „legea lui Zipf”, care a observat că al n-lea cel mai utilizat cuvânt din limbajul natural pare a avea o frecvenţă de utilizare invers proporţională cu N:

,;...;2

;1 21 N

cpcpcp N === unde NH

c 1= ;

în acest caz

NN H

NC = .

5.2.4 Căutarea în fişiere cu înregistrări de lungimi inegale

Formularea problemei

Să presupunem că tabelul în care se caută este memorat într-o structură secvenţială (spre exemplu, pe o bandă magnetică), iar înregistrările individuale au lungimi variabile.

Page 30: Cursul_6 Sortare-cautare

De exemplu, programele de sistem standard, cum sunt: compilatoarele, asambloarele, rutinele de încărcare, generatoarele de tabel etc. sunt „înregistrări” pe o asemenea bandă. Această organizare face inaplicabilă analiza anterioară algoritmului 5.2.2.1, deoarece pasul P3 necesită o durată de timp diferită de la o execuţie la alta. Fie iL lungimea înregistrării iR . Fie ip probabilitatea ca această înregistrare să fie căutată. Timpul de execuţie necesitat de metoda de căutare va fi acum aproximativ proporţional cu:

( ) ( )NN LLLpLLpLp +++++++ ...... 2121211 . Atunci când 1...21 ==== NLLL , expresia se reduce la

NN NpppC +++= ...2 21 , caz deja studiat. Observaţie. Pare logic să se plaseze înregistrările cele mai frecvent căutate la începutul benzii. Aceasta este însă uneori o idee proastă. De exemplu: presupunem că banda conţine doar două programe A şi B. A este solicitat de două ori mai frecvent decât B, dar este de patru ori mai lung decât acesta. Astfel:

.1,431,

32,2

==

===

BA

BA

LL

ppN

Dacă se plasează A la începutul benzii conform principiului „logic” exprimat mai sus, timpul mediu de execuţie este:

3135

314

32

=⋅+⋅ .

Dacă folosim o idee „ilogică” şi plasăm mai întâi pe B, timpul de execuţie se ridică la:

3115

321

31

=⋅+⋅ .

Aranjarea optimă a programelor pe o bandă-bibliotecă poate fi determinată după cum urmează: Teoremă. Fie iL şi ip definite ca mai sus. Aranjarea înregistrărilor în tabel este optimă dacă şi numai dacă:

1 2

1 2

... N

N

p p pL L L≥ ≥ ≥ .

Cu alte cuvinte, valoarea minimă a lui ( ) ( )

NN aaaaaaaa LLpLLpLp ++++++ ...... 121211

Page 31: Cursul_6 Sortare-cautare

pentru toate permutările { }1 2, ,..., ale lui 1, 2,..., Na a a N este egală cu relaţia prezentată mai sus. Demonstraţie. Implicaţia „ ⇒”: Să presupunem că iR şi 1+iR îşi schimbă reciproc locurile pe banda magnetică. Timpul mediu de execuţie se modifică din:

( ) ( ) ............ 11111 +++++++++ ++− iiiiii LLLpLLLp în

( ) ( ),......+... 11111 +− +++++++ iiiiii+ LLLpLLLp deci diferenţa dintre cele două relaţii este:

11

11 0+

+++ <⇒<−

ii

ii

iiii Lp

LpLpLp .

Rezultă că dacă 11+

+<ii

ii

Lp

Lp , atunci o asemenea schimbare va îmbunătăţi

timpul de execuţie, iar aranjamentul iniţial nu este cel optim. Implicaţia „⇐ ”: Se înlocuieşte fiecare probabilitate cu

( ) ( )N

ppN

iii

ε++ε+ε−ε+=ε

...21, unde ε este un infinit mic pozitiv.

Când ε este suficient de mic, nu vom avea niciodată: ( ) ( ) ( ) ( )ε++ε=ε++ε NNNN pypypxpx ...... 1111 ,

decât dacă: 1 1 2 2, , , ,N Nx y x y x y= = =…

iar în acest caz nu va avea loc egalitatea din teoremă. Să considerăm acum cele !N permutări ale înregistrărilor; cel puţin una este optimă şi ştim că satisface relaţia din enunţul teoremei. Una singură din permutări satisface această relaţie, deoarece nu există egalitate. Deci, relaţia din enunţ caracterizează în mod unic aranjamentul optim al înregistrărilor în tabel pentru probabilităţile ( )ip ε şi pentru ε suficient de mic. Prin continuitate, acelaşi aranjament trebuie să fie, de asemenea, optim şi când 0ε → . Anterior am folosit faptul că

( ) ( ) ( ) ( )

=

=⇒ε++ε=ε++ε

.......

11

1111

NN

NNNNyx

yxpypypxpx

În continuare demonstrăm acest lucru

( ) ( )N

ppN

iii

ε++ε+ε−ε+=ε

...21,

Page 32: Cursul_6 Sortare-cautare

( ) ( ) ( ) ( ) ( ) ( )( ) ( ) ( ) ( ) ( ) ( )

1 1 2 2 1 1 2 2

1 1 1 2 2 2

... ...

... 0 N N N N

N N N

x p x p x p y p y p y p

x y p x y p x y p

ε ε ε ε ε ε

ε ε ε

+ + + = + + + ⇒

− + − + + − ≡ ⇒

( ) ( )

( ) .0...

...2

21

212

222111

ε++ε+ε−ε+−+

+

ε+ε−ε+−+−

Npyx

pyxpyx

NN

NNN

Ordonând această expresie după puterile lui ε, obţinem:

( ) ( )

( )[ ] 0...

...1

1111

11

111

≡−ε+

++

−+−

−−ε−−

−ε −−

yx

yxyxN

yxN NNNN

NNN

N

Rezultă că coeficienţii lui ε trebuie să fie nuli, deci:

=

==

⇒ −−

.11

11

yx

yxyx

NN

NN

Cu aceasta teorema este complet demonstrată. În continuare vor fi discutate metodele de căutare ce se bazează pe o ordonare lineară a cheilor (de exemplu, în ordine alfabetică sau numerică). După compararea argumentului cu cheia iK din tabel, după cum iKK < ,

ii KKKK >= , , se va executa o anumită secvenţă. Metodele anterioare de căutare (secvenţială) erau limitate de fapt la o decizie cu două rezultate ii KKKK ≠= , , dar dacă ne eliberăm de restricţia accesului secvenţial, devine posibil să folosim eficient o relaţie de ordonare.

5.2.5 Căutarea într-un tabel ordonat În continuare ne vom ocupa de metode mai adecvate pentru căutarea în tabele ale căror înregistrări sunt ordonate:

NKKK <<< ...21 , presupunând că se pot efectua accese aleatoare la poziţiile din tabel. După compararea lui K cu iK , într-un asemenea tabel va rezulta: 1) [ ]Niii RRRKK ,...,, 1+⇒< nu sunt luate în considerare; 2) ⇒= iKK căutarea este terminată; 3) [ ]ii RRRKK ,...,, 21⇒> nu sunt luate în considerare.

Page 33: Cursul_6 Sortare-cautare

5.2.5.1 Căutarea binară Una din formele cele mai cunoscute ale acestui algoritm foloseşte două referinţe, I şi u, care indică limita inferioară şi limita superioară a căutării, şi anume: Algoritmul 5.2.5.1.1. Căutarea binară Fiind dat un tabel de înregistrări NRR ,...,1 ale căror chei sunt în ordine crescătoare NKKK <<< ...21 , acest algoritm caută înregistrarea cu cheia K. Pasul P1: (iniţializare): Se stabileşte

.

1Nu

I←←

Pasul P2: (obţine mijloc de tabel): În acest moment se cunoaşte că dacă se găseşte în tabel, el satisface uKKK ≤≤ . Dacă Iu < , algoritmul se termină fără succes. În caz contrar, se stabileşte ( ) 2/uIi +← (mijlocul aproximativ al tabelului). Pasul P3: (comparare): Dacă iKK < , se trece la pasul P4. Dacă iKK = , algoritmul se termină cu succes. Dacă iKK > , se trece la pasul P5. Pasul P4: (ajustare u): Se stabileşte 1−← iu şi se revine la pasul P2. Pasul P5: (ajustare I): Se stabileşte 1+← iI şi se revine la pasul P2.

Reprezentarea arborescentă

Pentru a înţelege mai bine ce se petrece în algoritmul de mai sus, este mai bine să fie privit ca un arbore de decizie binar, ca cel din figura 5.6, pentru N=16. Când N = 16, prima comparaţie efectuată de algoritm este K = K8; aceasta este reprezentată de nodul rădăcină 8 din figură. Apoi, dacă K < K8, algoritmul urmează subarborele din stânga, comparând pe K cu K4; similar, dacă K > K8, se merge pe subarborele din dreapta, comparând pe K cu K12. O căutare fără succes va conduce la unul din nodurile extreme. O căutare cu succes se va opri la un nod intermediar.

Page 34: Cursul_6 Sortare-cautare

Arborele binar corespunzător unei căutări binare se poate construi astfel: Dacă N = 0 ⇒ arborele este ∅. În caz contrar, nodul rădăcină este [ ]2/N , arborele din stânga este arborele corespunzător cu [ ]2/N –1 noduri, iar subarborele din dreapta este subarborele binar corespunzător cu [ ]2/N noduri, numerele nodurilor fiind crescute cu [ ]2/N .

Figura 5.6 Arbore de decizie binar

Orice algoritm de căutare într-un tabel ordonat de lungime N, folosind comparaţiile, poate fi reprezentat ca un arbore binar în care nodurile sunt etichetate cu numere de la 1 la N. Invers, orice arbore binar corespunde unei metode corecte de căutare într-un tabel ordonat. O variantă importantă a acestui algoritm este următoarea: în loc de a utiliza în căutare 3 indicatori, I, i şi u, este tentant să se utilizeze doar doi: poziţia curentă i şi viteza de modificare δ; după fiecare comparaţie de inegalitate se va stabili 2/şi δ=δδ±← ii (aproximativ). Algoritmul 5.2.5.1.2. Căutarea binară uniformă Fiind dat un tabel de înregistrări NRR ,...,1 ale căror chei sunt în ordine crescătoare NKKK <<< ...21 , acest algoritm caută înregistrarea cu cheia K. Dacă N este par, algoritmul va face uneori referire la o cheie inexistentă 0K , care trebuie făcută egală cu 1; ≥∞− N . Pasul P1: (iniţializare): Se stabileşte [ ] [ ]2/,2/ NmNi ←← .

Page 35: Cursul_6 Sortare-cautare

Pasul P2: (comparare): Dacă iKK < , se trece la pasul P3. Dacă iKK > , se trece la pasul P4. Dacă iKK = , algoritmul se termină cu succes. Pasul P3: (descreşte i):

(S-a ajuns cu căutarea la un interval care conţine m sau 1−m înregistrări; i va indica căutarea imediat spre dreapta acestui interval).

Dacă m = 0, algoritmul se termină fără succes. În caz contrar se stabileşte:

[ ]

[ ]2/2/

mmmii

←−←

şi se revine la pasul P2. Pasul P4: (creşte i):

(S-a ajuns cu căutarea la un interval care conţine m sau 1−m înregistrări; i va indica căutarea spre stânga acestui interval).

Dacă m = 0, algoritmul se termină fără succes. În caz contrar, se stabileşte:

[ ]

[ ]2/2/

mmmii

←+←

şi apoi se revine la pasul P2. Procesul de căutare poate fi denumit uniform, deoarece diferenţa dintre un nod de la nivelul I şi numărul ascendentului său de pe nivelul 1−I este o valoare constantă pentru toate nodurile de pe nivelul I. Principalul avantaj al acestui algoritm este că nu necesită memorarea separată a lui m.

1.8 Coduri sursă

1.8.1: Căutarea secvenţială

/* cautare secventiala a unei inregistrari intr-un tablou de numere intregi */ /* biblioteci necesare rularii programului */ #include<stdio.h> #include<conio.h> /* definirea unui tablou v de numere intregi de 100 de elemente */ int v[100]; /* definirea unei variabile de tip intreg i folosita ca contor in program */

Page 36: Cursul_6 Sortare-cautare

int i; /* definirea inregistrarii vk ce urmeaza a fi cautata in tabloul de numere intregi v de n elemente */ int vk,n; //partea principala a programului main() { /* definirea si deschiderea pentru citire a unui fisier f ce contine elementele tabloului ce urmeaza a fi initializat */ FILE *f=fopen("vector.in","r"); //initializarea contorului i cu valoarea 1 i=1; while (!feof(f)) /* atata timp cat nu am ajuns la sfarsitul fisierului f */

{ /* citim cate un element din f si il punem pe pozitia i in tabloul v */ fscanf(f, "%d", &v[i]); i=i+1; //incrementarea contorului }

fclose(f); // inchiderea fisierului f n=i-1; for(i=1;i<=n;i++) // pentru i de la 1 la n /* afisam valoarea v[i] a elementului de pe pozitia i */ printf(" %d",v[i]); printf("\n Introduceti inregistrarea de cautat:"); /* citirea inregistrarii in variabila vk */ scanf("%d",&vk); i=1; // initializarea contorului cu 1 /* daca inregistrarea de cautat vk este egala cu elementul v[i] */ secvential:

if (vk==v[i]) /* atunci afisam rezultatul pozitiv al cautarii lui vk in tabloul de intregi v */ printf("Inregistrarea %d a fost gasita in tablou pe pozitia %d.",vk,i);

else //in caz contrar { i=i+1; //se incrementeaza contorul i

Page 37: Cursul_6 Sortare-cautare

if (i<=n) /* daca inca nu am ajuns la

sfarsitul tabloului */ /* revenim in pozitia initiala a cautarii */ goto secvential;

else /* daca sa ajuns la sfarsitul tabloului */ /* afisam rezultatul negativ al cautarii lui vk in tabloul de intregi v */ printf("Inregistrarea %d Nu a fost gasita in tablou.",vk);

} return 0; //functia main nu returneaza nimic. }

1.8.2: Căutarea secvenţială rapidă

/* cautare secventiala rapida a unei inregistrari intr-un tablou de numere intregi */ /* biblioteci necesare rularii programului */ #include<stdio.h> #include<conio.h> /* definirea unui tablou v de numere intregi de 100 de elemente */ int v[100]; /* definirea unei variabile de tip intreg i folosita ca contor in program */ int i; /* definirea inregistrarii vk ce urmeaza a fi cautata in tabloul de numere intregi de n elemente */ int vk,n; //partea principala a programului main() { /* definirea si deschiderea pentru citire a unui fisier f ce contine elementele tabloului v ce urmeaza a fi initializat */ FILE *f=fopen("vector.in","r"); //initializarea contorului i cu valoarea 1 i=1; while (!feof(f))

Page 38: Cursul_6 Sortare-cautare

/* atata timp cat nu am ajuns la sfarsitul fisierului f */ {

/* citim cate un element din f si il punem pe pozitia i in tabloul v */ fscanf(f, "%d", &v[i]); i=i+1; //incrementarea contorului

} fclose(f); //inchiderea fisierului f n=i-1; for(i=1;i<=n;i++) //pentru i de la 1 la n

/* afisam valoarea v[i] a elementului de pe pozitia i */ printf(" %d",v[i]);

printf("\n Introduceti inregistrarea de cautat:"); /* citirea inregistrarii in variabila vk */ scanf("%d",&vk); i=1; //initializarea contorului cu 1 /* pe pozitia n+1 in tabloul v se insereaza inregistrarea vk */ v [n+1]=vk; secvential:

if (vk==v[i]) /* daca inregistrarea de cautat vk este egala cu elementul v[i] */

{ if (i<=n)

/* si daca inca nu am ajuns la finalul tabloului pe pozitia n+1 */ /* atunci afisam rezultatul pozitiv al cautarii lui vk in tabloul de intregi v */ printf("Inregistrarea %d a fost gasita in tablou pe pozitia %d.",vk,i);

else //in caz contrar /* afisam rezultatul negativ al cautarii lui vk in tabloul de intregi v */ p rintf("Inregistrarea %d Nu a fost gasita in tablou.",vk);

} else //in caz contrar {

i=i+1; //se incrementeaza contorul i //revenim in pozitia initiala a cautarii

Page 39: Cursul_6 Sortare-cautare

goto secvential; } return 0; //functia main nu returneaza nimic. }

1.8.3: Căutarea secvenţială într-un tabel ordonat /* cautare secventiala a unei inregistrari intr-un tablou ordonat de numere intregi */ /* biblioteci necesare rularii programului */ #include<stdio.h> #include<conio.h> /* definirea unui tablou v de numere intregi de 100 de elemente */ int v[100]; /* definirea unei variabile de tip intreg i folosita ca contor in program */ int i; /* definirea inregistrarii vk ce urmeaza a fi cautata in tabloul v de numere intregi de n elemente */ int vk,n; // partea principala a programului main() { /* definirea si deschiderea pentru citire a unui fisier f ce contine elementele tabloului ce urmeaza a fi initializat */ FILE *f=fopen("vector.in","r"); //initializarea contorului i cu valoarea 1 i=1; while (!feof(f))

/* atata timp cat nu am ajuns la sfarsitul fisierului f */

{ /* citim cate un element din f si il punem pe pozitia i in tabloul v */ fscanf(f, "%d", &v[i]); i=i+1; //incrementarea contorului

} fclose(f); //inchiderea fisierului f n=i-1; for(i=1;i<=n;i++) // pentru i de la 1 la n

/* afisam valoarea v[i] a elementului de pe pozitia i */

Page 40: Cursul_6 Sortare-cautare

printf(" %d",v[i]); printf("\n Introduceti inregistrarea de cautat:"); /* citirea inregistrarii in variabila vk */ scanf("%d",&vk); i=1; //initializarea contorului cu 1 secvential:

if (vk<=v[i]) /* daca inregistrarea de cautat vk este mai mica sau egala cu cea a elementul v[i] */ { if (vk==v[i])

/* daca inregistrarea de cautat vk este egala cu cea de pe pozitia i din tablou */ /* atunci afisam rezultatul pozitiv al cautarii lui vk in tabloul de intregi v */ printf("Inregistrarea %d a fost gasita in tablou pe pozitia %d.",vk,i);

else //in caz contrar /* afisam rezultatul negativ al cautarii lui vk in tabloul de intregi v */ printf("Inregistrarea %d Nu a fost gasita in tablou.",vk);

} else

/* in caz contrar cand vk este mai mare decat v[i] */

{ i=i+1; //se incrementeaza contorul i goto secvential; /* revenim in pozitia initiala a cautarii */ }

return 0; // functia main nu returneaza nimic. }

1.8.4: Căutarea binară

/* cautare binara a unei inregistrari intr-un tablou ordonat de numere intregi */ //biblioteci necesare rularii programului #include<stdio.h> #include<conio.h>

Page 41: Cursul_6 Sortare-cautare

/* definirea unui tablou v de numere intregi de 100 de elemente */ int v[100]; /* definirea unei variabile de tip intreg i folosita ca contor in program */ int i; /* definirea inregistrarii vk ce urmeaza a fi cautata in tabloul v de numere intregi de n elemente */ int vk,n; /* definirea limitelor inferioare si superioare de tip intreg a tabloului */ int l,u; // partea principala a programului main() { /* definirea si deschiderea pentru citire a unui fisier f ce contine elementele tabloului ce urmeaza a fi initializat */ FILE *f=fopen("vector.in","r"); i=1; // initializarea contorului i cu valoarea 1 while (!feof(f)) /* atata timp cat nu am ajuns la sfarsitul fisierului f */ {

fscanf(f, "%d", &v[i]); /* citim cate un element din f si il punem pe pozitia i in tabloul v */ i=i+1; // incrementarea contorului

} fclose(f); // inchiderea fisierului f n=i-1; for(i=1;i<=n;i++) // pentru i de la 1 la n

/* afisam valoarea v[i] a elementului de pe pozitia i */ printf(" %d",v[i]);

// citirea inregistrarii in variabila vk printf("\n Introduceti inregistrarea de cautat:"); scanf("%d",&vk); l=1; //initializarea limitei inferioare cu 1 u=n; //initializarea limitei superioare cu n cautare:

if (u<l) /* daca limita superioara este mai mica decat cea inferioara */

Page 42: Cursul_6 Sortare-cautare

/* atunci afisam rezultatul negativ al cautarii lui vk in tabloul de intregi v */ printf("Inregistrarea %d Nu a fost gasita in tablou.",vk);

else //in caz contrar /* fixam pozitia elementului de comparat din tablou cu media celor doua limite */ i=(l+u)/2;

if (u>=l) /* daca limita superioara u este mai mare decat cea inferioara l */ {

if (vk<v[i]) /* daca inregistrarea de cautat vk este mai mica decat cea de pe pozitia i */

{ u=i-1;

/* atunci limita superioara u devine media celor doua limite -1*/

goto cautare; //se revine la partea de inceput

}; if (vk==v[i]) /* atunci afisam rezultatul pozitiv al cautarii lui vk in tabloul de intregi v */ printf("Inregistrarea %d a fost gasita in tablou pe pozitia %d.",vk,i); if (vk>v[i]) /* daca inregistrarea de cautat vk este mai mare decat cea de pe pozitia i */

{ l=i+1;

/* atunci limita inferioara l devine media celor doua limite +1 */

goto cautare; // se revine la partea de inceput

}; }; return 0; /* functia principala a programului nu returneaza nimic */ }

Page 43: Cursul_6 Sortare-cautare

1.8.5: Căutarea binară uniformă

/* cautare binara uniforma a unei inregistrari intr-un tablou ordonat de numere intregi */ //biblioteci necesare rularii programului #include<stdio.h> #include<conio.h> /* definirea unui tablou v de numere intregi de 100 de elemente */ int v[100]; /* definirea unor variabile de tip intreg i si m folosita ca contor in program */ int i,m; /* definirea inregistrarii vk ce urmeaza a fi cautata in tabloul v de numere intregi de n elemente */ int vk,n; /* definirea limitelor inferioare si superioare de tip intreg ale tabloului */ int l,u; // partea principala a programului main() { /* definirea si deschiderea pentru citire a unui fisier f ce contine elementele tabloului ce urmeaza a fi initializat */ FILE *f=fopen("vector.in","r"); i=1; //initializarea contorului i cu valoarea 1 while(!feof(f)) /* atata timp cat nu am ajuns la sfarsitul fisierului f */ {

/* citim cate un element din f si il punem pe pozitia i in tabloul v */ fscanf(f, "%d", &v[i]); i=i+1; //incrementarea contorului

} fclose(f); //inchiderea fisierului f n=i-1; for(i=1;i<=n;i++) // pentru i de la 1 la n

/* afisam valoarea v[i] a elementului de pe pozitia i */ printf(" %d",v[i]);

// citirea inregistrarii in variabila vk printf("\n Introduceti inregistrarea de cautat:");

Page 44: Cursul_6 Sortare-cautare

scanf("%d",&vk); /* initializarea pozitiilor i si m cu [n/2] (pozitia de mijloc a tabloului) */ i=(int)n/2; m=(int)n/2; cautare:

if (vk==v[i]) /* atunci afisam rezultatul pozitiv al cautarii lui vk in tabloul de intregi v */ printf("Inregistrarea %d a fost gasita in tablou pe pozitia %d.",vk,i); if (vk<v[i]) /* daca inregistrarea de cautat vk este mai mica decat cea de pe pozitia i */

{ if (m==0) // daca m este egal cu 0

/* atunci afisam rezultatul negativ al cautarii lui vk in tabloul de intregi v */ printf("Inregistrarea %d Nu a fost gasita in tablou.",vk);

else //daca m este diferit de 0 { // i ia valoarea i-[m/2]

i=i-((int)m/2); // m ia valoarea [m/2]

m=(int)m/2; goto cautare;

//se revine la partea de inceput } };

if (vk>v[i]) /* daca inregistrarea de cautat vk este mai mare decat cea de pe pozitia i */

{ if (m==0) //daca m este egal cu 0

/* atunci afisam rezultatul negativ al cautarii lui vk in tabloul de intregi v */ printf("Inregistrarea %d Nu a fost gasita in tablou.",vk);

else //daca m este diferit de 0 { i=i+((int)m/2); // i ia valoarea i+[m/2]

Page 45: Cursul_6 Sortare-cautare

m=(int)m/2; // m ia valoarea [m/2] goto cautare;

// se revine la partea de inceput } }; return 0; /* functia principala a programului nu returneaza nimic */ }

5.2.6 Căutarea de tip Fibonacci Metoda care urmează este preferabilă pentru implementarea pe calculator, deoarece ea presupune numai operaţii de adunare şi scădere, nu şi împărţiri. Mai jos este dat arborele Fibonacci de ordinul 6. În general, arborele Fibonacci de ordinul K are 11 −+KF noduri interne (circulare) şi 1+KF noduri externe (pătratice) şi este construit astfel: • dacă K = 0 sau K = 1, arborele este pur şi simplu ∅; • dacă 2≥K , rădăcina este KF : - subarborele din stânga este arborele Fibonacci de ordinul 1−K ; - subarborele din dreapta este arborele Fibonacci de ordinul 2−K cu toate numerele mărite cu KF .

Figura. 5.7 Arborele Fibonacci de ordinul 6

De notat că, cu excepţia nodurilor externe, numerele din cei doi descendenţi ai fiecărui nod intern diferă de numărul nodului părinte prin aceeaşi valoare şi această valoare este un număr Fibonacci. Astfel, din figură:

4 45 8 şi 11 8F F= − = + .

Page 46: Cursul_6 Sortare-cautare

Când diferenţa este jF , diferenţa Fibonacci corespunzătoare pentru următoarea ramificaţie la stânga este 1−jF , în timp ce pentru ramificaţia la dreapta este 2−jF . De exemplu:

.1110iar,53 23 FF −=−= Aceste observaţii conduc la următorul algoritm:

Algoritmul 5.2.6.1: Fiind dat un tabel de înregistrări 1,..., NR R ale căror chei sunt în ordine crescătoare NKK << ,,1 … , acest algoritm caută înregistrarea cu cheia dată K. Pentru uşurinţa calculelor, vom presupune că N + 1 este un număr Fibonacci 1KF + . Nu este dificil de observat că metoda lucrează corect pentru N arbitrar, dacă se realizează o iniţializare corespunzătoare. Pasul P1: (iniţializare): Se stabileşte

2

1

−←←←

K

K

K

FqFp

Fi

(p şi q vor reprezenta numere Fibonacci consecutive). Pasul P2: (compararea): Dacă iKK < , se trece la pasul P3. Dacă iKK > , se trece la pasul P4. Dacă iKK = , algoritmul se termină cu succes. Pasul P3: (descreşte i): Dacă q = 0, algoritmul se termină fără succes. În caz contrar, se stabileşte:

( ) ( )qpqqpqii

−←−←

,,

şi se revine la pasul P2. Pasul P4: (creşte i): Dacă p = 1 algoritmul se termină fără succes. În caz contrar se stabileşte:

pqqqpp

qii

−←−←

+←

şi se revine la pasul P2.

Page 47: Cursul_6 Sortare-cautare

//Programul 1 - Cautarea de tip Fibonacci /* biblioteci necesare rularii programului */ #include<stdio.h> #include<conio.h> int a[20],n,i,x; /* se construieste functia de cautare de tip Fibonacci */ int fibonacci_search(int x) {

int f1,f2,t,mid; /* variabilele f1,f2,t,mid seversc la calculul termenilor sirului Fibonacci */

f1=1; f2=1; while (f1<n)

{ f1=f1+f2; f2=f1-f2;

} f1=f1-f2; f2=f2-f1; mid=n-f1+1; while (x!=a[mid]) if (mid<0 || x>a[mid])

{ if (f1==1) return -1;

mid=mid+f2; f1=f1-f2; f2=f2-f1;

} else

{ if (f2==0) return -1; mid=mid-f2; t=f1-f2; f1=f2; f2=t; } return mid; } // programul principal void main() {

Page 48: Cursul_6 Sortare-cautare

printf("\n introduceti numarul de elemente n="); scanf("%d",&n); printf("\n Introduceti vectorul: \n"); for(i=1;i<=n;i++)

{ printf("\n a[%d",i,"]="); scanf("%d", &a[i]); }

printf("\n Valoarea care trebuie cautata: "); scanf("%d", &x); i=fibonacci_search(x); if(i!=-1)

printf("\n Valoarea se afla pe pozitia ",i);

else printf("\n Valoarea nu se afla in vector");

}

5.2.7 Căutarea prin interpolare Când se cunoaşte că K se găseşte între şi uK K se poate alege următorul element de comparare ca fiind cel ce se găseşte la circa ( ) ( )/ uK K K K− − distanţa între şi u, presupunând că cheile sunt numerice şi că ele cresc în general în interval în mod constant. Experimentele de simulare cu calculatorul arată următoarele: căutarea prin interpolare nu micşorează numărul de comparaţii suficient de mult pentru a compensa timpul de calcul suplimentar necesar în cazul căutării într-un tabel memorat într-o oarecare măsură. Metoda s-a dovedit eficientă numai într-o oarecare măsură, când căutarea a fost o căutare externă, folosind o unitate de memorie. În paragrafele anterioare s-a constatat că o structură implicită de arbore binar uşurează înţelegerea comportării căutării binare şi a căutării de tip Fibonacci. Metodele prezentate anterior sunt adecvate pentru tabelele de dimensiuni fixe, deoarece alocarea succesivă a înregistrărilor complică operaţiile de căutare şi ştergere.

Dacă tabelul se modifică dinamic, am pierde mai mult timp pentru întreţinerea lui decât am economisi în cazul căutării binare.

//Programul 2 - Cautarea prin interpolare #include<stdio.h> #include<conio.h>

Page 49: Cursul_6 Sortare-cautare

#include<math.h> // programul principal void main() {

int i,j,m,x,n,a[20]; short gasit; printf("\n Introduceti numarul de elemente ale vectorului n="); scanf("%d",&n); /* Atentie!!!! vectorul trebuie sa fie ordonat strict crescator caci altfel programul va da eroare */ printf("\n Introduceti vectorul sortat strict crescator: \n"); for(i=1;i<=n;i++)

{ printf("\n a[%d",i,"]="); scanf("%d", &a[i]); }

printf("\n Valoarea pe care o cautam este: "); scanf("%d", &x); i=1; j=n; gasit=0; while((i<=j)&&(gasit==0)) { m=i+(j-i)*(x-a[i])/(a[j]-a[i]); if(x>a[m])

i=m+1; else

if (x==a[m]) gasit=1;

else j=m;

} if(gasit==1)

printf("\n Valoarea %d", x," se afla pe pozitia %d", m);

else printf("\n Valoarea %d", x," nu se afla in vector ");

}

Page 50: Cursul_6 Sortare-cautare

5.2.8 Utilizarea unei structuri de arbore binar explicit Aceasta face posibilă inserarea şi ştergerea rapidă din înregistrări, precum

şi căutarea eficientă a tabelului. În consecinţă, dispunem de o metodă eficientă atât pentru căutare, cât şi pentru sortare. Acest câştig în flexibilitate se realizează prin adăugarea la fiecare înregistrare din tabel a două câmpuri suplimentare de legături. Algoritmul 5.2.8.1: Fiind dat un tabel de înregistrări care formează un arbore binar descris ca mai sus, algoritmul caută înregistrarea cu o anumită cheie K. Dacă K nu se găseşte în tabel, se inserează în arbore la locul potrivit un nod ce conţine cheia K. Presupunem că nodurile arborelui conţin cel puţin următoarele câmpuri: - CHEIE(P) = cheia înregistrării în NOD(P); - LLINK(P) = adresa de legătură la subarborele din stânga lui NOD(P); - RLINK(P) = adresa de legătură la subarborele din dreapta lui NOD(P). Subarborii noduri extreme (vizi) sunt reprezentaţi prin adresa de legătură nulă λ. Variabila ROOT face legătura la tulpina arborelui. Vom presupune că arborele nu este vid (ROOT ≠ λ). Pasul P1: (iniţializarea): Se stabileşte ROOTP ← (Variabila de legătură P se va deplasa de-a lungul arborelui). Pasul P2: (comparare): Dacă K < CHEIE(P), se trece la pasul P3. Dacă K > CHEIE(P), se trece la pasul P4. Dacă K = CHEIE(P), căutarea se termină cu succes.

Pasul P3: (deplasarea spre stânga): Dacă LLINK(P)≠λ se stabileşte P←LLINK(P) şi se revine la pasul P2.

În caz contrar, se trece la pasul P5. Pasul P4: (deplasare spre dreapta): Dacă RLINK(P)≠λ se stabileşte: P←RLINK(P) şi se revine la pasul P2. Altfel se trece la pasul P5. Pasul P5: (Căutarea este fără succes, se inserează K în arbore). Se stabileşte DISP⇔Q , care reprezintă adresa unui nou nod.

Se stabileşte: CHEIE(Q)←K; LLINK(Q)←λ; RLINK(Q)←λ. Dacă K < CHEIE(P), atunci LLINK(P)←Q. Dacă K > CHEIE(P), atunci RLINK(P)←Q.

Page 51: Cursul_6 Sortare-cautare

În acest moment se poate stabili P←Q şi algoritmul se termină cu succes. Schema logică ce corespunde acestui algoritm este dată în figura 5.8.

Figura 5.8. Schema corespunzătoare algoritmului ce utilizează structuri de arbore

binar explicit (adăugare)

Ştergeri d in arbori Uneori se doreşte să se şteargă una din poziţiile unui tabel memorat de un calculator. Este uşor de şters un nod-frunză (un nod ai căror subarbori sunt vizi) sau un nod la care LLINK = λ sau RLINK = λ .

Page 52: Cursul_6 Sortare-cautare

Algoritmul 5.2.8.2: Fie Q o variabilă care face referirea la un nod al unui arbore pentru căutare binară reprezentat ca şi în algoritmul anterior. Algoritmul următor realizează ştergerea acestui nod, păstrând arborele de căutare binară. În practică vom avea: fie Q = ROOT, fie Q = RLINK(P), fie Q = LLINK(P) al unui nod oarecare din arbore. Iată algoritmul: Pasul P1: (este RLINK nul?): Se stabileşte T←Q.

Dacă RLINK(T) = λ se stabileşte Q←LLINK(T) şi apoi se trece la pasul P4.

Pasul P2: (găsire succesor): Se stabileşte R←RLINK(T). Dacă LLINK(R) = λ se stabileşte LLINK(R)←LLINK(T) Q←P şi se trece la pasul P4. Pasul P3: (găsire LLINK nul)

Se stabileşte S←LLINK(R). Apoi dacă LLINK(S) ≠ λ se stabileşte R←S şi se repetă această secvenţă până când LLINK(S) = λ.

În sfârşit se stabileşte: LLINK(S)←LLINK(T) RLINK(R)←RLINK(S) RLINK(S)←RLINK(T) Q←S. Pasul P4: (eliberare nod): Se stabileşte DISP⇐T (nodul şters este returnat rezervei comune de locaţii de memorie).

Arborii binari au numeroase aplicaţii, una dintre cele mai reprezentative fiind reprezentarea expresiilor cu operatori binari. De asemenea, arborii binari sunt utilizaţi frecvent pentru reprezentarea mulţimilor de informaţii ale căror elemente pot fi regăsite prin intermediul unei chei unice. În aceste cazuri arborii sunt organizaţi astfel încât, pentru fiecare nod neterminal în parte, sunt respectate condiţiile din definiţia următoare.

Page 53: Cursul_6 Sortare-cautare

Definiţie. Fie nodul K un nod neterminal oarecare căruia îi este asociată informaţia Ka . Atunci: - cheile informaţiilor asociate nodurilor i din subarborele stâng al nodului K au valori i Ka a< , pentru orice nod ( ) ;Si S K∈ - cheile informaţiilor asociate nodurilor j din subarborele drept al lui K au valori j Ka a> , pentru orice nod ( )Dj S K∈ . Un astfel de arbore este denumit arbore de căutare. Un exemplu de arbore de căutare este dat mai jos:

Figura 5.9. Arbore de căutare

Procesul de eliminare a unui nod cu o cheie dată dintr-un arbore de

căutare nu este atât de simplu. Eliminarea nu trebuie făcută la întâmplare, ci trebuie să îndeplinească următoarele condiţii: a) să se menţină structura de arbore de căutare (deci, relaţiile de ordine între nodurile arborelui de căutare); b) să se evite degenerarea arborelui. Condiţia b) mai poate fi formulată astfel:

− fie A un arbore de căutare aleatoriu (construit în urma sosirii aleatorii a cheilor asociate nodurilor sale); − fie ( )if a probabilitatea ca un nod oarecare, cu cheia ia , din arborele A, să fie găsit în intervalul de timp T; − fie A′ arborele rezultat din A în urma inserării unui nod oarecare şi A″ arborele rezultat din A′ în urma eliminării unui nod oarecare; − fie ( )KQ a probabilitatea ca un nod oarecare cu cheia Ka din A″ să fie găsit în timpul T; − procesul de eliminare este optim dacă:

( ) ( ) ( ) ,i K i KP a Q a a A a A′′= ∀ ∈ ∈ .

Page 54: Cursul_6 Sortare-cautare

Figura 5.10. Eliminarea unui nod dintr-un arbore

Modul în care se realizează efectiv eliminarea unui nod dintr-un arbore de căutare depinde de numărul de descendenţi direcţi ai acestuia, existând trei situaţii posibile: 1) nodul care trebuie eliminat este un nod terminal; 2) nodul este neterminal, dar îi este ataşat un singur subarbore nevid (deci, are un singur descendent direct); 3) nodul are doi descendenţi direcţi.

Page 55: Cursul_6 Sortare-cautare

Figura. 5.11 Transformarea arborelui Observaţie. S-au făcut următoarele notaţii: • e . – referinţa la nodul care trebuie eliminat, • p . – referinţa la nodul predecesor; • ref . – referinţa la rădăcina arborelui care trebuie transformat. • .sr – referinţa la subarborele în care se caută nodul substitutor al

nodului care trebuie eliminat; • .cr – referinţa la celălalt subarbore;

Page 56: Cursul_6 Sortare-cautare

• .rp – referinţa la predecesorul nodului substitutor; • r . – referinţa la succesorul unui nod. Cele 3 cazuri de eliminare sunt ilustrate în figura 5.10. În figura 5.11 este prezentat un exemplu de transformare a arborelui

dominat de nodul care trebuie eliminat dintr-un arbore de căutare. Să demonstrăm că algoritmul de eliminare a unui nod dintr-un subarbore

de căutare păstrează structura de arbore de căutare. Fie Ka nodul care trebuie eliminat:

{ }jj sssd aaaaS şi,...,, 11 −

= ,

unde: ( )d Ka SD a= (SD: succesor direct dreapta);

1( )s Ka SS a= (SS: succesor direct stânga);

1

( ) , 2,...,j js sa SS a i j

−= = ;

( )jsSS a λ= .

Din cele de mai sus rezultă că ( ) Sa∈∀ sunt îndeplinite relaţiile:

( )

∈′′′∀<′′′

<

., 3Saaa

aa js

Deci ( )( )

∈′∀<′

≠′′∈′′∀′′<

.,

,,

1

2

Saaa

aaSaaa

j

jj

s

ss

/* Programul 3 - Stergeri din arbori de cautare binari */ #include<stdio.h> #include<conio.h> // se defineste structura nod struct nod

{ int inf; nod *ls,*ld;

}; nod *rad; // aceasta functie adauga un nod intr-un arbore void adauga(int x) {

nod *p,*t; p=rad;

Page 57: Cursul_6 Sortare-cautare

while(p!=NULL) if(p->inf<x)

{ t=p; p=p->ld; }

else {

t=p; p=p->ls; }

p=new(nod); p->inf=x; p->ls=NULL; p->ld=NULL; if(x<t->inf)

t->ls=p; else

t->ld=p; } // aceasta functie sterge un nod dintr-un arbore void sterge(int x) {

nod *t,*p,*q; if(rad->inf==x)

{ if((rad->ls==NULL)&&(rad->ld==NULL)) {

printf("\n arborele este vid");

delete(rad); rad=NULL; return;

} if(rad->ls==NULL)

{ p=rad; rad=rad->ld; delete(p); return; }

if(rad->ld==NULL) {

Page 58: Cursul_6 Sortare-cautare

p=rad; rad=rad->ls; delete(p); return; } q=rad->ls; t=rad; while(q->ld!=NULL)

{ t=q; q=q->ld; } rad->inf=q->inf; if(t->ls==q)

t->ls=q->ls; else

t->ld=q->ls; delete(q); return; } t=rad; p=rad; while((p->inf!=x)&&(p!=NULL)) if(p->inf<x)

{ t=p; p=p->ld; }

else {

t=p; p=p->ls; }

if(p==NULL) {

printf("\n Valoarea %d", x, " nu se afla in arbore");

return; } if((p->ls==NULL)&&(p->ld==NULL)) {

if(t->ls==p) t->ls=NULL;

else

Page 59: Cursul_6 Sortare-cautare

t->ld=NULL; delete(p); return;

} if(p->ls==NULL)

{ if (t->ls==p)

t->ls=p->ld; else

t->ld=p->ld; delete(p); return; } if(p->ld==NULL)

{ if (t->ls==p)

t->ls=p->ls; else

t->ld=p->ls; delete(p); return; } q=p->ls; t=p; while(q->ld!=NULL)

{ t=q; q=q->ld; }

p->inf=q->inf; if(t->ls==q)

t->ls=q->ls; else

t->ld=q->ls; delete(q); } void preordine(nod *p) {

if(p!=NULL) {

printf("\n %d", p->inf," "); preordine(p->ls); preordine(p->ld);

Page 60: Cursul_6 Sortare-cautare

} } void main() {

int x; /* !!Atentie. Se introduce arborele sub forma nod cu nod. */ printf("\n Introduceti valorile arborelui:"); printf("\n Introduceti primul nod al arborelui:"); rad=new(nod); scanf("%d",&x); rad->inf=x; rad->ls=NULL; rad->ld=NULL;

while(0==0) {

/* Atentie. Se adauga valori in arbore pana cand se tasteaza -1 (-1 indica terminarea nodurilor) */ cout<<"\nDaca doriti sa finalizati arborele tastati -1.\nDaca doriti sa continuati introduceti urmatorul nod: ";

scanf("%d",&x); if(x==-1) break; adauga(x); }

printf("\n Arborele binar de cautare parcurs in preordine este: "); preordine(rad); printf("\n valoarea pe care o stergeti din arborele binar de cautare: "); scanf("%d",&x); sterge(x); if(rad!=NULL)

{ printf("\n arborele binar de cautare obtinut este: ");

preordine(rad); } }