Sunteți pe pagina 1din 17

Universitatea de Stat din Moldova

Facultatea de Matematica si Informatica


Specialitatea Informatica Aplicata

Dare de Seama
Lucrarea de Laborator nr 1
Tema: Metode de Cautare in Tabele

Realizat de: Bogaci Diana, grupa IA1901


Verificat de: Novac Ludmila

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

Secventa de cod care descrie structura de date (“TelefonMobil.h”):


#pragma once
#include <string>
#include <iostream>
#include <sstream>
using namespace std;

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);
}

TelefonMobil(const TelefonMobil& other)


{
this->id = other.id;
this->model = other.model;
this->os = other.os;
this->ram = other.ram;
this->capacitate = other.capacitate;
this->camera = other.camera;
}

friend ostream& operator<<(ostream& os, TelefonMobil& tel);


bool operator==(const TelefonMobil& other)
{
return (id == other.id)
&& (model == other.model)
&& (os == other.os)
&& (ram == other.ram)
&& (capacitate == other.capacitate)
&& (camera == other.camera);
}

bool operator<(const TelefonMobil& other)


{
return this->id < other.id;
}

bool operator>(const TelefonMobil& other)


{
return !operator<(other);
}
};

ostream& operator<<(ostream& os, TelefonMobil& tel)


{
os << tel.id << " | " << tel.model << " | " << tel.os << " | " << tel.ram << " | " <<
tel.capacitate << " | " << tel.camera << "\n";
return os;
}

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 “<<”, “==”, “<”, “>”.

Citirea din fisier:


Pentru a citi din fisier am utilizat fstream. Se citeste cate o linie si cu ajutorul constructorului descris
mai sus initializez obiectul si il introduc in tablou.
Mai jos este secventa:
typedef std::vector<TelefonMobil> telefoane;
typedef telefoane::iterator it;
void read_data_from_file(telefoane& t, const string& path)
{
fstream fs;
fs.open(path, fstream::in);

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

Intrucat tabloul meu contine 50 elemente, atunci T(n) = 25.

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.

Lungimea medie de cautare: T(n) = log2(n+1)-1/2 => T(50) = log2(50+1)-0.5=5.17243 ~ 5


Complexitate: O(log2(50)) =~ O(6).

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;
}

Sa analizam pe baza tabelului:

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.

Metoda ce cautare Fibonacci lucreaza doar pentru tablouri ordonate.

Complexitatea de timp: O(log2(n)); n = 50: ~O(6).

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));
}

int cautare_fibonacci(const telefoane& t, int k)


{
int counter = 0;
int i, p, q, j = 0;
int n = t.size();

while (fibonacci(j + 1) <= n)


j++;

int m = fibonacci(j + 1) - (n + 1);


i = fibonacci(j) - m;
p = fibonacci(j - 1);
q = fibonacci(j - 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:

4. Cautarea prin interpolare

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.

Toti algoritmii descrisi mai sus se vor gasi in “dependente.h”

5. Cautare prin hash-tabel

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.

Pentru a evita coliziunile am utilizat inlantuirea externa:


In fiecare locatie din hash tinem o lista inlantuita; astfel, la oricare din cele 3 operatii se va parcurge
toata lista. Pe un caz pur teoretic, toate cele N elemente ar putea fi repartizate in aceeasi locatie, insa pe
cazuri practice lungimea medie a celui mai lung lant este de lg(N).

Codul pentru tabelul meu hash (“HashTable.h”):


#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include "TelefonMobil.h"
using namespace std;

#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";
}

int hash_table_lookup(int key)


{
int index = hash_func(key);
Entity* tmp = hash_table[index];
int counter = 1;
while (tmp != nullptr
&& tmp->entity.id != key)
{
counter++;
tmp = tmp->next;
}
if (tmp == nullptr) return 0;
return counter;
}

};

inline int HashTable::hash_func(int key)


{
return hash<int>{}(key) % DIM;
}

