Sunteți pe pagina 1din 48

Structuri de baz:

liste, cozi, stive


CURS NR. 8/1

12/5/2016 1
Structuri de date
Structuri de date primitive tipurile fundamentale din limbajul C
char, int, float, double, void

Structuri de date abstracte prin gruparea structurilor de datele nrudite, utilizatorul poate defini
structuri agregate care permit operarea uoar cu datele i execuie eficient
Tablouri, Liste, Stive, Cozi, Arbori, Tabele de dispersie, Grafuri, etc.

Fiecare structur permite efectuarea unor anumite operaii asupra datelor

Structuri de date

Structuri de date primitive Structuri de date abstracte

char float pointer Structuri liniare Structuri ierarhice Fiiere


int double
Tablouri Liste Arbori Heap-uri
2
Stive Cozi Grafuri
Structuri de date i algoritmi
Un algoritm este o succesiune finit, ordonat i bine definit de instruciuni care constituie o metod
corect de rezolvare a unei probleme pornind dintr-o stare iniial, folosind datele disponibile i
ajungnd n starea final dorit

Algoritmii lucreaz cu date, care trebuie stocate n memorie sub forma unor structuri de date adecvate
Alegem anumite structuri de date pe baza modului de stocare i a operaiilor permise asupra datelor de ctre acea structur

Algoritmul adecvat trebuie s fie corect, eficient i uor de implementat

Performana algoritmului se msoar n


Spaiu: dimensiunea memoriei consumate
Spaiul ocupat de structurile de date, spaiul ocupat de executabil, spaiul necesar stocrii strii unei funcii suspendate

Timp: timpul necesar pentru execuia complet
Se estimeaz pe baza contorizrii numrului de instruciuni (atribuiri, comparaii) efectuate de algoritm asupra datelor
Acesta poate varia n funcie de natura datelor de intrare
Cel mai adesea considerm cazul cel mai defavorabil (worst-case time complexity) 3
Complexitatea n timp
Metode de calcul
Metrica cea mai popular este notaia O mare (big O notation) notaie asimptotic
Elimin factorii constani i timpul de rulare se estimeaz ca funcie a numrului de instruciuni de baz (atribuiri, comparaii)

instructiune;
Complexitate constant timpul de rulare nu depinde de N O(1)

for(i=0; i < N; i++) { Complexitate liniar timpul de rulare este direct proporional cu N
instructiune; O(N)
} Dac N se dubleaz, timpul de rulare se dubleaz

for(i=0; i < N; i++) {


for(j=0; j < N;j++) { Complexitate cvadratic timpul de rulare este direct
instructiune; proporional cu N2 (n la ptrat) O(N2)
}
} Dac N se dubleaz, timpul de rulare crete cu un factor de N*N

while(low <= high) {


mid = (low + high) / 2;
if (target < list[mid]) Complexitate logaritmic timpul de rulare este direct
high = mid - 1; proporional cu numrul de ori N poate fi divizat la 2
else
if (target > list[mid]) Spaiul de lucru este divizat n dou la fiecare iteraie
low = mid + 1;
else break; O(logN) 4
}
Complexitatea
Notaia Denumire
O-mare complexitate
O(1) Constant
O(log n) Logaritmic
O(n) Liniar
O(n log n) Log-liniar
O(n2) Cvadratic
O(n3) Cubic
O(2n) Exponenial

Big-O ne arat cum este afectat performan de dimensiunea datelor de intrare


Necesarul de timp dac se dubleaz dimnesiunea input-ului:
O(1) : necesit aceeai durat de timp
O(log n) : timpul necesar este mai mult cu o durat constant
O(n) : necesit timp dublu
O(n log n) : necesit timp dublu plus un pic mai mult
O(n2) : necesit de patru ori mai mult timp
O(2n) : la fiecare element adugat timpul necesar se dubleaz 5
Tip de dat abstract
Conceptul de tip de dat ne este cunoscut
ntreg, tablou de caractere, etc. i operaiile asupra acestora sunt definite de limbaj (ex. Acces la elementul unui vector numere: numere[3])

Motivaie pt tipuri de date abstracte


Problem: Programele mari trebuie s fie uor modificabile
Modificrile implic schimbri la nivelul structurilor de date care stocheaz datele
Ex. Un ir de ntregi implementat ca un vector va fi schimbat n list simplu nlnuit, sau se mai adaug un nou cmp ntr-o structur, etc.
Modificrile s nu implice rescrierea ntregului cod care folosete acele structuri de date!

Soluie: definirea unui model matematic pentru structura de date, adic desprirea modelului de comportament al
datei de modul de implementare
Se definete un container care poate reine un numr finit de obiecte
Se definete relaia dintre obiecte din container (ordonare liniar, ierarhic, etc.)
Se definete un set de operaii asupra containerului respectiv asupra obiectelor din container (ex. adaugare de obiect, tergere, cutare, etc.)

