Sunteți pe pagina 1din 17

Ministerul Educaţiei al Republicii Moldova

Universitatea de Stat din Moldova


Departamentul Informatică

Lucrare de laborator nr.1

La disciplina Algoritmi, Structuri de date si


Complexitate
Tema: Metode de căutare în tabele

Efectuat de studenta:
Țînțaru Valeria, gr.I1801

Verificat de:
Opinca Carolina, conf. Univ.

Chişinău, 2020
1
Sarcina laboratorului:

Să se creeze un fișier textual care conține cel puțin 50 de înregistrări și cel puțin 5 cîmpuri.
Unul din cîmp este cheia care este unică pentru fiecare înregistrare și este neordonată.
Să se implementeze citeva metode de căutare și pentru fiecare metodă să se analizeze lungimea
medie teoretică de căutare și lungimea practică de căutare.De descris algoritmul metodei pe pași:
1. Metoda secvențială în tabele neordonate;
2. Metoda de căutare în tabele neordonate structură arborescentă;
3. Metoda binara în tabele ordonate;
4. Metoda Hash (de dispersare);
5. Metoda Fibonacci.

Căutarea:

Problema cea mai frecvent întâlnită în practică este, probabil, cea a regăsirii rapide a unor
informaţii. De exemplu, căutarea unui cuvânt în dicţionar sau a unui număr în cartea de telefon. De
obicei, informaţiile sunt reprezentate sub forma unor înregistrări, fiecare înregistrare conţinând un
câmp numit cheie, ce permite identificarea înregistrării (de exemplu, într-un dicţionar înregistrările sunt
formate dintr-un cuvânt ce reprezintă câmpul cheie, din pronunţia şi definiţia corespunzătoare
cuvântului respectiv).
O problemă de căutare constă în identificarea unei înregistrări cu cheia specificată, în scopul
prelucrării informaţiilor corespunzătoare. Presupunem că înregistrările au chei distincte; cazul
înregistrărilor având chei identice poate fi tratat în diverse moduri, în funcţie de aplicaţie (de exemplu,
fiecare înregistrare poate conţine o listă simplu înlănţuită formată din înregistrări având aceeaşi cheie
comună).

Cãutarea secventialã:

Să presupunem că înregistrările sunt memorate într-un vector global R de dimensiune n. Pentru


a identifica înregistrarea cu cheia specificată x, o primă soluţie ar fi de a compara secvenţial cheia x cu
cheile înregistrărilor din R:
search (int c) { int position=-1,a,j;
for(int i=0;(position==-1)&&(i<n);i++)
{ a=t[i].getCod();
if(c==a )
{ position=i; t[i].show(); } }
if(position==-1 ) cout<<"\n Inexistent";
}
}

2
În cazul în care cheia nu se găseşte în vector se fac exact n comparaţii. În cazul în care cheia se
găseşte în vector, în ipoteza că înregistrările sunt căutate cu aceeaşi probabilitate (1/n), se fac în medie
1 n 1
(1  2 ... n )  comparaţii.
n 2
Deci algoritmul de căutare secvenţială este de O(n).
Căutarea secvenţială poate fi adaptată în mod natural pentru cazul în care înregistrările sunt
reţinute într-o listă simplu înlănţuită, complexitatea algoritmului rămânând neschimbată.

Căutarea binară

Dacă numărul de înregistrări este mare, putem diminua timpul de căutare presupunând că
înregistrările sunt în ordinea crescătoare a cheilor şi aplicând o strategie de tip "divide et impera ".
Comparăm cheia căutată x cu cheia înregistrării din mijloc. În caz de egalitate, căutarea se
încheie cu succes, altfel înjumătăţim spaţiul de căutare, determinând jumătatea în care există şanse să
se găsească cheia x şi reluând procedeul pentru această jumătate.
while((c!=t[m].getCod())&&(s<=f))
{ count++; delay(100);
if(t[m].getCod()>c)
{ f=m-1; }
else
{ s=m+1;}
m=(s+f)/2; }
if(s<=f)
{ t[m].show();
} else
{
cout<<"Not found!"; }