bool HashTable::hash_table_insert(TelefonMobil tel)


{
int index = hash_func(tel.id);
Entity* en = new Entity(tel);
if (hash_table[index] == nullptr)
hash_table[index] = en;
else {
en->next = hash_table[index];
hash_table[index] = en;
}
return true;
}

Explicatie succinta a codului:


Am creat o structura Entity care pastreaza un obiect de tip TelefonMobil si pointer la urmatorul obiect.
Tabelul meu hash reprezinta un tablou de pointeri de entitati, fiecare din aceste entitati poate puncta la
o alta entitate, si astfel va fi realizata inlantuirea externa.
Inserarea: hash_table_insert(TelefonMobil)
se afla indicele la care se va afla elementul;
se creeaza obiect;
daca locul nu e ocupat, se insereaza la acel indice;
daca locul e ocupat, se parcurge lista inlantuita si se insereaza la sfarsit.
Cautarea: hash_table_lookup(int)
se alfa indicele la care se afla elementul in tablou, utilizand functia hash;
se parcurge lista inlantuita de la acel indice:
daca cheia elementului curent este egal cu cheia cautata → elementul e gasit;
in caz contrar → nu exista asemenea element;

Exemplu:

Vedem ca elementele sunt gasite din prima data.

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

Codul pentru arborele descris de mine:


#pragma once
#include <iostream>
#include <vector>
#include "TelefonMobil.h"
using namespace std;

void tabs(int num)


{
for (int i = 0; i < num; i++)
printf("\t");
}

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);
};

inline void BST::printtree(node* root, int level)


{
if (root == nullptr) {
tabs(level);
printf("---<empty>---\n");
return;
}
//preorder print: root -> left -> right
tabs(level);
cout << root->data;

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");
}

inline bool BST::insert_node(node** rootptr, TelefonMobil tel)


{
node* rootp = *rootptr;
if (rootp == nullptr) {
//tree is empty
(*rootptr) = new node(tel);
return true;
}
if (tel == rootp->data) {
//value is already in the node
return true;
}
if (tel < rootp->data) {
return insert_node(&(rootp->left), tel);
}
else {
return insert_node(&(rootp->right), tel);
}
}

inline bool BST::insert(TelefonMobil tel)


{
return insert_node(&root, tel);
}

inline int BST::find(int key)


{
int counter = 0;
return find_data(root, key, counter);
}

inline int BST::find_data(node* rootptr, int key, int& counter)


{
if (rootptr == nullptr) return 0;
if (rootptr->data.id == key) { counter++; return counter; };
if (key < rootptr->data.id) { counter++; return find_data(rootptr->left, key, counter);
}
if (key > rootptr->data.id) { counter++; return find_data(rootptr->right, key,
counter); }
}

inline void BST::delete_nodes(node* nodeptr)


{
if (nodeptr == nullptr) return;
delete_nodes(nodeptr->left);
delete_nodes(nodeptr->right);
delete nodeptr;
}

Am descris o structura node care contine ca date informatia despre un telefon mobil si are pointeri la
arborele stang si arborele drept.

Arborele contine radacina root de la care pornesc celelalte noduri.

Cautarea: perechea de functii: find(int) si find_data(node*, int, int)


Se efectuiaza recursiv incepand de la nodul radacina:
daca radacina este null → nu a fost gasit;
daca radacina e elementul cautat → se returneaza ca e gasit din prima;
daca cheia e mai mica decat cheia nodului curent, se apeleaza functia recursiv trimitand in
functie drept radacina pointer catre urmatorul nod din partea stanga;
daca cheia e mai mare decat cheia nodului curent, se apeleaza functia recursiv trimitand in
functie drept radacina pointer catre urmatorul nod din partea dreapta;

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);

typedef int (*func)(const telefoane&, int);


func Cautare[4];
Cautare[0] = cautare_secventiala;
Cautare[1] = cautare_binara;
Cautare[2] = cautare_fibonacci;
Cautare[3] = interpoliation;

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;
}

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