Sunteți pe pagina 1din 48

Structuri de date (în baza C++): Suport de curs

S.Pereteatcu, A.Pereteatcu

2.3. Tabele neordonate structurate arborescent


Câteodată în calitate de tabele temporare se aplică tabele neordonate structurate arborescent,
organizate în formă de arbore binar. La fiecare înregistrare tabelară în aşa tabele se adaugă câte doi
pointeri: un pointer spre înregistrarea tabelară cu valoarea mai mică a cheii, iar al doilea pointer
spre înregistrărea tabelară cu valoarea mai mare a cheii.
O nouă înregistrare se adaugă în tabel la rând, ca şi în tabele neordonate simple. După adăugarea în
tabel a noii înregistrări valoarea cheii se compară cu valoarea cheii a primei înregistrări a tabelului.
După rezultatul comparării cu ajutorul pointerilor se află adresa păstrării următoarei înregistrări, cu
cheia cărei trebuie de comparat valoarea cheii înregistrării noi. Acest proces are loc până atunci,
până când nu va fi găsită înregistrarea tabelară cu pointerul vid în direcţia necesară de căutare. În
acest pointer se înscrie adresa păstrării noii înregistrări tabelare.
De exemplu, în figura 2.1 este arătat tabelul neordonat structurat arborescent creat pe baza fişierului
Stud.txt.

Pentru a demonstra lucrul cu tabele neordonate structurate arborescent mai întâi de toate creăm în
baza clasei usual_elem clasa tree_like la care mai adaugăm două câmpuri cu caracter de
pointeri.
//
// c l a s s "t r e e l i k e" e l e m e n t s
//
class tree_like: public usual_elem
{
protected:
int less;
int greater;

public:
tree_like()
{
less=greater=-1;
}
tree_like(char* init_name, int init_year, double init_salary):
usual_elem(init_name, init_year, init_salary)
{
less=greater=-1;
}

int get_less()
{
return less;
}

20
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

00 Green 1987, 350


2 1

01 Red 1980, 450


4 5

02 Blue 1981, 500


9 3

03 Gray 1968, 900


6 -1

04 Orange 1984, 550


8 -1

05 White 1980, 600


-1 7

06 Cyan 1975, 800


-1 -1

07 Yellow 1988, 300


-1 -1

08 Magenta 1983, 600


-1 -1

09 Black 1981, 500


-1 -1

Fig. 2.1 Tabelul neordonat structurat arborescent

int set_less(int new_less)


{
return less=new_less;
}

int get_greater()

21
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

{
return greater;
}

int set_greater(int new_greater)


{
return greater=new_greater;
}

virtual void tree_show(const char* opening =NULL,


const char* ending=NULL)
{
if(!opening)
opening="";
if(!ending)
ending="\n";
printf(”%s”, opening);
usual_elem::show("", "");
printf(" [%4d, %4d]", less, greater);
printf(”%s”, ending);
}

virtual int fscanf_el(FILE *pf)


{
less=greater=-1;
return usual_elem::fscanf_el(pf);
}
};

