Laboratorul 7: Heap si tabele de dispersie

Obiective

În urma parcurgerii acestui laborator studentul va fi capabil:

  • să implementeze algoritmi ce au la baza o structura de tip Heap
  • să selecteze tipul de tabelă de dispersie potrivit pentru o problemă

Structura laboratorului se gaseste in acest link.

1. Heap binar

Proprietați:

  • valoarea unui nod este mai mare (max heap)/mică (min heap) decât valorile tuturor copiilor săi
  • toate frunzele se găsesc pe nivelul h sau h-1, unde h este înălțimea arborelui
  • un heap binar este un arbore complet

Structura Heap:

structura_heap.c
typedef struct
{
    int *vec; 
    int size;
    int capacity;
 
    int type; // Tipul heap-ului, camp optional
}Heap;

Operații de bază:

  • Creare/Distrugere Heap
  • Popularea unui Heap
  • Adăugarea unui Element
  • Ștergere unui Element
  • Extragere element maxim/minim
  • Sortare - Heapsort

1.1 Crearea unei structuri de tip Heap

creare_heap.c
Heap* create(int capacitate)
{
	Heap *h = (Heap *) malloc(sizeof(Heap));
 
	if (h == NULL)
	{
		printf("Nu s-a putut aloca spatiu");
		return;
	}
	h->size = 0; // Initial heap-ul este gol
	h->capacity = capacitate;
 
	h->vec = (int*) calloc(capacitate, sizeof(int));
	if (h->vec == NULL)
	{
		printf("Nu s-a putut aloca spatiu");
		return;
	}
 
	return h;
 
}

1.2 Popularea unui Heap

populare_heap.c
void populateHeap(Heap *h, int *buf, int n)
{
	if(h == NULL || buf == NULL || n ==0) 
		return;
 
	while(n > h->capacity)
		resize(h);
 
	int i = 0;
	for( i = 0; i < n; i++)
		h->vec[i] = buf[i];
	h->size = n;
 
	for(i = (n-1)/2; i >= 0; i--)
		heapify(h, i);
 
}

Etape:

  1. se crează un heap cu o capacitate suficient de mare pentru stocarea elementelor
  2. se mută elementele vectorului in spațiul de stocare al heap-ului
  3. se aplică heapify pe toate nodurile interne

1.3 Adăugarea unui element

adaugare_element.c
void insert(Heap *h, int x)
{
	int i;
	if(h->capacity == h->size)
		resize(h);
	i = h->size;
	h->size++;
 
 
	while(i >= 0 && x > h->vec[parent(h,i)])
	{
		h->vec[i] = h->vec[parent(h,i)];
		i = parent(h,i);
	}
	h->vec[i] = x;
}

Etape:

  1. se verifică dacă avem suficient spațiu in heap, iar dacă nu avem se face o realocare
  2. se adaugă elementul la finalul heap-ului și se compară cu parintele său
  3. dacă este mai mare se interschimbă și se reia etapa 2

1.4 Ștergerea unui Element

Etape:

  1. se mută ultimul element pe poziția elementului de șters
  2. se aplică algoritmul heapify down pe elementul modificat

1.5 Heapsort

Etape:

  1. se redimensionează heap-ul corespunzător
  2. se populează heap-ul cu valorile vectorului pe care dorim să-l sortăm
  3. se aplică heapify pe toate nodurile interne
  4. se interschimbă primul element cu ultimul
  5. se scade dimensiunea heap-ului
  6. se aplică heapify pe poziția 0, apoi se reia până când epuizăm toate elementele