Observaţii
1. Algoritmul poate fi descris foarte elegant recursiv.
2. Strategiei de căutare binară i se poate asocia un arbore binar astfel :
- rădăcina arborelui este indicele elementului din mijloc, (n+1) div 2;
- subarborele stâng este format din arborele binar corespunzător căutării în şirul elementelor cu indici
mai mici decât rădăcina;
- subarborele drept este format din arborele binar corespunzător căutării în şirul elementelor cu indici
mai mari decât rădăcina.

3
Fig. 1.
De exemplu, pentru n=15 arborele binar asociat algoritmului de căutare binară este

Fig. 2.
i
pe fiecare nivel i0,1,2,3 fiind situate 2 vârfuri.
Pentru n = 10 arborele este :

Fig. 3.
primele 3 niveluri fiind complete, al patrulea conţinând 3 vârfuri.
Observaţii
1. Parcurgând în inordine arborele, obţinem şirul indicilor ordonat crescător.
2. Pe fiecare nivel i în arbore există 2 i vârfuri, cu excepţia ultimului nivel care este incomplet în cazul
în care n nu este de forma 2k-1.
Folosind aceste observaţii, putem calcula înălţimea arborelui asociat algoritmului de căutare
binară în funcţie de n, dimensiunea spaţiului de căutare. Dacă notăm cu h înălţimea arborelui, atunci :
1+2+...+2h-1 < n  1+2+...+2h
 2h-1 < n  2h+1-1
 2h  n < 2h+1
 h  log2n < h+1  h = [log2n]
Algoritmul de căutare binară efectuează cel mult o comparaţie pe fiecare nivel în arbore :
-dacă elementul căutat nu se găseşte în spaţiul de căutare, căutarea se termină pe un nod
terminal, deci pe nivelul h sau h-1.
-dacă elementul căutat se găseşte în spaţiul de căutare, în funcţie de poziţia sa, căutarea se poate
termina pe orice nivel, deci sunt necesare cel mult h+1 comparaţii.
Putem enunţa o teoremă de caracterizare a complexităţii algoritmului de căutare binară.

4
Teoremă
Algoritmul de căutare binară efectuează cel mult [log2n]+1 comparaţii, în cazul unei căutări cu
succes, respectiv [log2n] sau [log2n]+1 comparaţii, în cazul unei căutări fără succes, unde n este
dimensiunea spaţiului de căutare.
Deci complexitatea algoritmului de căutare binară este de O(log2n).
Observaţie
Problema enunţată presupune că mulţimea de valori în care se face căutarea este statică (deci nu
suportă inserări sau ştergeri de elemente) şi deci arborele binar asociat rămâne total echilibrat, cu
înălţimea h = [log2n]. De asemeni, în analiza complexităţii am presupus că orice valoare este căutată cu
aceeaşi probabilitate.

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.
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].

Căutarea Fibonacci

Algoritm: Sirul numerelor Fibonacci se defineste recursiv astfel: F0=0, F1=1, Fk+2=Fk+1+Fk, k>=0. 
Lungimea tabelului este egala cu ultimul numar Fibonacci si este N=Fm. Daca lungimea tabelului nu
este un numar Fibonacci se ia in calitate de Fm cel mai mic numar din F, imediat mai mare decat n.   
Pas1 (Initializare):       i=Fk, p=Fk-1, q=Fk-2, unde p si q sunt doua numere Fibonacci consecutive.
Pas2 (Comparare):    Daca K<Ki, atunci mergi la pasul 3; daca K>Ki atunci mergi la pasul 4; si daca
K=Ki algoritmul se finiseaza cu succes. 
Pas3 (Descrestere i):  Daca q=0, atunci avem insucces. In caz contrar se fac urmatoarele atribuiri: i=i-q
si p=q; q=p-q; apoi ne intoarcem la pasul 2.
5
Pas4 (Incrementare i): Daca p=1, atunci avem insucces. In caz contrar se fac urmatoarele atribuiri
i=i+q p=p-q si apoi q=q-p (p se ia acel obtinut acum); apoi ne intoarcem la pasul 2.