Clasa tree_table declarăm ca clasa generică derivată de la clasa generică cunoscută table.
//
// C l a s s "t r e e _ t a b l e"
//
template <class el> class tree_table: public table<el>
{
public:
tree_table<el>(int NMAX=200): table<el>(NMAX)
{
}

tree_table<el>(char* file_name, int NMAX=200):


table<el>(file_name, NMAX)
{

22
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

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


{
int forward=1;
int j=0;
while(forward)
if(t[i]<t[j])
if(t[j].get_less()==-1)
t[j].set_less(i), forward=0;
else
j=t[j].get_less();
else
if(t[i]>t[j])
if(t[j].get_greater()==-1)
t[j].set_greater(i), forward=0;
else
j=t[j].get_greater();
}
}

virtual void tree_show(const char* opening =NULL,


const char* ending=NULL, int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
printf("%s", opening);
if(!ending)
ending="\n";
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp ==0)
{
printf("Press any key to continue...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1));
t[i].tree_show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");

23
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

getch();
}

int search(el e)
{
int position = -1;
int forward=1;
int i=0;
int cmp_result;

while(forward)
if(SD::ncomp++, (cmp_result=e.cmp(t[i]))==0)
position=i, forward=0;
else
{
if(cmp_result<0)
i=t[i].get_less();
else
i=t[i].get_greater();
if(i==-1)
forward=0;
}
return position;
}
};

Căutarea în tabelul arborescent după cheia dată are loc asemănător cu căutarea locului, în care se
înscrie adresa de păstrare.
Lungimea medie de căutare în tabelul arborescent depinde de ordinea înscrierii înregistrărilor la
încărcarea tabelului. În cel mai rău caz, când înregistrările veneau în ordine crescătoare (sau
descrescătoare) a cheilor, arborele va avea numai o ramură, şi lungimea medie de căutare rămâne
egală cu n/2, ca şi în cazul tabelelor neordonate. În cel mai bun caz, când ordinea înscrierii
înregistrărilor este aşa, că se primeşte un arbore binar simetric, lungimea căutării se micşorează
până la D2=[log2n+2].
În funcţia main() demonstrăm crearea şi utilizarea tabelelor nerdonate structurate arborescent.
void main()
{
clrscr();

tree_table<tree_like> tree_gr("stud.txt");
tree_gr.show("Group:\n", "");

24
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

tree_gr.tree_show("Group:\n", "");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
tree_like e(surname, 2000, 0.0);
tree_gr.reset_ncomp();
int pos=tree_gr.search(e);
if(pos<0)
{
printf("No table! ");
printf("The number of comparisons: %d\n", tree_gr.get_ncomp());
}
else
{
printf("There are in the position %d. ", pos+1);
printf("The number of comparisons: %d\n", tree_gr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n");
}
}

O variantă de afişare poate arăta astfel:

Group:
1. Green 1987 350.00
2. Red 1980 450.00
3. Blue 1981 500.00
4. Gray 1968 900.00
5. Orange 1984 550.00
6. White 1980 600.00
7. Cyan 1975 800.00
8. Yellow 1988 300.00
9. Magenta 1983 600.00
10. Black 1981 500.00
End of Table. Press any key ...

S-a afişat tabelul simplu neordonat. După ce vom apăsa orice tastă, ecranul va fi şters şi va apărea
afişarea următoarea:

25
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

Group:
1. Green 1987 350.00 [ 2, 1]
2. Red 1980 450.00 [ 4, 5]
3. Blue 1981 500.00 [ 9, 3]
4. Gray 1968 900.00 [ 6, -1]
5. Orange 1984 550.00 [ 8, -1]
6. White 1980 600.00 [ -1, 7]
7. Cyan 1975 800.00 [ -1, -1]
8. Yellow 1988 300.00 [ -1, -1]
9. Magenta 1983 600.00 [ -1, -1]
10. Black 1981 500.00 [ -1, -1]
End of Table. Press any key ...
Enter a name to search: White
There are in the position 6. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Green
There are in the position 1. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Black
There are in the position 10. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 3
Done ? (y/n)

Dacă vrem să calculăm lungimea medie de căutare pentru acest tabel, scriem funcţia main() astfel:
void main()
{
clrscr();

tree_table<tree_like> gr("stud.txt");
gr.tree_show("Group:\n","");

tree_like sample;
long NCOMP=0;

FILE* pf=fopen("Stud.txt", "rt");


while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
gr.reset_ncomp();
if(gr.search(sample)>=0)

26
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

NCOMP+=gr.get_ncomp();
}
fclose(pf);
printf("N=%d, NCOMP=%d, ALS=%.2lf", gr.get_n(), NCOMP,
(double)NCOMP/gr.get_n());
printf(", MAX=%.2lf, MIN=%.2lf\n", (gr.get_n()+1)/2.0,
log((double)gr.get_n())/log(2.0)+2.0);
getch();
}

Afişarea va arăta astfel:


Group:
1. Green 1987 350.00 [ 2, 1]
2. Red 1980 450.00 [ 4, 5]
3. Blue 1981 500.00 [ 9, 3]
4. Gray 1968 900.00 [ 6, -1]
5. Orange 1984 550.00 [ 8, -1]
6. White 1980 600.00 [ -1, 7]
7. Cyan 1975 800.00 [ -1, -1]
8. Yellow 1988 300.00 [ -1, -1]
9. Magenta 1983 600.00 [ -1, -1]
10. Black 1981 500.00 [ -1, -1]
End of Table. Press any key ...
N=10, NCOMP=29, ALS=2.90, MAX=5.50, MIN=5.32

Analiza rezultatelor rămâne ca exerciţiu.


Neajunsul tabelelor neordonate structurate arborescent – cheltuieli mai mari de memorie (pentru
păstrarea pointerilor) şi algoritmii de încărcare tabelului şi căutare înregistrărilor puţin mai
complicaţi în comparaţie cu cei pentru tabelele neordonate simple.
Exerciţii.
1. Supraîncărcaţi operatorul de inserţie în clasa tree_table.
2. Supraîncărcaţi operatorul de extragere în clasa tree_table.

27
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

2.4. Tabele ordonate


Tabele pot fi ordonate crescător după codul numeric al cheii, sau după frecvenţa apelurilor la
înregistrări. În primul caz pentru căutarea înregistrării de obicei se foloseşte căutarea binară, dar în
al doilea – parcurgerea secvenţială.
Căutarea binară constă în împărţirea tabelului în două părţi aproape egale şi constatarea în care din
cele două părţi se găseşte valoarea cheii căutată. Partea ce conţine cheia se supune de fiecare dată
împărţirii secvenţiale. Fiindcă tabelul este aranjat crescător după cheie, pentru a găsi în care parte se
află valoarea cheii căutate, valoarea cheii în punctul împărţirii se compară cu valoarea cheii căutate.
//
// C l a s s "s o r t e d t a b l e"
//
template <class el> class sorted_table: public table<el>
{
public:

sorted_table<el>(int NMAX=200): table<el>(NMAX)


{
}

sorted_table<el>(char* file_name, int NMAX=200): table<el>(NMAX)


{
el tmp;
SD::pf=fopen(file_name, "rt");
n=0;
while(!feof(SD::pf))
if(tmp.fscanf_el(SD::pf)>0)
{
int i;
for(i=n-1; i>=0 && (tmp<t[i]); i--)
t[i+1]=t[i];
if(n>0 && i>=0 && tmp==t[i])
{
char message[60];
char repeated_str[10];
message[0]='\0';
strcat(message,
"Key coincides with the key in the position: ");
strcat(message, itoa(i+1, repeated_str, 10));
strcat(message, "!\n");
error(message);
}

28
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

t[i+1]=tmp, n++;
}
fclose(SD::pf) , SD::pf=NULL;
}

// Cautarea binara
int search(el e)
{
int a=0, b=n-1;
int result;

while(a<b)
{
int i=(a+b)/2;
if(SD::ncomp++, (result=e.cmp(t[i]))>0)
a=i+1;
else
if(result<0)
b=i;
else
a=b=i;
}
return (SD::ncomp++, e==t[a])? a : -1;
}

// Cautarea prin metoda lui Fibonacci (Fibonaccian search)


int fibsearch(el e)
{
int result;
int j=1;
while(fib(j)<n+1)
j++;

int f1=fib(j-2);
int mid=n-f1+1;
int f2=fib(j-3);
int foward=1;

while((SD::ncomp++, result=e.cmp(t[mid-1])) && foward)


if(mid<=0 || result>1)
if(f1==1)
foward=0;

29
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

else
{
mid+=f2;
f1-=f2;
f2-=f1;
}
else
if(f2==0)
foward=0;
else
{
mid-=f2;
int t=f1-f2;
f1=f2;
f2=t;
}

return foward ? mid-1 : -1;


}

protected:
int fib(int n)
{
int res;
if(n==0 || n==1)
res=n;
else
if(n>=2)
res=fib(n-1)+fib(n-2);
return res;
}

};

În funcţia main() demonstrăm căutarea binară în tabelul sortat după cheie.


void main()
{
clrscr();

sorted_table<usual_elem> sorted_gr("stud.txt");
sorted_gr.show("Group:\n", "");

char ch='n';

30
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
usual_elem e(surname, 2000, 0.0);
sorted_gr.reset_ncomp();
int pos=sorted_gr.search(e);
if(pos<0)
{
printf("No table! ");
printf("The number of comparisons: %d\n", sorted_gr.get_ncomp());
}
else
{
printf("There are in the position %d. ", pos+1);
printf("The number of comparisons: %d\n", sorted_gr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n");
}
}

Afişarea va arăta astfel:


Group:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of Table. Press any key ...
Enter a name to search: White
There are in the position 9. The number of comparisons: 4
Done ? (y/n)
Enter a name to search: Green
There are in the position 5. The number of comparisons: 2
Done ? (y/n)

31
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

Enter a name to search: Black


There are in the position 1. The number of comparisons: 5
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 4
Done ? (y/n)*/

Pentru a demonstra căutarea prin metoda lui Fibonacci înlocuim în funcţia precedentă rândul
int pos=sorted_gr.search(e);

cu
int pos=sorted_gr.fibsearch(e);

Afişarea va arăta astfel:


Group:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of Table. Press any key ...
Enter a name to search: Black
There are in the position 1. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Blue
There are in the position 2. The number of comparisons: 4
Done ? (y/n)
Enter a name to search: Cyan
There are in the position 3. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Gray
There are in the position 4. The number of comparisons: 4
Done ? (y/n)
Enter a name to search: Green
There are in the position 5. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Magenta
There are in the position 6. The number of comparisons: 1
Done ? (y/n)

32
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

Enter a name to search: Orange


There are in the position 7. The number of comparisons: 4
Done ? (y/n)
Enter a name to search: Red
There are in the position 8. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: White
There are in the position 9. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Yellow
There are in the position 10. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 5
Done ? (y/n)

Lungimea căutării binare se obţine după formula


D2 log 2 n 2 (2.3),

ce pentru n destul de mare aproape că este egal cu limita de jos teoretică pentru metode de căutare,
bazate numai pe compararea cheilor. Teoretic limita de jos este egală cu log2(n+1). Căutarea binară
mult mai efectivă decât parcurgerea secvenţială. Pentru n=1000, D1=500, iar D2=11.
Pentru a calcula lungimea medie practică de căutare binară în tabelul ordonat, creat în baza
fişierului "stud.txt", modificăm funcţia main():
void main()
{
clrscr();

sorted_table<usual_elem> gr("stud.txt");
gr.show("Group:\n","");

usual_elem sample;
long NCOMP=0;

FILE* pf=fopen("Stud.txt", "rt");


while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
gr.reset_ncomp();
if(gr.search(sample)>=0)
NCOMP+=gr.get_ncomp();
}

33
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

fclose(pf);
printf("N=%d, NCOMP=%d, ALS=%.2lf", gr.get_n(), NCOMP,
(double)NCOMP/gr.get_n());
printf(", ALS_TEOR=%.2lf\n", log((double)gr.get_n())/log(2.0)+2.0);
getch();
}

Afişarea va arăta astfel:


Group:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of Table. Press any key ...
N=10, NCOMP=38, ALS=3.80, ALS_TEOR=5.32

Pentru a calcula lungimea medie practică de căutare prin metoda lui Fibonacci în tabelul ordonat,
creat în baza fişierului "stud.txt", înlocuim în funcţia main() precedentă rândul
if(gr.search(sample)>=0)

cu
if(gr.fibsearch(sample)>=0)

De data aceasta afişarea va arăta astfel:


Group:
1. Black 1981 500.00
2. Blue 1981 500.00
3. Cyan 1975 800.00
4. Gray 1968 900.00
5. Green 1987 350.00
6. Magenta 1983 600.00
7. Orange 1984 550.00
8. Red 1980 450.00
9. White 1980 600.00
10. Yellow 1988 300.00
End of Table. Press any key ...
N=10, NCOMP=29, ALS=2.90, ALS_TEOR=5.32

Analiza rezultatelor rămâne ca exerciţiu.

34
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

Lungimea medie a parcurgerii secvenţiale în tabel, ordonat după frecvenţa apelărilor la înregistrări,
esenţial depinde de repartizarea frecvenţilor apelurilor şi se obţine după formula generală
n
D i pi . Dacă un număr relativ mic de înregistrări se caută foarte des, atunci lungimea medie
i 1

de căutare poate fi mult mai mică decât la căutare binară. Aceasta uneori se foloseşte la crearea
tabelelor ale translatorului, de exemplu tabelul de cuvinte cheie ale limbajului de intrare.
Ordonarea tabelelor cere adăugător cheltuieli de timp al calculatorului. Deaceea tabelele ordonate se
folosesc mai mult ca tabele constante ale translatorului. Dar uneori se ordonează şi tabele
temporare, cu toate că ordonarea aceasta are anumite greutăţi. Problema constă în aceea că tabelele
temporare ce se alcătuiesc în timpul translaţiei în multe cazuri tot aici se folosesc şi pentru căutare.
Deja completate astfel de tabele au nevoie de verificare: oare nu este inclusă înregistrarea dată în
tabel la etapa precedentă de lucru al translatorului. De aceea ordonarea tabelelor temporare este
necesar de făcut odată cu încărcarea lor.
Pentru reducerea cheltuielilor timpului calculatorului la ordonarea tabelelor temporare uneori se
foloseşte metoda împărţirii, la care tabelul se împarte în compartimente, corespunzătoare
intervalelor diferite ale valorilor cheii. Compartimentele sunt ordonate, iar înăuntru
compartimentelor înregistrările nu se ordonează. Pentru căutarea înregistrărilor se foloseşte metoda
combinată. De exemplu, compartimentul se găseşte prin căutare binară, iar înăuntru
compartimentului se foloseşte parcurgerea secvenţială.
Exerciţii.
1. Supraîncărcaţi operatorul de inserţie în clasa sorted_table.
2. Supraîncărcaţi operatorul de extragere în clasa sorted_table.
3. Înlocuiţi în clasa sorted_table funcţia recursivă fib() cu o versiune iterativă.
4. Modificaţi în clasa sorted_table codul funcţiei de căutare prin metoda lui Fibonacci
fibsearch() ca ultima să nu utilizeze funcţia fib().

35
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

2.5. Tabele cu adresare directă


Fie în tabel de m înregistrări, toate înregistrările au diferite valori ale cheilor k0, k1, k2,..., km-1, şi
tabelul este reflectat în vectorul T[0], T[1], …, T[n-1], unde m≤n. Dacă este definită funcţia f(k),
astfel, ca pentru orice ki, i=0, ..., m-1, f(ki) are o valoare întreagă între 0 şi n-1, unde f(ki)≠f(kj), i≠j
atunci înregistrarea tabelară cu cheia K se reflectă bireciproc în elementul T[f(K)].
Funcţia f(K) se numeşte funcţie de repartizare (dispersare sau Hash funcţie). Această funcţie asigură
calcularea pentru fiecare înregistrare tabelară a numărului corespunzător al elementului vectorului
T. Accesul la înregistrare după cheia K se efectuează în acest caz nemijlocit prin calcularea valorii
f(K). Tabelele, pentru care există şi este cunoscută (descrisă) funcţia de repartizare, se numesc
tabele cu adresare directă. Lungimea medie de căutare în astfel de tabele este minimală şi egală
D3=1.
Alegerea funcţiei de repartizare, care asigură transformarea bireciprocă a cheii înregistrării în adresa
de păstrare, în caz general este o problemă destul de complicată. Practic ea poate fi rezolvată numai
pentru tabelele constante cu setul de valori pentru cheii ştiut dinainte. De exemplu: tabelul de
recodificare, tabelul de simboluri ale limbajului de intrare al translatorului.

36
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

2.6. Tabele de repartizare (repartizarea aleatorie)

Noţiuni generale

Deoarece, practic transformarea bireciprocă a cheii în adresa păstrării înregistrării, în mod general,
nu poate fi îndeplinită, atunci suntem nevoiţi să ne refuzăm de cerinţă de reflectare birereciprocă.
Aceasta aduce la suprapunerea înregistrărilor sau, altfel zis, la coliziuni. Ca astfel de coliziuni să fie
cât mai puţine, funcţia de repartizare se alege din condiţia de reprezentare aleatorie şi cu cât se
poate mai uniformă de reflectarea cheilor în adresa de păstrare. Tabele construite după acest
principiu sunt numite tabele de repartizare.
Repartizarea nu exclude pe deplin posibilitatea de suprapunere a înregistrărilor (coliziuni). De acea
se aplică diferite metode pentru înlăturarea coliziunilor. Diferenţa variantelor de tabele de
repartizare este definită prin metoda folosită de înlăturare a coliziunilor.

Tabele de repartizare cu examinarea liniară (adresare deschisă)

În metoda de repartizare cu examinarea liniară la reprezentarea tabelelor în vector de lungime n se


foloseşte următorul algoritm al inserării înregistrării cu cheia dată K:
1. Calculăm i=f(K). Trecem la punctul 2.
2. Dacă poziţia i este liberă, atunci înscriem în aceasta poziţie înregistrarea noua. În caz contrar
trecem la punctul 3.
i 1, daca i 1 n
3. Punem i=(i+1) mod n, şi trecem la punctul 2: i
0, daca i 1 n

La includerea unei noi înregistrări algoritmul rămâne determinat până atunci, când vectorul, în care
se reflectă tabelul, conţine măcar o poziţie liberă.
Dacă această condiţie nu se îndeplineşte este posibilă ciclarea, împotriva căreia trebuie de luat
măsuri speciale. De exemplu, se poate introduce un contor de poziţii verificate, dacă acest număr a
devenit mai mare ca n, atunci algoritmul trebuie să fie oprit.
La căutarea înregistrării cu cheia dată K se foloseşte următorul algoritm:
1. Calculăm i=f(K). Trecem la punctul 2
2. Dacă poziţia i este liberă atunci în tabelul dat nu există înregistrarea cu cheia K. Dacă poziţia
este ocupată şi cheia coincide cu cheia K, atunci căutarea este reuşită, în caz contrar trecem la
punctul 3.
i 1, daca i 1 n
3. Punem i=(i+1) mod n, şi trecem la punctul 2: i
0, daca i 1 n

La căutare algoritmul este determinat dacă tabelul conţine înregistrarea tabelară cu cheia K, sau
vectorul conţine poziţii libere. În cazul neîndeplinirii acestor condiţii trebuie de luat măsuri
speciale. De exemplu, se poate introduce un contor de poziţii verificate, dacă acest număr a devenit
mai mare ca n, atunci algoritmul trebuie să fie oprit.

37
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

Declarăm în baza clasei usual_elem clasa hashing_elem care va fi dotată cu o funcţie de


repartizare.
//
// C l a s s "h a s h i n g _ e l e m"
//
class hashing_elem : public usual_elem
{
public:
hashing_elem()
{
}

hashing_elem(char* init_name, int init_year, double init_salary):


usual_elem(init_name, init_year, init_salary)
{
}

int hf(int n) // hashing function


{
return (name[0]-'A')%n;
}
};

În exemplul acesta valoarea funcţiei de dispersare este egală cu numărul de ordine al primei litere a
numelui în alfabet. Numărul de ordin se modulează după mărimea vectorului de reprezentare.
O altă versiune a funcţiei hf(), mai complicată, poate fi scrisă astfel:
int hf(int n) // hashing function
{
int sum=0;
for(int i=0; i<4 && i<strlen(name); i++)
sum+=*(unsigned char*)(name+i);
return sum%n;
}

Exerciţii.
1. Supraîncărcaţi operatorul de inserţie în clasa hashing_elem.
2. Supraîncărcaţi operatorul de extragere în clasa hashing_elem.

În baza clasei generice table declarăm clasa hashing_table tot generică, care va asigura lucrul cu
tabele de repartizare cu examinarea lineară.
//
// C l a s s "h a s h i n g t a b l e"

38
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

//
template<class el> class hashing_table: public table<el>
{
protected:
int m;

public:
hashing_table<el>(char* file_name, int NMAX=200): table<el>(NMAX)
{
n=NMAX, m=0;
el tmp;
int repeated;
SD::pf=fopen(file_name, "rt");
m=0;
while(!feof(SD::pf))
if(tmp.fscanf_el(SD::pf)>0)
{
int i=tmp.hf(n);
repeated=-1;
while((repeated==-1) && !t[i].free())
{
if(tmp==t[i])
repeated=i;
else
i=(i+1)%n;
}
if(repeated!=-1)
{
char message[60];
char repeated_str[10];
message[0]='\0';
strcat(message,
"Key coincides with the key in the position: ");
strcat(message, itoa(repeated+1, repeated_str, 10));
strcat(message, "!\n");
error(message);
}
t[i]=tmp;
m++;
}
fclose(SD::pf), SD::pf=NULL;
}

39
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

int search(el e)
{
int position=-1;
int i=e.hf(n);
while((position==-1) && !t[i].free())
if(SD::ncomp++, e==t[i])
position=i;
else
i=(i+1)%n;
return position;
}

int get_m()
{
return m;
}
};

În funcţia main()creăm un tabel de repartizare cu examinarea lineară, încărcând acest tabel din
fişierul text Stud.txt. Apoi afişăm acest tabel şi căutăm careva înregistrări după cheie.
void main()
{
clrscr();
hashing_table<hashing_elem> hashing_gr("stud.txt", 15);
hashing_gr.show("Group:\n","");

char ch='n';
char surname[21];
while(ch!='y')
{
cout << "Enter a name to search: ";
cin >> surname;
hashing_elem e(surname, 2000, 0.0);
hashing_gr.reset_ncomp();
int pos=hashing_gr.search(e);
if(pos<0)
cout << "No table! " << "The number of comparisons: "
<< hashing_gr.get_ncomp() << "\n";
else
cout << "There are in the position " << (pos+1)
<< ". The number of comparisons: "
<< hashing_gr.get_ncomp() << "\n";

40
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

cout << "Done ? (y/n) ";


ch = getch();
cout << endl;
}
}

O variantă de afişare poate arăta astfel:


Group:
1.
2. Blue 1981 500.00
3. Red 1980 450.00
4. Cyan 1975 800.00
5. Black 1981 500.00
6.
7. Green 1987 350.00
8. Gray 1968 900.00
9. White 1980 600.00
10. Yellow 1988 300.00
11.
12.
13. Magenta 1983 600.00
14.
15. Orange 1984 550.00
End of Table. Press any key ...
Enter a name to search: White
There are in the position 9. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Green
There are in the position 7. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Black
There are in the position 5. The number of comparisons: 4
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 0
Done ? (y/n)

41
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

Pentru exemplul nostru tabelul va arăta astfel:

00│
01│ Blue, 1981, 500
02│ Red, 1980, 450
”Green”
03│ Cyan, 1975, 800
”Red”
04│ Black, 1981, 500
”Blue”
05│
”Gray”
06│ Green, 1987, 350
”Orange”
07│ Gray, 1968, 900
”White”
08│ White, 1980, 600
”Cyan”
09│ Yellow, 1988, 300
”Yellow”
10│
”Magenta”
11│
”Black”
12│ Magenta,1983, 600
13│
14│ Orange, 1984, 550

După cum se vede poziţiile 0, 5, 10, 11, 13 au rămas libere. Pe parcursul completării tabelului în
poziţiile 1, 2, 6, 7 au avut loc coliziuni. Drept vorbind, coliziunea în poziţia 7 pentru înregistrarea
tabelară cu cheia White este secundară.
Cercetările teoretice şi experimente pentru căutarea la metoda de repartizare cu examinarea liniară
au arătat, că pentru repartizarea aleatorie şi uniformă a înregistrărilor prin funcţia de repartizare în
intervalul [0, n-1], lungimea medie de căutare nu depinde de lungimea tabelului, dar depinde numai
m
de factorul de încărcare , unde m - lungimea tabelului, iar n – lungimea vectorului de
n
reprezentare.
Această proprietate este foarte importantă, mai ales pentru tabele mari. Tabele deterministe, atât
aranjate cât şi cele nearanjate nu posedă această proprietate. În tabele deterministe lungimea medie
de căutare creşte cu creşterea lungimii tabelului.
2
Formula aproximativă D( ) , pentru lungimea medie de căutare la metoda de repartizare
2 2
cu examinarea liniară oferă coincidenţă suficientă cu experimentul pentru σ≤0,85.
Formula este obţinută în presupunerea repartizării aleatorie şi uniformă a înregistrărilor pe poziţiile
vectorului de reprezentare.
Următorul tabel demonstrează dependenţa lungimii medie de căutare de factorul de încărcare σ.
σ 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9
D(σ) 1.06 1.13 1.21 1.33 1.50 1.75 2.17 3.00 5.5

42
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

Experimentele arată, că pentru σ=1 lungimea medie de căutare nu întrece 20.


Dacă distribuirea înregistrărilor pe poziţiile vectorului de reflectare nu este uniformă, atunci
numerele din tabel cresc în mediu cu 1.5 – 2 ori. Chiar şi cu astfel de corecţii repartizarea cu
examinarea lineară are lungimea medie de căutare mai mică, ca alte metode. De exemplu, pentru
tabelul din 400 înregistrări şi lungimea vectorului de reflectare 512, lungimea medie de căutare
pentru tabelul de repartizare cu examinarea liniară, luând în consideraţie distribuirea neuniformă a
înregistrărilor, nu depăşeşte 4.5 – 6. În aceleaşi condiţii lungimea medie la căutarea binară este
egală cu 10, iar lungimea medie a parcurgerii secvenţiale – 200.
Neajunsurile: la repartizarea cu examinarea liniară aproape 20% de memorie, rezervată pentru tabel,
nu se foloseşte.
Pentru a calcula lungimea medie de căutare pentru tabelul cu repartizare lineară din exemplul
nostru, cercetăm următoarea funcţia main():
void main()
{
clrscr();

hashing_table<hashing_elem> hashing_gr("stud.txt", 15);


hashing_gr.show("Group:\n","");

hashing_elem sample;
long NCOMP=0;

FILE* pf=fopen("Stud.txt", "rt");


while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
hashing_gr.reset_ncomp();
if(hashing_gr.search(sample)>=0)
NCOMP+=hashing_gr.get_ncomp();
}
fclose(pf);
printf("m=%d, n=%d, NCOMP=%d, ALS=%.2lf", hashing_gr.get_m(),
hashing_gr.get_n(), NCOMP, (double)NCOMP/hashing_gr.get_m());
double sigma = (double)hashing_gr.get_m()/hashing_gr.get_n();
printf(", m/n=%.2lf, D(m/n)=%.2lf\n", sigma, (2.-sigma)/(2.-2.*sigma));
getch();
}