Exemple
Stiva are definite urmtoarele operaii: adugare, tergere, testare dac este gol, etc.
i poate fi implementat ca un vector , o list simplu nlnuit, etc.

Structur de cutare are definite urmtoarele operaii: adugare, tergere, cutare, etc.
i poate fi implementat ca un vector , o list simplu nlnuit, arbore, tabel de dispersie, etc.
6
Tipul de dat abstract LIST
Model matematic:
secven de elemente (A0, A1, A2, ..., An-1), unde Ai precede Ai+1, unde 0 <= i <= n-1

Operaii
Inserare
tergere
Cutare
Traversare afiare
Inversare
etc.

Structur liniar

Implementare
Tablou unidimensional
List dinamic
Simplu nlnuit
Dublu nlnuit
Circular 7
Liste simplu nlnuite
Pentru stocarea unei colecii de elemente alternativa la tablouri
Lista dinamic simplu nlnuit
este o nlnuire de elemente (structuri) numite noduri, n care fiecare nod include un pointer (o legtur) la urmtorul nod din lan
Ultimul nod conine un pointer nul

Avantaj fa de tablouri
Flexibilitate mare inserarea i tergerea foarte uoar permite creterea i descreterea listei n mod dinamic
Fiecare adugare de nod presupune o nou alocare a unui spaiu pentru un nod
Fiecare tergere de nod presupune eliberarea zonei de memorie ocupate de nod

Dezavantaj fa de tablouri
Se pierde accesul direct la elemente
n tablou toate elementele sunt accesibile n mod direct prin specificarea indexului
Elementele din list sunt accesibile doar secvenial pornind de la nceputul listei ctre final
Nu beneficiaz de principiul localitii datelor n memoria cache
8
Liste simplu nlnuite
Declararea unui nod Definirea tipului NOD
Exemplu: pentru simplitate nodul conine
un singur ntreg i pointerul ctre urmtorul nod

Eticheta
struct nod { structurii typedef struct nod {
int data; int data;
struct nod *urmator; struct nod *urmator;
}; } NOD;
pointer ctre urmtorul nod

Eticheta structurii este esenial


Structura are ca i componenet un pointer la o structur de acelai tip care tocmai se declar / definete

nceputul listei capul listei


O variabil care indic ntotdeauna nceputul listei pointer la primul nod din list

struct nod *prim; NOD *prim;

List vid: prim = NULL; 9


Liste simplu nlnuite
Crearea unui nod implic
Alocarea dinamic a memoriei pentru nod
Stocarea datei n nod
Inserarea nodului n list Variabil temporar pentru a indica
noul nod pn la inserarea n list
struct nod *nou;
Operatorul sizeof primete argumentul struct nod i nu nou
new = malloc(sizeof(struct nod)); - nou este doar un pointer!

nou 10

Stiv Heap

new->data = 10; Echivalent cu (*new).data = 10;

Inserare n lista vid


Noul nod poate fi inserat n orice poziie din list
Exemplu: inserarea la nceputul listei prim

nou->urmator = prim;
prim = nou; nou 10 10
Inserarea unui nou nod n list
Inserarea nodului nou dup nodul p

nou->urmator = p->urmator;

p->urmator = nou;

11
Inserarea unui nou nod n list
k
Inserarea nodului newVtx pe poziia k

Parcurgem lista de la nceput


Pn la poziia dorit

newVtx->next = temp1->next;

temp1->next = newVtx;

12
Inserarea unui nou nod n list
typedef struct nod {
int data;
struct nod *urmator;
} NOD;

Inserare n capul listei la nceput Inserare dup un nod specificat


