SDA Lucrarea 6
În acest laborator se vor implementa structuri de date asociative (map) cu arbori binari și funcții hash.
Structura de date asociativă - Map
Structura de date asociativă (map) este o structură de date abstractă care stochează perechi de elemente cheie-valoare, într-o ordine arbitrară stabilită de structură, astfel încât nu pot exista două perechi cu aceeași cheie în map.
În esență, un map este o mulțime (set) de chei care sunt întotdeauna stocate împreună cu o valoare.
Map-ul are următoarele proprietăți:
- Datele sunt plasate într-o ordine oarecare stabilită arbitrar de structură.
- Numărul de elemente ce poate fi stocat de structură este nelimitat.
- Elementele stocate în map sunt de același fel.
- Tipul de date al cheii și tipul de date al valorii pot fi diferite.
- Map-ul poate conține doar chei unice, în baza unei funcții definite de egalitate. Altfel spus, dacă două chei sunt egale din punctul de vedere al map-ului, ele nu pot fi ambele prezente în perechi cheie-valoare în map.
Map-ul suportă următoarele operații de bază:
- Interogarea numărului de elemente din map.
- Verificarea dacă map-ul este gol.
- Adăugarea perechi cheie-valoare (insert) - dacă cheia există deja în map, aceasta nu se înlocuiește.
- Verificarea dacă o cheie există în map (count).
- Căutarea unei valori după o cheie dată (at);
- Eliminarea unei chei din map (erase).
Implementarea de structuri asociative cu funcții hash
Definirea funcțiilor hash și a proprietăților lor s-a făcut în laboratorul 5 (SDA Lucrarea 5#Implementarea de mulțimi cu funcții hash).
Tipul de date și funcțiile de bază
În continuare vom prezenta un exemplu de map de la CNP (cheia) la elemente de tip "persoană" (valoarea), implementată cu funcții hash. În primul rând vom defini structura ce memorează datele legate de o persoană:
struct Person {
char firstName[30];
char lastName[30];
char idNumber[9]; // seria si numarul de buletin
char address[255];
char birthday[11];
char cnp[14];
};
Pentru a implementa o funcție hash pentru variabilele de tip char *
(cheia este CNP-ul care este stocat ca un șir de caractere), avem întâi nevoie să definim operația de egalitate:
/**
* Functia intoarce 1 daca CNP-urile celor doua persoane sunt egale.
* @param cnp1 primul CNP de comparat.
* @param cnp2 al doilea CNP de comparat.
* @return 1 daca CNP-urile celor doua persoane sunt identice.
*/
char equals(char * cnp1, char * cnp2);
Mai departe definim funcția hash pentru un CNP.
Pentru a putea implementa un map cu funcții hash trebuie ca hash-ul maxim să fie o valoare rezonabilă pentru a putea aloca memorie cu dimensiunea valorii respective.
Din motivul de mai sus, și pentru a micșora posibilitatea unei coliziuni de hash ce va micșora performanța structurii, ieșirea funcției hash va fi un număr între 0 și 999999 obținut din ultimele 6 cifre ale CNP-ului convertite din text în număr (pentru conversie puteți folosi funcția atoi):
/**
* Functie hash pentru un CNP stocat ca string.
* @param cnp CNP-ul pentru care se doreste codul hash.
* @return codul hash al CNP-ului.
*/
unsigned hash(char * cnp);
În continuare vom utiliza pentru implementarea unui Hash Map o structură de date de tip secvență împlementată cu liste (LinkedList) și vom folosi funcțiile deja definite de la laboratoarele anterioare:
Elementul stocat în listă este un element de tip Pair
care stochează împreună o cheie cu o valoare:
struct Pair {
char * key; /* CNP-ul */
struct Person value; /* Persoana */
};
Structura HashMap
Pentru stocarea datelor necesare map-ului, definim următoarea structură:
#define MAX_HASH 1000000
struct HashMap {
struct LinkedList array[MAX_HASH]; // un vector de liste, una pentru fiecare valoare posibila de hash
unsigned size; // numarul de elemente din map
};
Crearea unui HashMap
Pentru crearea unui HashMap
, se definește următoarea funcție:
/**
* Functia creeaza un hashMap nou si intoarce adresa de memorie alocata.
* @return un pointer la o structura de tip HashMap.
*/
struct HashMap * createHashMap();
Pentru crearea unui HashMap
se urmează următorii pași:
- Se alocă memorie pentru un element de tip
struct HashMap
folosind funcția corectă pentru reseta memoria la alocare (scrierea ei cu 0). - Se inițializează
size
cu 0. - Se întoarce adresa elementului de tip
struct HashMap
.
- Complexitate în timp: O(1)
- Complexitate în spațiu: O(MAX_HASH)
Interogarea numărului de elemente din map
Pentru interogarea numărului de elemente din map definim:
/**
* Functia intoarce numarul de elemente din map.
* @param map map-ul pentru care se cere dimensiunea.
* @return numarul de elemente din map.
*/
unsigned hashMapSize(struct HashMap * map);
Funcția va întoarce valoarea din câmpul size
din structură.
- Complexitate în timp: O(1)
- Complexitate în spațiu: O(1)
Pentru a afla dacă map-ul este gol definim:
/**
* Functia intoarce 1 dacă map-ul nu conține nici un element.
* @param map map-ul de interes.
* @return 1 dacă map-ul este gol, 0 în rest.
*/
char hashMapIsEmpty(struct HashMap * map);
Funcția va întoarce 1 dacă valoarea din câmpul size
este 0.
- Complexitate în timp: O(1)
- Complexitate în spațiu: O(1)
Adăugarea unei perechi cheie-valoare în map
Pentru operația de adăugare se definește următoarea funcție:
/**
* Functia adauga perechea data in map daca cheia nu exista. Daca
* cheia exista deja, valoarea si cheia sunt actualizate.
* @param map map-ul in care trebuie adaugata perechea.
* @param key cnp-ul folosit pe post de cheie.
* @param value persoana asociata CNP-ului.
*/
void hashMapPut(struct HashMap * map, char * key, struct Person value);
Adăugarea se realizează în felul următor:
- Se aplică funcția hash pe variabila
key
. - Folosind valoarea obținută se accesează lista de pe poziția hash-ului din
array
. - Se iterează peste nodurile listei căutând cheia
key
în pereche, folosind funcțiaequals
. Dacă cheia este găsită, atunci cheia și valoarea din pereche sunt actualizate cu valorile argumentelorkey
șivalue
și funcția se încheie; altfel, se crează un nod nou care se adaugă listei care va conține perecheakey
-value
, dimensiunea listei și dimensiunea map-ului se incrementează.
- Complexitate în timp: O(1) best case (fără coliziuni), O(1) average case pentru număr mai mic de 1000000 de persoane, O(n) worst case (același hash pentru poate persoanele).
- Complexitate în spațiu: O(1)
array
conține variabile de tip struct LinkedList
și nu pointeri, pentru a folosi funcțiile definite în hashMapLinkedList.h e necesară obținerea adresei variabilei de tip struct LinkedList
folosind operatorul &.Eliminarea unei chei din map
Pentru operația de eliminare se definește următoarea funcție:
/**
* Functia elimina cheia data din map daca acesta exista. Daca
* nu exista, functia nu are nici un efect.
* @param map map-ul din care trebuie eliminat elementul.
* @param key cheia (CNP-ul) ce trebuie eliminata.
*/
void hashMapRemove(struct HashMap * map, char * key);
Eliminarea se realizează în felul următor:
- Se aplică funcția hash pe variabila
key
. - Folosind valoarea obținută se accesează lista de pe poziția hash-ului din
array
. - Se folosește funcția
linkedListSearch
pentru a verifica dacăperson
există în listă. - Dacă valoarea întoarsă este diferită de -1 atunci se folosește funcția
linkedListDelete
pentru a șterge elementul de pe poziția respectivă și size se decrementează cu 1.
- Complexitate în timp: O(1) best case (fără coliziuni), O(1) average case pentru număr mai mic de 1000000 de persoane, O(n) worst case (același hash pentru poate persoanele).
- Complexitate în spațiu: O(1)
Verificarea dacă o cheie există în map
Pentru a verifica dacă o cheie există în map, se definește următoarea funcție:
/**
* Functia intoarce 1 daca cheia specificata exista in map.
* @param map map-ul in care se cauta cheia.
* @param key cheia (CNP-ul) cautata.
* @return 1 daca cheia exista in map, 0 daca nu.
*/
char hashMapHasKey(struct HashMap * map, char * key);
Verificarea unei chei dacă este sau nu în map se realizează în felul următor:
- Se aplică funcția hash pe variabila
key
. - Folosind valoarea obținută se accesează lista de pe poziția hash-ului din
array
. - Se folosește funcția
linkedListSearch
pentru a verifica dacăkey
există în listă. - Dacă valoarea întoarsă este diferită de -1 atunci se întoarce 1, altfel se întoarce 0.
- Complexitate în timp: O(1) best case (fără coliziuni), O(1) average case pentru număr mai mic de 1000000 de persoane, O(n) worst case (același hash pentru poate persoanele).
- Complexitate în spațiu: O(1)
Căutarea unei valori după cheie
Pentru a căuta o valoare după cheie, se definește urmtoarea funcție:
/**
* Functia intoarce valoarea asociată cheii key.
* @param map map-ul in care se cauta cheia.
* @param key cheia (CNP-ul) cautata.
* @return valoarea asociată cheii.
*/
struct Person hashMapGet(struct HashMap * map, char * key);
Căutarea unei valori după cheie se realizează în felul următor:
- Se aplică funcția hash pe variabila
key
. - Folosind valoarea obținută se accesează lista de pe poziția hash-ului din
array
. - Se iterează peste nodurile din listă și se caută cheia folosind funcția
equals
. - Dacă cheia a fost găsită, se întoarce valoarea asociată; dacă s-a ajuns la sfârșitul listei fără a se găsi cheia, se întoarce o structură de tip
Person
, indiferent cu ce valori (comportament nedefinit).
- Complexitate în timp: O(1) best case (fără coliziuni), O(1) average case pentru număr mai mic de 1000000 de persoane, O(n) worst case (același hash pentru poate persoanele).
- Complexitate în spațiu: O(1)
Ștergerea unui HashMap
Pentru dezalocarea unui HashMap
, se definește următoarea funcție:
/**
* Functia sterge hash map-ul specificat.
* @param map hash map-ul ce trebuie sters.
*/
void deleteHashMap(struct HashMap * map);
Pentru ștergerea unui HashMap
se urmează următorii pași:
- Se iterează peste tot
array
-ul, ștergându-se toate nodurile din toate listele. - Se șterge memoria alocată pentru structura de tip
HashMap
.
- Complexitate în timp: O(MAX_HASH)
- Complexitate în spațiu: O(1)