Afişarea este următoarea:


Group:
1.
2. Blue 1981 500.00

43
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

3. Red 1980 450.00


4. Cyan 1975 800.00
5. Black 1981 500.00
6.
7. Green 1987 350.00
8. Gray 1968 900.00
9. White 1980 600.00
10. Yellow 1988 300.00
11.
12.
13. Magenta 1983 600.00
14.
15. Orange 1984 550.00
End of Table. Press any key ...
m=10, n=15, NCOMP=16, ALS=1.60, m/n=0.67, D(m/n)=2.00

Analiza rezultatelor rămâne ca exerciţiu.


Exerciţii.
1. Supraîncărcaţi operatorul de inserţie în clasa hashing_table.
2. Supraîncărcaţi operatorul de extragere în clasa hashing_table.
3. Găsiţi o funcţie de repartizare mai potrivită.

Tabele de repartizare cu înlănţuirea externă (repartizarea deschisă, înlănţuirea separată)

În metoda examinării liniare înregistrările ce produc coliziuni se includ în poziţiile libere aceluiaşi
vector de reflectare. Însă pentru aceste înregistrări se poate crea un tabel aparte. În tabelul adăugător
înregistrările se poate lega în lanţ, ca în liste, pentru uşurarea căutării.

44
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

Primary Secondary
Table Table
00│ │ 00|Gray, 1968,900│
01│Blue, 1981,500│02 01│Cyan, 1975,800│
02│Red, 1980,450│01 02│Black, 1981,500|
”Green”
03│ │ 03│ │
”Red”
04│ │ 04│ │
”Blue”
05│ │ 05│ │
”Gray”
06│Green, 1987,350│00 06│ │
”Orange”
07│White, 1980,600│ 07│ │
”White”
08│ │ 08│ │
”Cyan”
09│Yellow,1988,300│ 09│ │
”Yellow”
10│ │ 10│ │
”Magenta”
11│ │ 11│ │
”Black”
12│Magenta,1983,600│ 12│ │
13│ │ 13│ │
14│Orange,1984,550│ 14│ │