Pointer la pointer: Capul listei
se modific n funcie
void inserare_inceput(NOD **prim, int n) { void inserare_dupa(NOD *p, int n) {
NOD *nou; NOD *nou;
nou = (NOD *)malloc(sizeof(NOD)); nou = (NOD *)malloc(sizeof(NOD));
if (nou == NULL) { if (nou == NULL) {
perror("eroare la alocare - in inserare"); perror("eroare la alocare - in inserare");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
nou->data = n; nou->data = n;

nou->urmator = *prim; nou->urmator = p->urmator;


*prim = nou; p->urmator = nou;
} }

Apel: NOD * prim = NULL;


// .....
inserare_inceput(&prim, 10); 13
tergerea unui nod din list
Alegerea nodului victim: c (curent)
Prin cutare n list
Nodul de dup nodul p (precedent)

p->urmator = c->urmator;

c->urmator = NULL;

free(c);

14
tergere
Operaia de tergere implic Trebuie s meninem pointer la nodul curent i cel precedent
Localizarea nodului de ters (victima)
Modificare nodului precedent pentru a ocoli nodul victim
Eliberarea spaiului ocupat de nodul victim cu free

curent = prim, precedent = NULL;


while (curent != NULL && curent->data != n) {
NOD *stergere_nod(NOD *prim, int n) { precedent = curent;
NOD *curent, *precedent; curent = curent->urmator;
}
for (curent = prim, precedent = NULL;
Cutarea nodului cu data n Echivalent cu
curent != NULL && curent->data != n;
precedent = curent, curent = curent->urmator)
;

if (curent == NULL)
return prim; // n NU a fost gasit
if (precedent == NULL)
prim = prim->urmator; // n este in primul nod tergerea primului nod este un caz special
else // n este intr-un alt nod
precedent->urmator = curent->urmator;

free(curent);
return prim;
}

15
Exemplu: liste simplu nlnuite
Inventarul de produse dintr-un depozit
Baza de date cu produsele este stocat ntr-o list simplu nlnuit
Avantaj:
fr limite prestabilite pentru dimensiune alocare dinamic n timpul rulrii
Adugarea i tergerea facil
Meninerea ordonat a produselor prin adugare n poziia corespunztoare

Structura pentru un nod din list


#define NAME_LEN 25

struct part {
int number; Pointer ctre
char name[NAME_LEN + 1]; nodul urmtor
int on_hand;
struct part *next;
};

struct part *inventory = NULL; /* pointer la inceputul listei */

struct part *find_part(int number);


void insert(void); Prototipurile funciilor
void search(void);
void update(void);
void print(void);

16
Exemplu: liste simplu nlnuite
Funcia principal
Utilizatorul introduce o opiune i se execut funcia corespunztoare
Se repet ntr-un ciclu pn cnd utilizatorul introduce opiunea q
Se afieaz mesaj de eroare dac opiunea este invalid

int main(void) {
char code;

for (;;) {
printf("Enter operation code: ");
scanf(" %c", &code);
while (getchar() != '\n') /* se ignora restul liniei */
; /* alternativa: fflush(stdin); */
switch (code) {
case 'i': insert(); break;
case 's': search(); break;
case 'u': update(); break;
case 'p': print(); break;
case 'q': return 0;
default: printf("Illegal code\n");
}
printf("\n");
}
}

17
Exemplu: liste simplu nlnuite
Funcia de regsire produs
Cutare pe baza numrului de produs
struct part *find_part(int number) {
struct part *p;

for (p = inventory; Cutarea produsului


p != NULL && number > p->number;
p = p->next)
;
if (p != NULL && number == p->number)
return p; Produs regsit -> se returneaz pointer la nod
return NULL;
Returneaz NULL dac nu s-a regsit numrul de produs cutat
}

Funcia de cutare i afiare a numrul de produse n stoc


void search(void) {
int number;
struct part *p;
Numrul de produs introdus de utilizator
printf("Enter part number: ");
scanf("%d", &number);
p = find_part(number); Regsirea nodului care conine produsul
if (p != NULL) {
printf("Part name: %s\n", p->name); Produs regsit -> se afieaz numele i stocul disponibil
printf("Quantity on hand: %d\n", p->on_hand);
}
else
printf("Part not found.\n"); 18
}
Exemplu: liste simplu nlnuite
void insert(void) {
Funcia de inserare struct part *cur, *prev, *new_node;

new_node = malloc(sizeof(struct part));


if (new_node == NULL) {
printf("Database is full; can't add more parts.\n");
return;
}

printf("Enter part number: "); scanf("%d", &new_node->number);

for (cur = inventory, prev = NULL;


cur != NULL && new_node->number > cur->number;
prev = cur, cur = cur->next)
;
if (cur != NULL && new_node->number == cur->number) {
printf("Part already exists.\n");
free(new_node);
return;
}

printf("Enter part name: "); fgets(new_node->name, NAME_LEN, stdin);


new_node->name[strlen(new_node->name)] = '\0';
printf("Enter quantity on hand: ");
scanf("%d", &new_node->on_hand);

new_node->next = cur;
if (prev == NULL)
inventory = new_node;
else
prev->next = new_node;
} 19
Exemplu: liste simplu nlnuite
Funcia de actualizare
void update(void) {
int number, change;
struct part *p;

printf("Enter part number: ");


scanf("%d", &number);
p = find_part(number);
if (p != NULL) {
printf("Enter change in quantity on hand: ");
scanf("%d", &change);
p->on_hand += change;
}
else
printf("Part not found.\n");
}

Funcia de afiare
void print(void) {
struct part *p;

printf("Part Number Part Name ""Quantity on Hand\n");


for (p = inventory; p != NULL; p = p->next)
printf("%7d %-25s%11d\n", p->number, p->name,
p->on_hand);
} 20
Liste dublu nlnuite
Declararea unui nod Definirea tipului NOD
Exemplu: pentru simplitate nodul conine
un singur ntreg i pointerul ctre urmtorul nod

Eticheta
struct nod_d { structurii typedef struct nod_d {
int data; int data;
struct nod *precedent, struct nod *precedent,
*urmator; *urmator;
}; } NODD;
pointer ctre nodul
precedent i urmtor
Eticheta structurii este esenial
Structura are ca i componenet un pointer la o structur de acelai tip care tocmai se declar / definete

nceputul listei capul listei


O variabil care indic ntotdeauna nceputul listei la primul nod din list

struct nod_d *prim; NODD *prim;

List vid: prim = NULL; 21


Liste dublu nlnuite
Crearea unui nod implic
Alocarea dinamic a memoriei pentru nod
Stocarea datei n nod
Inserarea nodului n list Variabil temporar pentru a indica
noul nod pn la inserarea n list
struct nod_d *nou;
nou = malloc(sizeof(struct nod_d));

nou 10 Pointer ctre


nodul urmtor
Stiv Heap
Pointer ctre
nodul precedent
nou->data = 10; Echivalent cu (*nou).data = 10;

prim Inserare n lista vid


Noul nod poate fi inserat n orice poziie din list
Exemplu: inserarea la nceputul listei
nou 10
nou->urmator = prim;
prim = nou; 22
Inserarea n lista dublu nlnuit
k
Inserarea nodului newVtx pe poziia k
Parcurgem lista de la nceput
Pn la poziia dorit
temp1->next

newVtx->next = temp2;

temp2->prev = newVtx;

temp1->next->prev

temp1->next = newVtx;

newVtx->prev = temp1;

23
tergerea din lista dublu nlnuit
k
tergerea nodului del de pe poziia k

Parcurgem lista de la nceput


Pn la poziia dorit

del->prev del->next

prev->next = after;

after->prev = prev;

del->next->prev

free(del);

24
Liste nlnuite circulare
De dou tipuri
Liste simplu nlnuite circulare
Liste dublu nlnuite circulare

Ultimul i primul nod sunt adiacente


Ultimul nod pointeaz ctre primul nod din list
Nodul urmtor ultimului nod este primul nod
n plus, la dublu nlnuite: precedentul primului nod este ultimul nod

25
Stiva
Tipul de dat abstract STIVA este o colecie secvenial de elemente asupra crora se impune accesul
n regim LIFO Last In First Out
Elemetele sunt ordonate astfel nct, la orice moment doar cel mai recent element adugat poate fi extras

Acces restricionat, doar prin operaiile fundamentale:


Push: inserare n vrful stivei (la nceput)
Pop: tergere din vrful stivei (de la nceput)
i returneaz elementul extras
Eroare dac stiva este vid

Alte operaii
size: numrul de elemente din stiv
isEmpty: testare stiv vid
top: returneaz elementul din vrful stivei fr s extrag / tearg elementul
Dac stiva este goal - mesaj de eroare

Implementare
Vector implementare simpl, dar capacitatea maxim este fixat
List simplu nlnuit implementare mai complex, dar capacitatea este flexibil (limitat doar de memoria disponibil)

Utilizare frecven
Vezi alocarea variabilelor locale, recursivitate (creterea stivel la autoapeluri stack frame)

26
push & pop
Push inserare n vrful stivei

temp->next = head;
head = temp;

Pop tergere din vrful stivei

Dac stiva este vid mesaj de eroare


temp = head;
head = head->next;

free(temp);
27
Coada
Tipul de dat abstract COADA este o colecie secvenial de elemente asupra crora se impune
accesul n regim FIFO First In First Out
Elemetele sunt ordonate astfel nct, la orice moment doar elementul cel mai de mult (cel mai vechi) poate fi extras

Acces restricionat, doar prin operaiile fundamentale:


Enqueue: inserarea la finalul cozii (la sfrit)
Dequeue: tergerea din vrful cozii (de la nceput)
i returneaz elementul extras
Eroare dac coada este vid

Alte operaii
size: numrul de elemente din coad
isEmpty: testare coad vid
front: returneaz elementul de la nceputul cozii fr s extrag / tearg elementul
Dac coada este goal - mesaj de eroare

Implementare eficient
Pentru operaii n timp constant O(1)
List dublu nlnuit att inserarea ct i teregerea sunt operaii simple, deoarece ambele capete sunt accesibile direct
List simplu nlnuit accesul este direct doar la un capt, dar pentru eficientizare se menine un pointer i la finalul listei
Ambele capete devin direct accesibile
28
Coada
Enqueue adugare la sfritul cozii

tail->next = temp;

tail = temp;

Dequeue extragere din vrful cozii

Dac stiva este vid mesaj de eroare


temp = head;
head = head->next;

free(temp);
29
Concluzii pentru liste

Lista este structura potrivit dac sarcina de efectuat implic inserri i tergeri de elemente din
mijlocul (interiorul) listei la care avem deja un pointer
Impic de regul un cost de O(1)

Fa de tablouri unde trebuie copiate toate elementele de dup punctul de inserare pentru a face loc noului element
Dac inserarea i tergerea se efectueaz numai la un singur capt atunci tabloul poate oferi performane mai bune

Listele nu sunt potrivite pentru sarcini care implic acces direct la elemente
Accesul presupune parcurgere --> O(n)
Tabloul poate fi o soluie mai bun pentru astfel de cazuri, NS
dac se dorete inserare / tergere n mijloc, atunci trebuie s apelm la ARBORI

30
Exemplu - stiva
Implementare modular /* stack.h (K. N. King, page 488) */
Fiierul header al modulului interfaa
S ne reamintim de la programare modular ... #ifndef STACK_H
#define STACK_H

#include <stdbool.h> /* C99 only */

void make_empty(void);
bool is_empty(void);
bool is_full(void);
Dou abordri n implementarea stivei void push(int i);
Vector (stack1.c) int pop(void);
List simplu nlnuit (stack2.c) #endif

Ambele implementri se potrivesc cu interfaa


Putem folosi oricare - putem schimba de la una la alta
Ascunderea informaiei clienii modulului nu trebuie s cunoasc modul de implementare (vector sau list)
Avantaje:
Securitate: anse mai mici de corupere dac nu se cunoate modul de stocare a stivei
Flexibilitate: modificrile sunt uor de integrat schimbrile afecteaz doar implemenetarea
Interfaa rmne neschimbat - dac a fost bine proiectat
31
Exemplu stiva implementat ca vector
Impementarea stack1.c
fiierul surs Rol: ascunderea informaiei
static private acestui fiier surs.
Nu sunt accesibile direct din afar

/* stack1.c (K. N. King, page 488) */ bool is_empty(void) {


#include <stdio.h> return top == 0;
#include <stdlib.h> }
#include "stack.h"
bool is_full(void) {
#define STACK_SIZE 100 return top == STACK_SIZE;
}
static int contents[STACK_SIZE];
static int top = 0; void push(int i) {
if (is_full())
terminate("Error in push: stack is full.");
static void terminate(const char *message) { contents[top++] = i;
printf("%s\n", message); }
exit(EXIT_FAILURE);
} int pop(void) {
if (is_empty())
void make_empty(void) { terminate("Error in pop: stack is empty.");
top = 0; return contents[--top];
} }

32
Exemplu stiva ca list nlnuit
Impementarea stack2.c
fiierul surs
/* stack2.c (K. N. King, page 488) */
#include <stdio.h>
#include <stdlib.h> void push(int i) {
#include "stack.h" struct node *new_node = malloc(sizeof(struct node));
if (new_node == NULL)
struct node { terminate("Error in push: stack is full.");
int data;
struct node *next; new_node->data = i;
}; new_node->next = top;
top = new_node;
static struct node *top = NULL; }

static void terminate(const char *message) { int pop(void) {


printf("%s\n", message); struct node *old_top;
exit(EXIT_FAILURE); int i;
}
if (is_empty())
void make_empty(void) { terminate("Error in pop: stack is empty.");
while (!is_empty())
pop(); old_top = top;
} i = top->data;
top = top->next;
bool is_empty(void) { free(old_top);
return top == NULL; Returneaz fals, pt. C implementarea return i;
} list nu are constrngere de capacitate }

bool is_full(void) {
return false; 33
}
Exemplu stiva ca tip de dat abstract (ADT)
Dezavantajul modulului descris precedent
Nu putem avea mai multe instane de stiv

Soluie: definim un nou tip: Stack


Utilizabil de clieni, chiar fr a ti ce este de fapt n spate acest tip (vector, list, structur, etc.)

/* stackADT.h (K. N. King, page 496) */

#ifndef STACKADT_H Rol: ncapsulare


#define STACKADT_H
Tip de dat abstract. Interfaa nu reveleaz modul reprezentare a tipului Stack
#include <stdbool.h> /* C99 only */
Dac s-ar fi inclus n header definirea structurii Stack,
typedef int Item; atunci clienii ar putea folosi direct compoenentele structurii,
fr a apela la funciile specifice, puse la dispoziie de modul.
typedef struct stack_type *Stack; -> coruperea stivei

Necesare Stack create(void);


pentru ADT void destroy(Stack s);
Tipuri incomplete
void make_empty(Stack s); Abordarea furnizat de C pentru ncapsulare
bool is_empty(Stack s);
Tipuri care descriu obiecte dar lipsete informaia necesar pentru
bool is_full(Stack s);
void push(Stack s, Item i);
determinarea spaiului ocupat
Item pop(Stack s); Exemplu: struct t;
#endif struct t s; typedef struct t *T; 34
Exemplu stiva ca tip de dat abstract (ADT)
Implementarea tipului de dat abstract stiv folosind un vector alocat static
/* stackADT.c (K. N. King, page 496) */

#include <stdio.h>
#include <stdlib.h> void make_empty(Stack s) {
#include "stackADT.h" s->top = 0;
}
#define STACK_SIZE 100
bool is_empty(Stack s) {
struct stack_type { return s->top == 0;
Item contents[STACK_SIZE]; }
int top;
}; bool is_full(Stack s) {
return s->top == STACK_SIZE;
static void terminate(const char *message) { }
printf("%s\n", message);
exit(EXIT_FAILURE); void push(Stack s, Item i) {
} if (is_full(s))
terminate("Error in push: stack is full.");
pointer Stack create(void) { s->contents[s->top++] = i;
Stack s = malloc(sizeof(struct stack_type)); }
if (s == NULL)
terminate("Error in create: stack could not be created."); Item pop(Stack s) {
s->top = 0; if (is_empty(s))
return s; terminate("Error in pop: stack is empty.");
} return s->contents[--s->top];
}
void destroy(Stack s) {
free(s);
} 35
Exemplu stiva ca tip de dat abstract (ADT)
Implementarea tipului de dat abstract stiv folosind un vector alocat dinamic
/* stackADT2.c (K. N. King, page 496) */
void destroy(Stack s) {
#include <stdio.h> modificri free(s->contents);
#include <stdlib.h> free(s);
#include "stackADT2.h" }

struct stack_type { void make_empty(Stack s) {


Item *contents; s->top = 0;
int top; }
int size;
}; bool is_empty(Stack s) {
return s->top == 0;
static void terminate(const char *message) { }
printf("%s\n", message);
exit(EXIT_FAILURE); bool is_full(Stack s) {
} return s->top == s->size;
}
Stack create(int size) {
Stack s = malloc(sizeof(struct stack_type)); void push(Stack s, Item i) {
if (s == NULL) if (is_full(s))
terminate("Error in create: stack could not be created."); terminate("Error in push: stack is full.");
s->contents = malloc(size * sizeof(Item)); s->contents[s->top++] = i;
if (s->contents == NULL) { }
free(s);
terminate("Error in create: stack could not be created."); Item pop(Stack s) {
} if (is_empty(s))
s->top = 0; terminate("Error in pop: stack is empty.");
s->size = size; return s->contents[--s->top];
return s; }
} 36
Exemplu stiva ca tip de dat abstract (ADT)
Implementarea tipului de dat abstract stiv folosind o list simplu nlnuit
/* stackADT3.c (K. N. King, page 500) */

#include <stdio.h>
#include <stdlib.h>
#include "stackADT.h"

struct node { bool is_empty(Stack s) {


Item data; return s->top == NULL;
struct node *next; }
};
bool is_full(Stack s) {
struct stack_type { return false;
struct node *top; }
};
void push(Stack s, Item i) {
static void terminate(const char *message) { struct node *new_node = malloc(sizeof(struct node));
printf("%s\n", message); if (new_node == NULL)
exit(EXIT_FAILURE); terminate("Error in push: stack is full.");
}
new_node->data = i;
Stack create(void) { new_node->next = s->top;
Stack s = malloc(sizeof(struct stack_type)); s->top = new_node;
if (s == NULL) }
terminate("Error in create: stack could not be created.");
s->top = NULL; Item pop(Stack s) {
return s; struct node *old_top;
} Item i;

void destroy(Stack s) { if (is_empty(s))


make_empty(s); terminate("Error in pop: stack is empty.");
free(s);
} old_top = s->top;
i = old_top->data;
void make_empty(Stack s) { s->top = old_top->next;
while (!is_empty(s)) free(old_top);
pop(s); return i; 37
} }
Tipuri de date abstracte
Reguli i recomandri
Principiul lui parna legat de ascunderea informaiei: limitele abstractizrii trebuie s fie ct de strmte
Dezvoltatorul de component software trebuie s furnizeze utilizatorului toate informaiile necesare pentru a utiliza eficient
serviciile furnizate de component dar nimic mai mult!
Dezvoltatorului de component software trebuie s i se furnizeze toate informaiile necesare pentru a putea furniza serviciile
dorite - i nimic mai mult!

Deci - se furnizeaz minimul posibil de funcii de acces i modificare a ADT-ului

ADT-ul furnizeaz nivelul corespunztor de abstractizare dac operaiile furnizate sunt naturale dat
fiind descrierea de nivel nalt

Cnd s folosim ADT-uri? oricnd putem ct de des

Cum s folosim ADT-uri?


Abordare:
Descrierea modului de funcionare a programului
Pentru fiecare entitate / concept / substantiv identificm tipul corespunztor de dat
Tip predefinit sau ADT

38
Tabele de dispersie (tabele de hashing)
Mapeaz domeniul cheilor posibile n spaiul de adrese disponibil (dimensiunea tabelei)
Tabelele de dispersie implementeaz tipul mulime (set) sau tipul dicionar

Ideea de baz
Considerm un tablou de dimnesiunea m
Tablourile au avantajul accesului direct la elemente cost O(1)
Definim o funcie de dispersie care mapeaz cheile la indicii tabelului valori n domeniul {0, , m-1}
Pentru valori ntregi putem avea funcia: h(k) = k mod m
Valoarea k se insereaz / terge / acceseaz la indexul h(k) n tabela de hashing
Inserm valoarea 12 .... h(12) = 12 mod 5 = 2

Coliziune Coliziune !!
Funcia de dispersie returneaz acelai index pentru mai multe valori de intrare (chei)

Soluie
--> tabel de liste nlnuite (n loc de tablou de elemente simple avem tablou de pointeri la liste nlnuite)
Cheia k se insereaz / terge / acceseaz n lista nlnuit de la indexul h(k) n tabela de hashing

O alt soluie este adresarea deschis


Inserm valoarea 12
adugnd valoarea 12
la lista de la indexul 2

39
Tabele de dispersie
Maparea cheilor K n tabela de hashing T, unde T este un tablou cu T[0 m-1] i indexul se calculeaz
pe baza funciei de hashing h: U {0, 1, , m-1}

INSERT(T, x): Inserare elementului x n capul listei de la indexul h(x.k) Dac elementul nu este n tabel O(1)
DELETE(T, x): tergerea elementului din lista de la poziia T[(h(x.k)] elem.prev = elem.next O(1)
SEARCH(T, k): Cutarea elementului cu cheia k n lista de la indexul h(k) Depinde de lungimea listei max O(n)

Factorul de ncrcare (umplere)


numrul de elemente / dimensiunea tabelei

Cutarea are coplexitate constant O(1) dac factorul de ncrcare este 1


Dac sunt attea poziii libere cte elemente se vor pstra n tabel 40
Tabele de dispersie
Performana tabelelor de hashing
Operaii O(1) dac listle nlnuite sunt scurte
Se poate degrada pn la O(n) dac
Tabela este prea plin factorul de ncrcare este prea mare
are dimensiuni prea mici pentru numrul de elemente de stocat
Funcia de hashing nu distribuie uniform valorile

Alegem un factor de ncrcare constant i cnd se


Cnd? atinge acel nivel de ncrcarea, redimensionm tabela
Cerine
Creterea dimensiunii tabelei dac devine prea ncrcat
Alocarea unei tabele de dimensiuni duble - i redistribuirea elementelor
Pentru eficien: Funcia de hashing s nu depind de dimensiunea vectorului
Ex. Pentru un ntreg n avem h(n) = n Dimensiunea listelor nlnuite
devine constant operaii O(1)

Funcie de dispersie cu distribuie uniform: cu ieire aleatoare


Fr abloane, fr nclinaii fa de unele valori
Chei apropiate se mapeaz la valori de hashing ndeprtate
Pentru n elemente stocate i
nlnuire de max l elemente,
La redimensionare: alegem ca dimensiune a tabelei un numr prim
alegem m prim n apropierea
apropiat de dublul dimensiunii anterioare pentru mascarea abloanelor 41
valorii:
Tabele de dispersie
Adresarea deschis locaia elementului nu este determinat de codul de dispersie
Soluie pentru evitarea coliziunilor (alternativa la nlnuire)
Tabela de dispersie stocheaz un singur element per poziie

Cerine
Factorul de impact trebuie s fie subunitar nu se pot stoca mai multe elemente dect locaii disponibile n tabel
Strict mai mic dect 1 altfel unele cutri nu se vor termina niciodat
Dispersie cu distribuie uniform
Minimizare coliziunilor

Trateaz coliziunile prin stocarea elementului nou ntr-o alt locaie nc nefolosit
Gsirea locaiei: folosind secvene de probare
Probare liniar: se caut locaie liber n poziiile succesive pn se gsete o locaie liber
Mergnd circular: la final se ntoarce la nceputul tabelei
( h(k)+i ) mod m
Probare cuadratic: se caut locaie liber n poziii care depind cuadratic de i
Ex. srind peste 1 element apoi 2, apoi 4, etc.
Mergnd circular: la final se ntoarce la nceputul tabelei
( h(k)+c1i+ c1i2 ) mod m
Dispersie dubl: se caut locaie liber n poziii variabile
Pe baza unei funcii de dispersie auxiliar
Cu atene pe alegerea valorilor: h2(k) relativ prim cu m ( h1(k)+i*h2(k) ) mod m
42
Tabele de dispersie
Probare liniar: se caut locaie liber n poziiile succesive
pn se gsete o locaie liber T[(h(k)], T[(h(k) + 1], T[(h(k) + 2], ... , T[m-1], T[0], T[(h(k) - 1]
Mergnd circular: la final se ntoarce la nceputul tabelei

Inserare locaie liber


Calculeaz h(k), Acceseaz T[h(k)] succesiv

Dac locaie liber adaug element


Dac locaie ocupat
Se probeaz locaiile succesive (circular)
locaie liber
Dac se gsete locaie liber se adaug elementul revenind
Dac nu se gsete locaie liber inserarea eueaz circular

Cutare
Calculeaz h(k), Acceseaz T[h(k)]
Dac locaie liber elementul nu este n tabel
Element regsit n
Dac locaie ocupat
locaie succesiv
Se caut potrivirea dintre cheia cutat i cheia elementului
(prin probarea elementelor succesive - circular)
Dac se gsete potrivire se returneaz elementul
Dac se ajunge la locaie liber elementul nu este n tabel Cutare euat

Un grup de locaii succesive ocupate se numete cluster 43


Tabele de dispersie
Probare liniar: se caut locaie liber n poziiile succesive pn se gsete o locaie liber
Mergnd circular: la final se ntoarce la nceputul tabelei

tergere
Cutare euat,
Cutare urmat de eliminarea elementului gsit dei elementul
problem la cutare ulterioar exist n tabel

Explicaie s ne areamintim de invarint


La nlnuire fiecare element se regsete n lista de la indexul H(x)
La probare liniar fiecare element se regsete la indexul H(x) sau
la un index succesiv din acelai cluster
tergerea simpl poate rupe n dou clusterul ncalc invarinatul
Avem nevoie de o valoare care s in clusterul mpreun

Soluie: tergere lene


Marcarea elementelor terse cu un flag: locaie eliberat n urma unei tergeri
Cutarea se oprete doar la o locaie pur liber care nu a fost eliberat dup o tergere
La inserare se terge flagul

44
Tabele de dispersie
Probare liniar: se caut locaie liber n poziiile succesive pn se gsete o locaie liber
Mergnd circular: la final se ntoarce la nceputul tabelei

tergere
Problema metodei de tergere lene: poluarea tabelei
Locaiile cu flagul setat pe ters pot lega mpreun date nenrudite (dpdv. al algoritmului de cutare)
Eficiena cutrii scade ajungem la cutare liniar

Soluie: crearea unei noi tabele de hashing cu datele prin adugarea elementelor existente n tabela veche

Calculeaz h(k), Acceseaz T[h(k)]


Dac locaie liber elementul nu este n tabel: tergerea eueaz
Dac locaie ocupat
Se caut potrivirea dintre cheia cutat i cheia elementului
(prin probarea elementelor succesive - circular)
Dac se gsete potrivire se terge elementul i se seteaz flagul
Dac se ajunge la locaie complet liber elementul nu este n tabel: tergerea eueaz

45
Tabele de dispersie
Alegerea funciei de dispersie
Metoda divizrii
Dac cheile sunt valori ntregi mari calculm restul modulo m
Unde m este numr prim mare
Dac cheile sunt iruri de caractere tratm stringul ca pe un ntreg

Stringul a1a2a3...ak se reprezint ca p(b) = a0 + a1 b + a2 b2 + + ak1bk1,


unde b este o baz aleas astfel nct s fie mai mare dect numrul de caractere

Nu se calculeaz direct valoarea ntregului p(b) ci restul mpririi la m


Unde m este numr prim adesea se alege numrul 33
Problema: calculare restului este o operaie lent
Soluie: metoda multiplicrii

Metoda multiplicrii
n loc de m se folosete o putere a lui 2, ex. 232 sau 264
n funcie de dimensiunea lui size_t)
pentru baz se folosete un numr prim mic (37, 31, 97, ...)

Dispersie universal (universal hashing)


Alegerea funciei de dispersie n mod aleator

O familie de funcii de dispersie {Hr} este universal dac pentru orice chei distincte 46
x i y, probabilitatea ca r s fie ales astfel nct Hr(x) = Hr(y) este exact 1/m
Tabele de dispersie
Performana operaiilor depinde de facotrul de ncrcare (umplere)
Eficien dac factorul de umplere este meninut la valori mici

n cazul probrii liniare depinde de dimensiunea clusterului


Cluster mic performan O(1)
Tablou aproape plin performan O(n)

Timp de cutare: Probare vs. nlnuire


Observaii
Dac factorul de ncrcare devine mare este necesar
Extinderea i redispersarea elementelor
Funcie de dispersie uniform
La probare este important s minimizeze clusterizarea
Coliziunea se trateaz prin probare sau nlnuire
nlnuirea este mai eficient n timp
Probarea este mai eficient n spaiul de memorie

Avantaje
Timpul mediu al operaiilor este O(1)
Dac funcia de dispersie este bun i dimensiunea tabeleli adecvat
Performan mai bun dect n cazul arborilor binari de cutare

Dezavantaj
Elementele nu sunt ordonate
47
Surse bibliografice
K. N. King, C Programming A Modern Approach, 2nd edition, W. W. Norton & Co., 2008
Capitolul 19

Deitel & Deitel, C How to Program, 6th edition, Pearson, 2009


Capitolul 12

http://visualgo.net/

James Aspnes, Notes on Data Structures and Programming Techniques,


http://www.cs.yale.edu/homes/aspnes/classes/223/notes.html#dataStructuresAndProgrammingTechniques

48

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