Documente Academic
Documente Profesional
Documente Cultură
Dare de Seama
Lucrarea de Laborator nr 1
Tema: Metode de Cautare in Tabele
Chisinau, 2021
Sarcina
Să se creeze un fişier textual care conţine cel puţin 50 de înregistrări, cu cel puţin 5 câmpuri şi are cel
puţin 2 tipuri de date, iar câmpul cheie trebuie să fie unic şi neordonat. Tematica structurii de date se
stabileşte împreună cu profesorul la lecţiile de laborator.
Să fie realizat un program în C++, în care sunt implementate cel puţin două metode de căutare în tabele
ordonate şi neordonate după câmpul cheie din fişierul textual creat anterior. Pentru fiecare metodă de
căutare de analizat lungimea medie teoretică şi lungimea practică de căutare. De descris algoritmul
metodelor de căutare pe paşi.
Descrierea temei
Tematica structurii de date al laboratorului meu este “Telefon Mobil”. Am un fisier textual creat din 50
inregistrari din linie noua, delimitatorul dintre campuri este caracterul ‘\t’.
struct TelefonMobil
{
int id;
string model;
string os;
int ram;
int capacitate;
float camera;
TelefonMobil()
{
id = 0;
model;
os;
ram = 0;
capacitate = 0;
camera = 0.0f;
}
TelefonMobil(string line)
{
istringstream is(line);
string s;
getline(is, s, '\t');
id = stoi(s);
getline(is, s, '\t');
model = (string)s;
getline(is, s, '\t');
os = (string)s;
getline(is, s, '\t');
ram = stoi(s);
getline(is, s, '\t');
capacitate = stoi(s);
getline(is, s, '\t');
camera = stof(s);
}
Structura de date are un constructor implicit si un constructor care primeste parametru un sir de
caractere. Acest sir de caractere primit reprezinta o linie din fisierul textual. Pe baza acestui sir se
extrag fragmentele delimitate prin caracterul ‘\t’ si se initializeaza campurile obiectului. In asa mod
primim un obiect creat pe baza unei inregistrari din fisier.
Am supraincarcat operatorii “<<”, “==”, “<”, “>”.
string line;
while (getline(fs, line, '\n'))
{
TelefonMobil tmp = TelefonMobil(line);
t.push_back(tmp);
}
fs.close();
}
Realizarea algoritmilor
1. Cautarea secventiala.
Cautarea secventiala reprezinta cea mai simpla metoda: se parcurge element cu element tabloul si se
verifica daca cheia elementului curent este egala cu cheia elementului cautat.
Lungimea medie teoretica: T(n) = (n+1)/2 = 25;
Complexitatea: O(n) = 50.
(n – numarul de elemente al tabloului).
Codul:
int cautare_secventiala(const telefoane& t, int x)
{
int counter = 0;
for (auto tel : t)
{
counter++;
if (tel.id == x) {
return counter;
}
}
return 0;
}
Tablou ordonat:
Pentru cheia 15 se executa 15 iteratii,
pentru cheia 45, se executa 45 iteratii.
Cu cat mai aproape cheia se afla de inceput, cu atat mai rapid se gaseste si invers. Se intelege ca pentru
cheia 50 se atinge cazul cel mai rau si se executa 50 de iteratii.
Tablou neordonat:
In tabloul neordonat totul depinde de ordinea elementelor.
Spre exemplu, in aceasta captura de ecran, elementul cu cea mai mare cheie se afla pe pozitia 8.
2. Cautarea binara
Algoritmul de căutare binară oferă performanţe mai bune decât algoritmul de căutare secvenţială. El
funcţionează astfel: se compară numărul de căutat cu elementul aflat la mijlocul şirului (element care se
mai numeşte şi pivot). În cazul în care cele două elemente coincid căutarea s-a încheiat cu succes. Dacă
numărul de căutat este mai mare decât pivotul, se continuă căutarea în aceeaşi manieră în subşirul
delimitat de pivot şi capătul şirului iniţial. Dacă numărul de căutat este mai mic decât pivotul se
continuă căutarea în aceeaşi manieră în subşirul delimitat de pivot şi începutul şirului iniţial.
Algoritmul cautarii binare lucreaza doar in tablouri ordonate.
Codul:
int cautare_binara(const telefoane& t, int x)
{
int counter = 0;
int a = 0, b = t.size() - 1;
while (a <= b)
{
counter++;
int k = (a + b) / 2;
if (t[k].id == x)
{
return counter;
}
if (t[k].id > x) b = k - 1;
else a = k + 1;
}
return 0;
}
1.
1 2 3 4 ... 25 ... 48 49 50
2.
1 2 3 4 ... 12 ... 23 24
3.
1 2 3 4 5 6 ... 10 11
4.
1 2 3 4 5
5.
4 5
Intr-adevar, elementul a fost gasit la a 5-a iteratie. Am nimerit ca elementul cu cheia 4 sa fie cel gasit
cel mai greu, adica se implineste cazul cel mai rau de cautare.
Alte exemple:
3. Cautarea Fibonacci
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.
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.
Codul:
int fibonacci(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
if (n > 1)
return (fibonacci(n-1) + fibonacci(n - 2));
}
while (true)
{
counter++;
if (i < 0)
{
if (p == 1) {
cout << "Elementul cu indicele " << k << " nu a fost gasit\n";
return 0;
}
i += q;
p -= q;
q -= p;
}
if (i >= n)
{
if (q == 0) {
cout << "Elementul cu indicele " << k << " nu a fost gasit\n";
return 0;
}
i -= q;
int tmp = q;
q = p - q;
p = tmp;
}
if (t[i].id == k)
return counter;
if (k < t[i].id)
{
//scade indexul si cauta in partea stanga a tabloului
if (q == 0) {
cout << "Elementul cu indicele " << k << " nu a fost gasit\n";
return 0;
}
i -= q;
int tmp = q;
q = p - q;
p = tmp;
}
else if (k > t[i].id)
{
//ridica indexul si cauta in partea dreapta a tabloului
if (p == 1) {
cout << "Elementul cu indicele " << k << " nu a fost gasit\n";
return 0;
}
i += q;
p -= q;
q -= p;
}
}
}
Exemplu:
Cheia: 15
1 iteratie:
m = 4; i = 30; p = 21; q = 13;
15 > t[13]:
i = 17; p = 13; q = 8.
2 iteratie:
15 < t[17]:
i = 9; p = 8; q = 5;
3 iteratie:
15 > t[9]:
i = 14; p = 3; q = 2;
4 iteratie:
15 = t[14]:
gasit;
Cazul mediu pentru gasirea unui element este 6 iteratii. Am gasit elementul 15 in 4 iteratii.
Exemplu de cel mai rau caz de cautare:
Este similară cu căutarea binară, dar foloseşte o altă formulă pentru calculul lui "m", şi anume:
m=st+(x-v[st])*(dr-st)/(v[dr]-v[st])
ceea ce conduce la o delimitare mai rapidă a zonei din tablou în care s-ar putea găsi x.
Această metodă este eficientă în cazul cand elementele sunt distribuite uniform in tablou.
Numărul de căutări în acest caz este de ordinul O(lg(lgN)). N = 50: ~O(1.3)
Codul:
int interpoliation(const telefoane& t, int k)
{
int counter = 0;
int lower = 0;
int upper = t.size() - 1;
while (lower <= upper && k >= t[lower].id && k <= t[upper].id)
{
if (lower == upper)
if (t[lower].id == k) return counter;
else return 0;
int pos = lower + ((upper - lower) / (t[upper].id - t[lower].id)) * (k -
t[lower].id);
counter++;
if (t[pos].id == k)
return counter;
if (t[pos].id < k)
lower = pos + 1;
else
upper = pos - 1;
}
return 0;
}
Exemple:
Observam cum orice element este gasit din prima incercare. Totul se bazeaza pe faptul ca elementele
sunt distribuite uniform cu pasul 1 in tablou.
Ideea din spatele hashing-ului este memorarea unui element intr-un tablou sau lista, in functie de cheia
sa. Pe cazul mediu toate aceste operatii necesita O(1) timp.
Elementele sunt puse intr-un tablou alocat static pe pozitiile cheilor lor. Prin adresare directa, un
element cu cheia k va fi memorat in locatia k. Toate cele 3 operatii(inserarea, cautarea, stergerea) sunt
extrem de simple (necesita doar o accesare de memorie), dar dezavantajul este ca aceasta tehnica
"mananca" foarte multa memorie: O(|U|), unde U este universul de chei.
#define DIM 50
struct Entity
{
TelefonMobil entity;
Entity* next;
Entity(TelefonMobil e)
{
entity = e;
next = nullptr;
}
};
class HashTable
{
private:
Entity* hash_table[DIM];
private:
int hash_func(int key);
bool hash_table_insert(TelefonMobil tel);
public:
HashTable()
{
for (int i = 0; i < DIM; i++)
hash_table[i] = nullptr;
}
HashTable(telefoane tabel)
{
for (int i = 0; i < DIM; i++)
hash_table[i] = nullptr;
for (TelefonMobil t : tabel)
{
hash_table_insert(t);
}
}
~HashTable()
{
for (int i = 0; i < DIM; i++)
{
Entity* tmp = hash_table[i];
Entity* next = nullptr;
while (tmp != nullptr) {
next = tmp->next;
delete tmp;
tmp = next;
}
delete tmp;
}
}
void print_table()
{
cout << "---Start---\n";
for (int i = 0; i < DIM; i++) {
if (hash_table[i] == nullptr)
cout << i << "\t---\n";
else
{
Entity* tmp = hash_table[i];
cout << i;
while (tmp != nullptr)
{
cout << '\t' << tmp->entity << " --- ";
tmp = tmp->next;
}
cout << endl;
}
}
cout << "---End---\n";
}
};
Exemplu:
Tabelele hash sunt utilizate atat pentru tablourile ordonate, cat si neordonate. Indiferent de ce tablou se
va folosi, el va genera aceiasi indici bazandu-se pe functia hash si cheia inregistrarii.
6. Cautarea folosind arborele binar de cautare
Un arbore binar de căutare este un arbore binar care are asociată o cheie cu fiecare din nodurile sale
interne, cu proprietatea suplimentară că cheia fiecărui nod este mai mare sau egală cu cheile din toate
nodurile subarborelui său stâng şi mai mică sau egală cu cheile din toate nodurile subarborelui său
drept.
Căutarea unei informaţii într-un ABC se bazează pe ideea construcţiei arborelui. Se compară informaţia
cu cea din nodul curent. Dacă sunt egale, căutarea se încheie, dacă nu, căutarea continuă în subarborele
stâng sau în subarborele drept după cum valoarea căutată este mai mică, respectiv mai mare decât
informaţia din nodul curent.
Arborele binar de cautare se utilizeaza la tablouri neordonate, intrucat, daca se ia unul ordonat, atunci
arborele va deveni o lista inlantuita.
Daca arborele este balansat, timpul de cautare este: O(log2(n)); n = 50: ~O(6).
struct node {
TelefonMobil data;
node* left;
node* right;
node(TelefonMobil val)
{
data = TelefonMobil(val);
left = nullptr;
right = nullptr;
}
};
class BST
{
private:
node* root = nullptr;
void printtree(node* root, int level);
bool insert_node(node** rootptr, TelefonMobil tel);
int find_data(node* root, int key, int& counter);
void delete_nodes(node* data);
public:
BST(vector<TelefonMobil> tabel)
{
for (int i = 0; i < tabel.size(); i++)
insert(tabel[i]);
}
~BST()
{
delete_nodes(root);
}
void print_tree();
bool insert(TelefonMobil tel);
int find(int key);
};
tabs(level);
printf("left: \n");
printtree(root->left, level + 1);
tabs(level);
printf("right: \n");
printtree(root->right, level + 1);
}
void BST::print_tree() {
printf("---<start>---\n");
printtree(root, 0);
printf("---<done>---\n");
}
Am descris o structura node care contine ca date informatia despre un telefon mobil si are pointeri la
arborele stang si arborele drept.
Exemplu:
Functia main:
(Contine un fel de meniu care cere utilizatorului sa introduca ce doreste)
int main()
{
cout << log2(51) - 0.5 << endl;
string path = "telefoane_in.txt";
telefoane data;
read_data_from_file(data, path);
telefoane data1 = shuffle_data(data);
while (true)
{
system("cls");
int var;
print_menu1();
cin >> var;
if (var == 3) break;
system("cls");
switch (var)
{
case 1: {
while (true) {
system("cls");
print_menu_neordonat();
int i;
cin >> i;
if (i == 4) break;
else if (i == 3) {
int key;
HashTable ht = HashTable(data1);
printf("Introdu cheia: ");
cin >> key;
int j = ht.hash_table_lookup(key);
if (j) {
printf("Elementul a fost gasit din a %d oara\n", j);
}
else {
printf("Nu a fost gasit element cu asemenea cheie.\n");
}
printf("\n");
printf("Sa afisez tabloul? 0/1\n");
bool l;
cin >> l;
if (l)
ht.print_table();
system("pause");
}
else if (i == 2) {
BST bst = BST(data1);
int key;
printf("Introdu cheia: ");
cin >> key;
int j = bst.find(key);
if (j) {
printf("Elementul a fost gasit din a %d oara\n", j);
}
else {
printf("Nu a fost gasit element cu asemenea cheie.\n");
}
printf("\n");
printf("Sa afisez arborele? 0/1\n");
bool l;
cin >> l;
if (l)
bst.print_tree();
system("pause");
}
else {
int key;
printf("Introdu cheia: ");
cin >> key;
int j = cautare_secventiala(data1, key);
if (j)
printf("Elementul a fost gasit din a %d oara\n", j);
else
printf("Nu a fost gasit element cu asemenea cheie.\n");
system("pause");
}
}
break;
}
case 2: {
system("cls");
while (true) {
print_menu_ordonat();
int i;
cin >> i;
if (i == 6) break;
else if (i == 5) {
int key;
HashTable ht = HashTable(data1);
printf("Introdu cheia: ");
cin >> key;
int j = ht.hash_table_lookup(key);
if (j) {
printf("Elementul a fost gasit din a %d oara\n", j);
}
else {
printf("Nu a fost gasit element cu asemenea cheie.\n");
}
printf("\n");
printf("Sa afisez tabloul? 0/1\n");
bool l;
cin >> l;
if (l)
ht.print_table();
system("pause");
}
else {
int key;
printf("Introdu cheia: ");
cin >> key;
int j = Cautare[i - 1](data, key);
if (j) {
printf("Elementul a fost gasit din a %d oara\n", j);
}
else {
printf("Nu a fost gasit element cu asemenea cheie.\n");
}
system("pause");
}
system("cls");
}
break;
}
}
}
return 0;
}