Probleme propuse

  1. Implementați ștergerea unui element dintr-un heap.
  2. Implementați metoda de sortare heapsort.
  3. Aflați eficient cele mai mari/mici k elemente dintr-un vector folosind un heap.
    1. Metoda I: Folositi un min heap de dimensiune k pentru a stoca primele k elemente din vectorul primit ca parametru, apoi parcurgeti restul elementelor din vector. Daca elementul curent este mai mare decat varful heap-ului adaugati elementul in heap in locul varfului. Atentie: Asigurati-va ca dupa ce adaugati elementul refaceti proprietatea de min heap!
    2. Metoda II: Metoda II: Creati un min heap folosind valorile opuse ale elementelor din vectorul primit ca parametru, eliminati pe rand varful heap-ului de k ori, numerele rezultate vor fi numerele cu semn schimbat ale solutiei.
  4. Fie un vector k-sortat de dimensiune n, sortați eficient vectorul k-sortat. Un vector se numeste k-sortat daca fiecăre element se află la maxim k poziții distanță de poziția sa din cadrul vectorului sortat.
    1. Hint: Creati un min-heap de dimensiune k+1, populati-l cu primele k+1 elemente, apoi parcurgeti restul elementelor, la fiecare pas stocati varful in vectorul solutie si adaugati urmatorul element din vector in heap. Nu uitati ca la final sa adaugati si elementele ramase in heap in vectorul solutie
  5. Fie M vectori sortați de diferite dimensiuni. Interclasați vectorii eficient folosind un heap.
    1. Metoda I: Implementati un min heap in care fiecare element este o pereche (valoarea, index_vector). Creati un min heap de acest tip de dimensiune M, introduceti primul element din fiecare vector. La fiecare pas din cadrul algoritmului eliminati radacina din heap, introduceti valoarea in vectorul solutie si adaugati in heap urmatorul element din vectorul corespunzator elementului abia eliminat.
    2. Metoda II: Implementati un min heap (vezi curs), folosind elemente de tip min heap implementati un heap in care fiecare element este un heap de sine statator. Pentru simplitate vom numii heap-ul de heap-uri heap global, iar heap-urile din acesta heap-uri interne. Creati un heap global de dimensiune M si populati fiecare heap intern cu cate unul dintre vectorii sortati, aplicati heapify pe toate nodurile interne ale heap-ului global. Cat timp heap-urile interne inca mai au elemente faceti urmatoarele operatii:
      • Extrageti radacina radacinii heap-ului global si stocati valoarea in vectorul solutie
      • Aplicati heapify pe radacina heap-ului global
      • Aplicati heapify pe heap-ul global

2. Hashtable

O tabelă de dispersie (hashtable) este o structură de date ce realizează o mapare de forma cheie - valoare. Această formă de stocare facilitează accesul rapid la date.

2.1 Tratarea Coliziunilor

  1. Dispersie deschisă(Chaining)
  2. Dispersie închisă(Open addressing)

2.1.1 Dispersia deschisă

În cazul dispersiei deschise se folosesc liste înlănțuite pentru stocarea elementelor cu aceiași cheie. În cel mai nefavorabil caz toate elementele sunt puse la același index rezultând o complexitate O(n) pentru operațiile efectuate.
Mai jos aveți un exemplu de tabelă de dispersie în care coliziunile se tratează prin înlănțuire, iar funcția hash este f(x) = x % 7:

În cazul unei dispersii deschise este foarte important factorul de încărcare a = N/M unde:

  • N - numărul de chei stocate
  • M - numărul slot-uri puse la dispoziție

2.1.2 Dispersia închisă

În cazul dispersiei închise toate elementele se memorează în tablea T, iar la producerea coliziunilor se verifică alte celule, cel mai simplu mod de rezolvare al coliziunilor este cel în care se caută prima celula liberă pentru inserare, dar se pot utiliza si alte metode.
Mai jos aveți un exemplu de tabelă de dispersie în care coliziunile se tratează liniar, iar funcția hash este f(x) = x % 7:

Probleme propuse

  1. Fie structura din josul paginii. Considerați că trebuie stocate pentru căutări/adăugări/ștergeri cât mai rapide 10.000 de înregistrări de tip Student.Implementați folosind:
    1. Tabelă de dispersie cu înlănțuire
    2. Tabelă de dispersie cu adresare închisă
  2. Găsiți toate perechile de mulțimi simetrice dintr-un vector de mulțimi de 2 elemente. Două mulțimi sunt simetrice dacă capătul din stanga al primei mulțimi este egal cu cel din dreapta a celei de-a doua, iar capătul din dreapta al primei este egal cu cel din stanga al celei de-a doua.
  • Exemplu: { [10, 20], [20, 10], [1, 11], [1, 10], [10, 1]} ⇒ {[10, 20], [1, 10]}
  • Hint: Se folosește o singură tabelă de dispersie.
Student.c
typedef struct
{
    int cod; // unic
    char *nume, *prenume, *adresa;
    int medie;
    int an_studii;
}Student;
sda-ab/laboratoare/08.txt · Last modified: 2021/05/24 12:59 by leonard.necula
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0