Listingul programului:

#include <conio.h>
#include <stdio.h>
#include <iostream.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <dos.h>

class elem {
public:
virtual int fscanf_el (FILE * f)=0;
virtual int show(const char * opening,const char * ending)=0;
virtual int free ()=0;
int operator > (elem &) {
error("Error should overcode operator \">\"!\n"); return 0; }
int operator < (elem &) {
error("Error should overcode operator \"<\"!\n"); return 0; }
int operator >= (elem &) {
error("Error should overcode operator \">=\"!\n"); return 0; }
int operator <= (elem &) {
error("Error should overcode operator \"<=\"!\n"); return 0; }
int operator == (elem &) {
error("Error should overcode operator \"==\"!\n"); return 0; }
int operator != (elem &) {
error("Error should overcode operator \"!=\"!\n"); return 0; }
protected :
void error (char * message){
cout<<message; cout<<"Apasa orice tasta pentru a finisa...\n";
getch(); exit(1); }
};

class STUDENT
{
public:
int cod;
char nume[20];
char prenume[20];
char obiect[20];
int an;
int st;
int dr;

STUDENT()
{ cod=0; strcpy(nume,""); strcpy(prenume,"");
strcpy(obiect,""); an=0; st=-1; dr=-1; }
void setcod(int i) { cod=i;}
void setnume(char* n) { strcpy(nume,n); }
void setprenume(char* a) { strcpy(prenume,a);}

6
void setan(int i) {an=i; }
void setobiect(char* d) { strcpy(obiect,d); }
int getcod() { return cod; }
int set_st(int new_st)
{
st=new_st;
return st;
}
int get_st()
{
return st;
}
int set_dr(int new_dr)
{
dr=new_dr;
return dr;
}
int get_dr()
{
return dr;
}
char* getnume() { return nume; }
char* getprenume() { return prenume;}
int getan(){ return an; }
char* getobiect() { return obiect; }
int fscanf_el (FILE * f)
{return fscanf(f,"%i %s %s %s
%i",&cod,nume,prenume,obiect,&an);}
void virtual show( ) {
cout<<cod<<" "<<nume<<" "<<prenume<<" "<<obiect<<"
"<<an<<endl; }
virtual int free() { return cod==0; }
int operator >(STUDENT &e2) {
return (this->cod>e2.cod);}
int operator < (STUDENT &e2) {
return (this->cod<e2.cod); }
int operator <= (STUDENT &e2) {
return (this->cod<=e2.cod); }
int operator >= (STUDENT &e2){
return (this->cod>=e2.cod); }
int operator == (STUDENT &e2) {
return (this->cod==e2.cod); }
int operator != (STUDENT &e2) {
return (this->cod!=e2.cod); }
};

template <class el> class tabel


{
protected:
int n;
el t[200];
public:
tabel() {n=0;}
tabel (char * file);
void search (int c) ;
void searchbin (int c);
int fib_search(el initial);
int searchtree(STUDENT tmp);
void createtree();

7
void killtree();
void sort();

void show(const char *opening,const char *ending);

protected:
void error(char *message){
cout<<message; cout<<"Apasa orice tasta pentru a finisa...\n";
getch(); exit(1); }
};

template
<class el>
tabel<el>::tabel (char * file) {
FILE *pf;
pf=fopen(file,"rt"); n=0;
while(!feof(pf))
if (t[n].fscanf_el(pf)>0)
n++;
fclose(pf);
}

template
<class el>
void tabel<el>::search (int c) { int position=-1,a,j;

for(int i=0;(position==-1)&&(i<n);i++)
{ a=t[i].getcod();
if(c==a )
{ position=i; t[i].show(); } }
if(position==-1 ) cout<<"\nNu exista";
else{
cout<<"\nLungimea practica de cautare este:"<<position;
cout<<"\nLungimea teoretica de cautare este:"<<n/2; }
}

template
<class el>
int tabel<el>::fib_search(el initial)
{
int position=-1, contor=0, i=0, q=0, p=0;
double durata;
int
fib[]={0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765};
for(int j=0;;j++)
{
if(i>n)
{
i=fib[j-2];
p=fib[j-3];
q=fib[j-4];
break;
}
else i=fib[j];
}
while(2<3)
{
contor++;

8
if(initial==t[i]) {position=i; break;}
if(initial<t[i])
{
if(q==0) break;
else {int v=p;i=i-q;p=q;q=v-q;}
}
if(initial>t[i])
{
if(p==1) break;
else{ i=i+q; p=p-q; q=q-p;}
}
}
if(position==-1){cout<<endl<<cout<<"Error!Error!"<<endl;}
else
{
cout<<endl;
t[position].show();
cout<<endl<<"Lungimea practica de cautare este:"<<" "<<contor<<endl;
float caut;
caut=log(n)/log(2);
cout<<"Lungimea teoretica de cautare este:"<<" "<<caut<<endl;
}
return position;
}

template
<class el>
void tabel<el>::show(const char *opening,const char *ending)
{ cout<<opening;
for(int i=0;i<n;i++){
t[i].show();if(i%20==0&&i!=0){ cout<<endl<<"Apasa pentru a vedea";
getch();clrscr();}
cout<<ending;
cout<<"\n " ;} }

template
<class el>
void tabel<el>::sort()
{
int j,count=0;
el aux;
for(int i=0;i<n-1;i++)
for(j=i;j<n;j++)
{
if(t[i].getcod()>t[j].getcod())
{
aux=t[i];
t[i]=t[j];
t[j]=aux;
count++;
}
}
cout<<"Sortarea s-a terminat!"<<endl;
getch();
}

template
<class el>
void tabel<el>::searchbin(int c)

9
{
if(!n)
{
cout<<"ERROR! Introduceti datele";
getch(); return; }
int s=0,f=n-1,count=1,j;
int m=(s+f)/2;

while((c!=t[m].getcod())&&(s<=f))
{ count++;
if(t[m].getcod()>c)
{ f=m-1; }
else
{ s=m+1;}
m=(s+f)/2; }
if(s<=f)
{
t[m].show();
cout<<"\nLungimea practica de cautare este:"<<" "<<count;
cout<<"\nLungimea teoretica de cautare este:"<<" "<<log(n)/log(2);
}
else
{
cout<<"\n Nu exista asa date!";
}
}

template
<class el>
void tabel<el>::createtree()
{
for(int i=1;i<n;i++)
{
int forw=1,j=0;
while(forw)
{
if(t[i]<t[j])
{
if(t[j].get_st()==-1)
{
t[j].set_st(i);
forw=0;
}
else
j=t[j].get_st();
}
else
if(t[i]>t[j])
{
if(t[j].get_dr()==-1)
{
t[j].set_dr(i);
forw=0;
}
j=t[j].get_dr();
}
}
}
}

1
template
<class el>
void tabel<el>::killtree()
{
for(int i=0;i<n;i++)
{
t[i].set_st(-1);
t[i].set_dr(-1);
}
}

template
<class el>
int tabel<el>::searchtree(STUDENT tmp)
{
int i=0,count=0,forw=1;
while(forw)
{
if(tmp==t[i])
{
t[i].show();
cout<<t[i].st<<" "<<t[i].dr<<endl;
cout<<"Lungimea practica de cautare este:"<<count<<endl;
cout<<"Lungimea teoretica maxima de cautare este:"<<n/2<<endl;
cout<<"Lungimea teoretica minima de cautare
este:"<<log(n)/log(2.0)*2.0;
forw=0;
}
else
{
if(tmp<t[i])
i=t[i].get_st();
else
i=t[i].get_dr();
if(i==-1)
{
forw=0;
cout<<"Elementul nu este gasit"<<endl;
}
}
count++;
}
return 0;
}

void main()
{
clrscr();
STUDENT pl,tmp;
tabel <STUDENT> gr("student.txt");
char ch, c;
int coden;

do{
clrscr();

1
cout<<"Menu:"<<endl;
cout<<"*****************************************"<<endl;
cout<<"0)Afisare tabel"<<endl;
cout<<"1)Metoda secventila"<<endl;
cout<<"2)Metoda arborescenta"<<endl;
cout<<"3)Sortarea"<<endl;
cout<<"4)Metoda binara"<<endl;
cout<<"5)Metoda Fibonacci"<<endl;
cout<<"6)Exit"<<endl;
c=getch();
switch(c)
{
case '0':
ch='n';
while(ch!='y')
{
clrscr();
gr.show("Continutul fisierului:\n"," ");
cout<<endl<<"Iesiti?(Y/N)";
ch=getch();
}
break;
case '1':
ch='n';
while(ch!='y')
{
clrscr();
cout<<"Introduceti codul pentru cautare"<<endl;
cin>>coden;
gr.search(coden);
cout<<endl;
cout<<endl<<"Iesiti?(Y/N)";
ch=getch();
}
break;
case '2':
ch='n';
while(ch!='y')
{
clrscr();
cout<<"Introduceti codul pentru cautare"<<endl;
cin>>coden; cout<<endl;
tmp.setcod(coden);
gr.createtree();
cout<<endl;
gr.searchtree(tmp); cout<<endl;
cout<<endl<<"Iesiti?(Y/N)";
gr.killtree();
ch=getch();
}
break;
case '3':
clrscr();
gr.sort();
getch();
break;
case '4':
ch='n';
while(ch!='y')

1
{
clrscr();
cout<<"Introduceti codul pentru cautare"<<endl;
cin>>coden;
cout<<endl;
gr.searchbin(coden); cout<<endl;
cout<<endl<<"Iesiti?(Y/N)";
ch=getch();
}
break;
case '5':
ch='n';
while(ch!='y')
{
clrscr();
cout<<"Introduceti codul pentru cautare"<<endl;
cin>>pl.cod;
cout<<endl;
if(gr.fib_search(pl)==-1) cout<<endl<<"Eroare!
Sortati!"<<endl;
cin.ignore();
cout<<endl;
cout<<endl<<"Iesiti?(Y/N)";
ch=getch();
}
break;
} }
while(c!='6');
}

1
Continutul fișierului student.txt

1
Rezultatul

Afișare tabel:

1
Metoda secvențială

Metoda arborescentă

Metoda binară

1
Metoda Fibonacci

Concluzii

La execuția programului s-a afișat tabloul cu înregistrările încarcate din fișier apoi la
introducerea unui identificator pentru o inregistrare pe care o vom cauta în tabloul de elemente s-a
afisat înregistrarea, lungimea teoretica de cautare, lungimea practica de cautare, iar daca nu se gaseste
ni se afișeaza înregistrare inexistentă.
Pentru a vedea mai bine care metodă este mai eficientă pentru căutarea în tabele am cautat una
și aceiași înregistrare folosind toate metodele descrise în program.
În final am observat că cea mai bună metodă pentru căutare este:
1. Metoda Fibonacci
2. Metoda binară
3. Metoda arborescentă
4. Metoda secventială
Nu este mare diferență dintre metoda Fibonacci și cea binară, dar totuși este la lungimea
teoretică cu citeva miimi.