În tabele de repartizare cu înlănţuirea externă lungimea medie de căutare pentru distribuirea


uniformă şi aleatorie a înregistrărilor se defineşte după formula:

m 1
D m, n 1 , n – lungimea vectorului de reflectare, m – lungimea tabelului.
2n
Pentru a demonstra lucru cu tabele de repartizare cu înlănţuirea externă, în primul rând să declarăm
clasa hashing_linked_elem ca clasa derivată de la clasa usual_elem şi dotată cu câmpul next,
pentru a crea lanţuri de legături.
//
// C l a s s "h a s h i n g _ l i n k e d _ e l e m"
//
class hashing_linked_elem: public usual_elem
{
protected:
int next;
public:
hashing_linked_elem()
{
next = -1;
}

45
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

hashing_linked_elem(char* init_name, int init_year, double init_salary):


usual_elem(init_name, init_year, init_salary)
{
next = -1;
}

virtual void hashing_linked_show(const char* opening=NULL,


const char* ending=NULL)
{
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
usual_elem::show("", "");
if(!free())
printf(" [%4d]", next);
printf("%s", ending);
}

int hf(int n) // hashing function


{
return (name[0]-'A')%n;
}

int get_next()
{
return next;
}

int set_next(int new_next)


{
return next=new_next;
}
};

Exerciţii.
1. Supraîncărcaţi operatorul de inserţie în clasa hashing_linked_elem.
2. Supraîncărcaţi operatorul de extragere în clasa hashing_linked_elem.

Apoi, pe baza clasei abstracte SD, să declarăm clasa generică extern_hashing_table, care na va
da posibilitatea de a crea tabele de repartizare cu înlănţuirea externă.

46
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

//
// C l a s s "e x t e r n _ h a s h i n g _ t a b l e"
// m/n
template <class el> class extern_hashing_table: public SD
{
protected:
int n;
int m;
el *t;
el *v;

public:
extern_hashing_table<el>(char* file_name, int init_n=0): SD(file_name)
{
n=init_n;
if(n<=0)
n=countn();
t=new el[n];
v=new el[n];
m=0;
el tmp;
int repeated, position;
while(!feof(SD::pf))
if(tmp.fscanf_el(SD::pf)>0)
{
int i=tmp.hf(n);
if(t[i].free())
{
t[i]=tmp;
m++;
}
else
{
repeated=-1;
if( tmp==t[i] )
{
repeated=i;
t[i].show("", " !!!\n");
}
else
{
if(t[i].get_next()==-1)

47
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

{
int j=0;
while(!v[j].free())
j++;
t[i].set_next(j);
v[j]=tmp;
m++;
}
else
{
i=t[i].get_next();
position=-1;
while((repeated==-1) && position==-1)
{
if( tmp==v[i] )
{
repeated=i;
v[i].show("", " !!!\n");
}
else
if(v[i].get_next()==-1)
{
position=i+1;
while(!v[position].free())
position++;
v[i].set_next(position);
v[position]=tmp;
m++;
}
else
i=v[i].get_next();
}
}
}
if ( repeated!=-1 )
{
char message[60];
char repeated_str[10];
message[0]='\0';
//strcat(message, "Key coincides with the key in the position: ");
//strcat(message, itoa(repeated+1, repeated_str, 10));
//strcat(message, "!\n");

48
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

strcat(message, "Key coincides !!!\n");


error(message);
}
}
}
fclose(SD::pf), SD::pf=NULL;
}

virtual void show(const char* opening=NULL, const char* ending=NULL,


int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)
{
printf("Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}

virtual void primary_show(const char* opening=NULL, const char* ending=NULL,


int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);

49
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

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


{
if(i>0 && i% nlinepp==0)
{
printf("%s", "Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].hashing_linked_show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}

virtual void secondary_show(const char* opening=NULL,


const char* ending=NULL, int nlinepp=20)
{
//clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)
{
printf("%s", "Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); v[i].hashing_linked_show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}

int search(el e)

50
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

{
int position=-1;
int i=e.hf(n);
if(!t[i].free())
if(SD::ncomp++, e==t[i])
position=i;
else
if((i=t[i].get_next())!=-1)
do
{
if(SD::ncomp++, e==v[i])
position=i;
else
i=v[i].get_next();
}
while((position==-1) && (i!=-1));
return position;
}

int get_n()
{
return n;
}

int get_m()
{
return m;
}

protected:
int countn()
{
return 200;
}
};

În funcţia main() să creăm pe baza fişierului stud.txt un tabel de repartizare cu înlănţuirea


externă pentru n=15 şi să demonstrăm căutarea elementelor.
void main()
{
clrscr();

extern_hashing_table<hashing_linked_elem> ex_hashing_gr("stud.txt", 15);

51
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

ex_hashing_gr.primary_show("Primary table:\n","");
ex_hashing_gr.secondary_show("Secondary table:\n","");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
hashing_linked_elem e(surname, 2000, 0.0);
ex_hashing_gr.reset_ncomp();
int pos=ex_hashing_gr.search(e);
if(pos<0)
{
printf("No table! ");
printf("The number of comparisons: %d\n", ex_hashing_gr.get_ncomp());
}
else
{
printf("There are in the position %d. ", pos+1);
printf("The number of comparisons: %d\n", ex_hashing_gr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n"); }
}

O variantă de afişare poate arăta astfel:


Primary table:
1.
2. Blue 1981 500.00 [ 2]
3. Red 1980 450.00 [ 1]
4.
5.
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.
12.
13. Magenta 1983 600.00 [ -1]
14.

52
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

15. Orange 1984 550.00 [ -1]


End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ -1]
3. Black 1981 500.00 [ -1]
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
End of Table. Press any key ...
Enter a name to search: White
There are in the position 8. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Green
There are in the position 7. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Black
There are in the position 3. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 0
Done ? (y/n)

Pentru n=10 afişarea va fi:


Primary table:
1.
2. Blue 1981 500.00 [ 4]
3. White 1980 600.00 [ 1]
4.
5. Orange 1984 550.00 [ 2]
6.
7. Green 1987 350.00 [ 0]
8. Red 1980 450.00 [ -1]
9.

53
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

10.
End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ 3]
3. Yellow 1988 300.00 [ -1]
4. Magenta 1983 600.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7.
8.
9.
10.
End of Table. Press any key ...
Enter a name to search: Gray
There are in the position 1. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Blue
There are in the position 2. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Magenta
There are in the position 4. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 0
Done ? (y/n)

Pentru a calcula lungimea medie de căutare modificăm funcţia main().


void main()
{
clrscr();

extern_hashing_table<hashing_linked_elem> ex_hashing_gr("stud.txt", 15);


ex_hashing_gr.primary_show("Primary table:\n","");
ex_hashing_gr.secondary_show("Secondary table:\n","");

hashing_linked_elem sample;
long NCOMP=0;

FILE* pf=fopen("Stud.txt", "rt");


while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{

54
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

ex_hashing_gr.reset_ncomp();
if(ex_hashing_gr.search(sample)>=0)
NCOMP+=ex_hashing_gr.get_ncomp();
}
fclose(pf);
printf("m=%d, n=%d, NCOMP=%d, ALS=%.2lf", ex_hashing_gr.get_m(),
ex_hashing_gr.get_n(), NCOMP, (double)NCOMP/ex_hashing_gr.get_m());
printf(", D(m/n)=%.2lf\n",
1.+(ex_hashing_gr.get_m()-1.)/(2.*ex_hashing_gr.get_n()));

getch();
}

Pentru n=15 rezultatul va fi:


Primary table:
1.
2. Blue 1981 500.00 [ 2]
3. Red 1980 450.00 [ 1]
4.
5.
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.
12.
13. Magenta 1983 600.00 [ -1]
14.
15. Orange 1984 550.00 [ -1]
End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ -1]
3. Black 1981 500.00 [ -1]
4.
5.
6.
7.
8.
9.
10.
11.

55
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

12.
13.
14.
15.
End of Table. Press any key ...
m=10, n=15, NCOMP=13, ALS=1.30, D(m/n)=1.30

Iar pentru n=10 rezultatul va fi:


Primary table:
1.
2. Blue 1981 500.00 [ 4]
3. White 1980 600.00 [ 1]
4.
5. Orange 1984 550.00 [ 2]
6.
7. Green 1987 350.00 [ 0]
8. Red 1980 450.00 [ -1]
9.
10.
End of Table. Press any key ...
Secondary table:
1. Gray 1968 900.00 [ -1]
2. Cyan 1975 800.00 [ 3]
3. Yellow 1988 300.00 [ -1]
4. Magenta 1983 600.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7.
8.
9.
10.
End of Table. Press any key ...
m=10, n=10, NCOMP=16, ALS=1.60, D(m/n)=1.45

Exerciţii.
1. Supraîncărcaţi operatorul de inserţie în clasa extern_hashing_table.
2. Supraîncărcaţi operatorul de extragere în clasa extern_hashing_table.

56
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

Tabele de repartizare cu înlănţuirea internă

Încărcarea poziţiilor vectorului de reflectare constă din două etape:


prima etapă se aseamănă cu repartizarea cu înlănţuirea externă;
a doua etapă se îndeplineşte după terminarea creării tabelului primar şi celui secundar. Ea constă
în mutarea lanţurilor din tabelul secundarr în poziţiile libere ale tabelului primar.
Astfel tabelele din exemplul precedent vor fi transformate în următorul tabel:

00│Black, 1981,500│
01│Blue, 1981,500│00
02│Red, 1980,450│03
”Green”
03│Cyan, 1975,800│
”Red”
04│Gray, 1968,900│
”Blue”
05│ │
”Gray”
06│Green, 1987,350│04
”Orange”
07│White, 1980,600│
”White”
08│ │
”Cyan”
09│Yellow,1988,300│
”Yellow”
10│ │
”Magenta”
11│ │
”Black”
12│Magenta,1983,600│
13│ │
14│Orange,1984,550│

Prioritatea acestei metode în comparaţie cu precedenta – economie de memorie, dar neajunsul –


flexibilitatea mică şi algoritmul de încărcare a tabelului este mai complicat.
m 1
Lungimea medie de căutare aici se defineşte după aceiaşi formula: D(m, n) 1 . Această
2n
metodă se foloseşte pentru tabele permanente, şi pentru tabele temporare, care se încărcă la prima
etapă, dar se folosesc la alta.
Pentru tabele constante înregistrările cel mai des folosite se înscriu primele, atunci accesările
lanţurilor interioare sunt rare, ce micşorează lungimea medie de căutare. Este caracter, că la
folosirea lanţurilor interioare toate poziţiile ale vectorului de reflectare pot fi încărcate (adică m=n),
dar lungimea medie de căutare pentru repartizarea uniformă şi aleatorie a înregistrărilor nu întrece
1.5.

57
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

Pentru a demonstra lucru cu tabele de repartizare cu înlănţuirea internă să declarăm pe baza clasei
abstracte SD clasa generică extern_hashing_table, care na va da posibilitatea de a crea tabele de
repartizare cu înlănţuirea internă.
//
// C l a s s "i n t e r n _ h a s h i n g _ t a b l e"
// m/n
template <class el> class intern_hashing_table: public SD
{
protected:
int n;
int m;
el *t;
el *v;

public:
intern_hashing_table<el>(char* file_name, int init_n=0): SD(file_name)
{
n=init_n;
if(n<=0)
n=countn();
t=new el[n];
v=new el[n];
m=0;
el tmp;
int i, j;
int repeated, position;
while(!feof(SD::pf))
if(tmp.fscanf_el(SD::pf)>0)
{
i=tmp.hf(n);
if(t[i].free())
{
t[i]=tmp;
m++;
}
else
{
repeated=-1;
if( tmp==t[i] )
{
repeated=i;
t[i].show("", " !!!\n");

58
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

}
else
{
if(t[i].get_next()==-1)
{
j=0;
while(!v[j].free())
j++;
t[i].set_next(j);
v[j]=tmp;
m++;
}
else
{
i=t[i].get_next();
position=-1;
while((repeated==-1) && position==-1)
{
if( tmp==v[i] )
{
repeated=i;
v[i].show("", " !!!\n");
}
else
if(v[i].get_next()==-1)
{
position=i+1;
while(!v[position].free())
position++;
v[i].set_next(position);
v[position]=tmp;
m++;
}
else
i=v[i].get_next();
}
}
}
if ( repeated!=-1 )
{
char message[60];
char repeated_str[10];

59
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

message[0]='\0';
//strcat(message, "Key coincides with the key in the position: ");
//strcat(message, itoa(repeated+1, repeated_str, 10));
//strcat(message, "!\n");
strcat(message, "Key coincides !!!\n");
error(message);
}
}
}

fclose(SD::pf), SD::pf=NULL;

el empty;
int k=0;
for(j=0; j<n; j++)
if(!v[j].free())
{
i=v[j].hf(n);
int j1=j;
do
{
while(!t[k].free())
k++;
t[i].set_next(k);
i=k;
t[i]=v[j1];
t[i].set_next(-1);
int jtmp=j1;
j1=v[j1].get_next();
v[jtmp]=empty;
}
while (j1!=-1);
}

delete v;
}

virtual void show(const char* opening=NULL, const char* ending=NULL,


int nlinepp=20)
{
clrscr();
if(!opening)

60
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i% nlinepp==0)
{
printf("Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");
getch();
}

virtual void primary_show(const char* opening=NULL, const char* ending=NULL,


int nlinepp=20)
{
clrscr();
if(!opening)
opening="";
if(!ending)
ending="\n";
printf("%s", opening);
for(int i=0; i<n; i++)
{
if(i>0 && i%nlinepp==0)
{
printf("%s", "Press any key to continue ...\n");
getch();
clrscr();
printf("%s", opening);
}
printf("%4d. ", (i+1)); t[i].hashing_linked_show();
}
printf("%s", ending);
printf("End of Table. Press any key ...\n");

61
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

getch();
}

int search(el e)
{
int position=-1;
int i=e.hf(n);
if(!t[i].free())
do
{
if(SD::ncomp++, e==t[i])
position=i;
else
i=t[i].get_next();
}
while((position==-1) && (i!=-1));
return position;
}

int get_n()
{
return n;
}

int get_m()
{
return m;
}

protected:
int countn()
{
return 200;
}
};

În funcţia main() să creăm pe baza fişierului stud.txt un tabel de repartizare cu înlănţuirea


internă pentru n=15 şi să demonstrăm căutarea elementelor.
void main()
{
clrscr();

intern_hashing_table<hashing_linked_elem> in_hashing_gr("stud.txt", 15);

62
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

in_hashing_gr.primary_show("Primary table:\n","");

char ch='n';
char surname[21];
while(ch!='y')
{
printf("Enter a name to search: ");
scanf("%s", surname);
hashing_linked_elem e(surname, 2000, 0.0);
in_hashing_gr.reset_ncomp();
int pos=in_hashing_gr.search(e);
if(pos<0)
{
printf("No table! ");
printf("The number of comparisons: %d\n", in_hashing_gr.get_ncomp());
}
else
{
printf("There are in the position %d. ", pos+1);
printf("The number of comparisons: %d\n", in_hashing_gr.get_ncomp());
}
printf("Done ? (y/n) ");
ch = getch();
printf("\n");
}
}

O variantă de afişare poate arăta astfel:


Primary table:
1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 4]
3. Red 1980 450.00 [ 3]
4. Cyan 1975 800.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.
12.
13. Magenta 1983 600.00 [ -1]
14.

63
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

15. Orange 1984 550.00 [ -1]


End of Table. Press any key ...
Enter a name to search: White
There are in the position 8. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Green
There are in the position 7. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Black
There are in the position 5. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 1
Done ? (y/n)

Pentru n=10 afişarea va fi:


Primary table:
1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 9]
3. White 1980 600.00 [ 3]
4. Cyan 1975 800.00 [ 5]
5. Orange 1984 550.00 [ 8]
6. Magenta 1983 600.00 [ -1]
7. Green 1987 350.00 [ 0]
8. Red 1980 450.00 [ -1]
9. Yellow 1988 300.00 [ -1]
10. Black 1981 500.00 [ -1]
End of Table. Press any key ...
Enter a name to search: Gray
There are in the position 1. The number of comparisons: 2
Done ? (y/n)
Enter a name to search: Blue
There are in the position 2. The number of comparisons: 1
Done ? (y/n)
Enter a name to search: Magenta
There are in the position 6. The number of comparisons: 3
Done ? (y/n)
Enter a name to search: Purple
No table! The number of comparisons: 1
Done ? (y/n)

Pentru a calcula lungimea medie de căutare modificăm funcţia main().


void main()
{

64
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

clrscr();

intern_hashing_table<hashing_linked_elem> in_hashing_gr("stud.txt", 15);


// intern_hashing_table<hashing_linked_elem> in_hashing_gr("stud.txt", 10);
in_hashing_gr.primary_show("Primary table:\n","");

hashing_linked_elem sample;
long NCOMP=0;

FILE* pf=fopen("Stud.txt", "rt");


while(!feof(pf))
if(sample.fscanf_el(pf)>0)
{
in_hashing_gr.reset_ncomp();
if(in_hashing_gr.search(sample)>=0)
NCOMP+=in_hashing_gr.get_ncomp();
}
fclose(pf);
printf("m=%d, n=%d, NCOMP=%d, ALS=%.2lf", in_hashing_gr.get_m(),
in_hashing_gr.get_n(), NCOMP, (double)NCOMP/in_hashing_gr.get_m());
printf(", D(m/n)=%.2lf\n",
1.+(in_hashing_gr.get_m()-1.)/(2.*in_hashing_gr.get_n()));

getch();
}

Pentru n=15 afişarea va fi:


Primary table:
1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 4]
3. Red 1980 450.00 [ 3]
4. Cyan 1975 800.00 [ -1]
5. Black 1981 500.00 [ -1]
6.
7. Green 1987 350.00 [ 0]
8. White 1980 600.00 [ -1]
9.
10. Yellow 1988 300.00 [ -1]
11.
12.
13. Magenta 1983 600.00 [ -1]
14.
15. Orange 1984 550.00 [ -1]

65
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

End of Table. Press any key ...


m=10, n=15, NCOMP=13, ALS=1.30, D(m/n)=1.30

Pentru n=10 afişarea va fi:


Primary table:
1. Gray 1968 900.00 [ -1]
2. Blue 1981 500.00 [ 9]
3. White 1980 600.00 [ 3]
4. Cyan 1975 800.00 [ 5]
5. Orange 1984 550.00 [ 8]
6. Magenta 1983 600.00 [ -1]
7. Green 1987 350.00 [ 0]
8. Red 1980 450.00 [ -1]
9. Yellow 1988 300.00 [ -1]
10. Black 1981 500.00 [ -1]
End of Table. Press any key ...
m=10, n=10, NCOMP=16, ALS=1.60, D(m/n)=1.45

Tabelele de repartizare aleatorie se încarcă destul de simplu, nu necesit ordonarea înregistrărilor şi


asigură o căutare rapidă. Deaceea aceste tabele deseori se folosesc în practică.
Exerciţii.
1. Supraîncărcaţi operatorul de inserţie în clasa intern_hashing_table.
2. Supraîncărcaţi operatorul de extragere în clasa intern_hashing_table.

Funcţii de repartizare

Timpul calculării funcţiei de repartizare f(k) intră în timpul mediu de căutare, deaceiea trebuie de
ţinut cont la alegerea algoritmului, realizând funcţia de repartizare.
O funcţie bună de repartizare trebuie să asigure repartizarea uniformă a înregistrărilor pe poziţiile
vectorului de reflectare, fiindcă distribuirea neuniformă măreşte timpul mediu de căutare. Însă dacă
calcularea valorii funcţiei de repartizare necesită îndeplinirea unui număr mare de operaţii, aceasta
poate distruge toată economia în timpul căutării. Deci, algoritmul calculării funcţiei de repartizare
nu trebuie să fie complicat. Să privim câteva metode de calculare a funcţiei de repartizare:
1. Una din metodele simple se bazează pe evidenţierea unei părţi din codul numeric al cheii. De
exemplu, fie dimensiunea maximă aşteptată a tabelului de nume simbolice nu întrece 256.
Atunci funcţia de repartizare poate avea în calitate de valoare 8 biţi, fiindcă 256=28. Se poate
pur şi simplu de a evidenţia primii 8 biţi din codul binar al identificatorului sau de a lua careva 8
biţi din mijlocul codului. Trebuie doar să asigurăm cu cât este posibil o distribuire uniformă a
înregistrărilor prin funcţia f(k) în intervalul [0, 255].

66
Structuri de date (în baza C++): Suport de curs
S.Pereteatcu, A.Pereteatcu

2. Pentru asigurarea distribuirii uniforme se foloseşte “sumarea” codului identificatorului: prima


jumătate a codului se sumează cu a doua şi din rezultat se evidenţiază 8 biţi. Se poate de
asemenea de împărţit codul cheii în bucăţi câte 8 biţi, de sumat bucăţile şi de pus suma pe
modulul 28. Ultima modificare are careva probabilitate teoretică: la presupunerea a statisticei
independente de sumare a bucăţilor se primeşte repartizarea aproape de uniformă.
3. O altă metodă de calculare a funcţiei de repartizare este împărţirea. Pentru vectorul de reflectare
de lungimea n, cheia se priveşte ca un număr întreg şi se împarte la mărimea n. Experimentele
arată, că restul de la împărţire este repartizat aproape uniform în intervalul [0, n-1] şi poate fi
folosit ca valoarea funcţiei de repartizare.
Verificarea experimentală a metodelor descrise pentru tabelele de repartizare cu examinarea lineară
a arătat că evidenţierea simplă a bucăţii din codul identificatorului măreşte lungimea medie de
2
căutare în comparaţie cu cea teoretică, definită după formula: D( ) , în 4-5 sau şi mai
2 2
multe ori. Lungimea medie de căutare pentru metoda de sumare a bucăţilor după modulul 2k
aproape de două ori mai mare ca teoretică, dar pentru împărţirea, lungimea medie de căutare practic
coincide cu teoretică pentru σ≤8.85.

67

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