Sunteți pe pagina 1din 35

1.

Structuri elementare de date


nainte de a elabora un algoritm, trebuie s ne gndim la modul n care reprezentm datele.
Structurile fundamentale de date cu care se poate opera sunt: fiier, tablou, list, graf, arbore. n acest
capitol ne vom ocupa de liste, arbori i grafuri, ntruct primele dou (tablou, fiier) au fost studiate
anterior. O list este o colecie de elemente de informaie (noduri) aranjate ntr-o anumit ordine.
Lungimea unei liste este dat de numrul de noduri din list. Structura listei trebuie s ne permit s
determinm eficient care este primul/ultimul nod n structur i care este predecesorul/succesorul unui
nod dat. Cea mai simpl list este lista liniar. Un alt tip de list este cea circular, n care, dup ultimul
nod, urmeaz primul, deci fiecare nod are succesor i predecesor.
Operaiile curente care se fac n liste sunt: inserarea unui nod, tergerea (extragerea) unui nod,
concatenarea unor liste, numrarea elementelor unei liste etc. Implementarea unei liste se poate face in
principal n dou moduri.
Primul, const n implementarea secvenial, adic n locaii succesive de memorie, conform
ordinii nodurilor din list, care conin informaia dorit. Avantajul acestei tehnici este accesul rapid la
predecesorul/succesorul unui nod, inclusiv gsirea rapid a primului/ultimului nod. Dezavantajele sunt
inserarea/tergerea relativ complicat a unui nod i faptul c, n general, nu se folosete ntreaga
memorie alocat listei.
Cel de-al doilea, implementarea nlnuit. n care fiecare nod conine dou pri: informatia
propriu-zis i adresa nodului urmtor. Alocarea memoriei fiecrui nod se poate face n mod dinamic,
n timpul rulrii programului. Accesul la un nod necesit parcurgerea tuturor predecesorilor si, ceea ce
poate lua ceva mai mult timp. Inserarea/tergerea unui nod este n schimb foarte rapid. Se poate
construi o list dublu nlnuit, care conine dou adrese n loc de una, astfel ncat un nod s conin pe
lna adresa nodului urmtor i adresa nodului anterior.
Listele implementate nlnuit pot fi reprezentate cel mai simplu prin tablouri. n acest caz,
adresele sunt de fapt indici de tablou. O alternativ este s folosim tablouri paralele: s memoram
informaiile fiecrui nod ntr-un tablou INFO[n], iar adresele (indicii) nodurilor succesoare ntr-un
tablou SUCC[n]. Indicele primului nod din list (fie c este de tip coad sau stiv) va fi pstrat ntr-o
variabil. Acest mod de reprezentare este simplu dar apare o problem, i anume cea a gestionrii
locaiilor libere. O soluie este s reprezentm locaiile libere tot sub forma unei liste nlnuite. Atunci,
tergerea unui nod din list implic inserarea sa ntr-o list cu locaii libere, iar inserarea unui nod n
list implic tergerea sa din lista cu locaii libere. Este interesant de remarcat c, pentru implementarea
listei de locaii libere, putem folosi aceleai tablouri, dar avem nevoie de o alta variabil, care s
conin indicele primei locaii libere din VAL i LINK, n timp ce variabila cealalt, iniial, va conine
adresa primei locaii din lista propriu-zis.
Vom exemplifica n continuare dou tipuri de liste foarte des folosite, i anume coada i stiva,
considernd implementarea ce utilizeaz alocarea static (tablouri).
O stiv (stack) este o list liniar cu proprietatea c operaiile de inserare/extragere a nodurilor
se fac n/din coada listei, motiv pentru care stivele se mai numesc i liste LIFO (Last In First Out). Cel
mai natural mod de reprezentare pentru o stiv este implementarea secvenial ntr-un tablou S[n], unde
n este numarul maxim de noduri. Primul nod va fi memorat n S[0], al doilea n S[1], iar ultimul n S[n1]; sp este o variabila care conine adresa (indicele) ultimului nod inserat. Iniial, cand stiva este vid,
avem sp = -1 (sau poate fi 0). Iat funciile de inserare, extragere a unui nod, precum i cele de afiare
i de semnalare a situaiilor extreme (stiv goal sau stiv plin).
/* stivatab.c - operatii cu o stiva realizata static intr-un tablou;
operatii disponibile: inserare, extragere, afisare cap stiva,
afisare stiva, afisare pointer la varful stivei */
5

#include <stdio.h>
#include <conio.h>
#define MaxS 10
typedef int ElTip; // ElTip - definire tip element din stiva
typedef
struct {
ElTip elem [MaxS];// spatiu pentru elem. stivei
int
sp;
/* index/ referinta la varful stivei (stack pointer) */
} TStiva, *AStiva;
void eroare (char *meserr){
printf("%s\n", meserr);
}
void InitStiva (AStiva rs){
/* initializare stiva, sp nu refera un element (stiva goala) */
rs->sp=-1;
}
int StivaGoala (AStiva rs){
return (rs->sp == -1);
}
int StivaPlina (AStiva rs){
/* stiva este plina, index=MaxS-1, indice maxim,
nu se mai pot introduce elemente in stiva */
return (rs->sp == MaxS-1);
}
void Push (AStiva rs, ElTip el){
/* introduce element in stiva, daca nu este plina */
if (StivaPlina (rs))
eroare ("Stiva Plina !!!");
else {rs->sp=rs->sp+1;
rs->elem[rs->sp]=el;
}
}
ElTip Pop (AStiva rs){
/* extrage element din stiva, daca nu este goala */
ElTip e;
if (StivaGoala (rs))
{eroare ("Stiva Goala !!!");
return 0;
}
else
{e=rs->elem[rs->sp];
rs->sp=rs->sp-1;
return e;
}
}
ElTip CapStiva (AStiva rs){
if (StivaGoala (rs))
{eroare ("Stiva Goala !!!");
return 0;
6

}
else
return rs->elem[rs->sp];
}
void AfisareStiva (AStiva rs){
/* se creaza o copie a stivei curente, pentru a utiliza functiile
definite pentru operare stiva (Pop, CapStiva, StivaGoala) */
TStiva s=*rs;
AStiva pt=&s;
int nvl=0;
/* Numar de valori afisate pe linie */
if (pt->sp == -1)
eroare("Stiva Goala !!!\n");
else
while (!StivaGoala (pt))
{printf ("%d -> ", CapStiva (pt));
Pop (pt);
if (++nvl % 10 == 0)
printf("\n");
}
printf("NULL\n");
}
void Afisare_Poz_sp (AStiva rs){
printf("Referinta (indexul), sp: %d\n", rs->sp);
if (!rs->sp)
printf("\tSTIVA GOALA!!!\n");
else if (rs->sp == MaxS-1)
printf("\tSTIVA PLINA !!! ELIMINATI ...\n");
}
void main (void){
TStiva s, *rs;
char optiune[20];
ElTip n;
clrscr();
printf("Exemplu de stiva realizata utilizand un tablou.\n");
printf("Stiva este alcatuita din numere intregi.\n");
InitStiva (rs);
printf("\nOptiuni Introduc, Extrag, Afis stiva, Cap stiva, Poz sp, Stop: ");
gets(optiune);
while (optiune[0] != '\0' && optiune[0] != 's' && optiune[0] != 'S'){
switch(optiune[0]){
case 'i':
case 'I':if (StivaPlina(rs)){
eroare("\n\tSTIVA PLINA !!! ELIMINATI...\n");
break;}
printf("Introd. nr. (CTRL-Z, Enter-Stop Introd): ");
while (scanf("%d", &n) == 1 && !StivaPlina(rs)){
Push (rs, n);
if (StivaPlina (rs)){
eroare("Stiva PLINA !!! ELIMINATI.....\n");
7

break;
}
printf("Introd. nr. (CTRL-Z, Enter-Stop Introd): ");
}
fflush(stdin);
break;
case 'e':
case 'E': optiune[0]='D';
while ( !StivaGoala (rs) &&
(optiune[0] == 'D' || optiune[0] == 'd') )
{printf ("Se extrage primul element din stiva: %d\n",
CapStiva (rs));
Pop (rs);
if (!StivaGoala (rs)){
printf("Continuati extragerea (Da/Nu): ");
gets(optiune);
fflush(stdin);
}
}
if (StivaGoala (rs))
printf("Stiva Goala !!! INTRODUCETI ELEMENTE \n");
break;
case 'c':
case 'C': if (!StivaGoala (rs))
printf("Afisare element din capul stivei: %d\n",
CapStiva (rs));
else
eroare("Stiva Goala !!!\n");
break;
case 'a':
case 'A':AfisareStiva (rs);
break;
case 'p':
case 'P': Afisare_Poz_sp (rs);
break;
}
printf("\nOptiuni Int, Ext, Afis stiva, Cap stiva, Poz sp, Stop: ");
gets(optiune);
}
}
O coad este o list liniar n care inserrile se fac doar n capul listei (prim), iar extragerile
doar din coada listei (ultim). Cozile se numesc i liste FIFO (First In First Out). O reprezentare
secvenial pentru o coad se obine prin utilizarea unui tablou C[n], pe care l tratm ca i cum ar fi
circular: dup locatia C[n-1] urmeaz locaia C[0]. Dac cei doi indeci (ultim i prim) refer cele dou
locaii, sau dou locaii n aceast ordine (k-1, respectiv k) coada este goal; dac cei doi indeci sunt
egali (k), atunci coada conine un singur element, iar dac ultim=k-2, iar prim=k, atunci coada este
plin.
8

/* coadatab.c - operatii cu o coada realizata static intr-un tablou;


operatii disponibile: inserare, extragere, afisare cap coada,
afisare coada, afisare pozitii referinte la primul si ultimul
element din coada */
#include <stdio.h>
#include <conio.h>
#define MaxC
10
typedef int ElTip; // ElTip - definire tip element din coada
typedef
struct {
ElTip elem [MaxC];// spatiu pentru elem. cozii
int
prim, ultim;
/* indecsi/ referinte la primul/ ultimul element din coada */
} TCoada, *ACoada;
void eroare (char *meserr){
printf("%s\n", meserr);
}
int IncIndexC (int i){ /* increment index element curent coada */
return ((i+1) % MaxC);
}
void InitCoada (ACoada pc){
/* initializare coada, referintele prim si ultim */
pc->prim=0;
pc->ultim=MaxC-1;
}
int CoadaGoala (ACoada pc){
/* coada este goala: index (ultim+1) == index (prim)
coada goala: x x x x u p x x x x ,
daca p si u ocupa aceeasi pozitie coada are un singur element:
x x x x x p(u) x x x x , p si u au aceeasi valoare */
return (IncIndexC (pc->ultim) == pc->prim);
}
int CoadaPlina (ACoada pc){
/* coada este plina: index (ultim+2) == index (prim)
coada goala: x x x u - p x x x x , pozitia - nu este element in coada*/
return (IncIndexC (IncIndexC(pc->ultim)) == pc->prim);
}
void AdaugElC (ACoada pc, ElTip el){
if (IncIndexC (IncIndexC(pc->ultim)) == pc->prim)
eroare ("Coada Plina !!!");
else {pc->ultim=IncIndexC(pc->ultim);
pc->elem[pc->ultim]=el;
}
}
ElTip ScotElC (ACoada pc){
ElTip e;
if (CoadaGoala (pc))
{eroare ("Coada Goala !!!");
9

return 0;
}
else
{e=pc->elem[pc->prim];
pc->prim=IncIndexC(pc->prim);
return e;
}
}
ElTip CapCoada (ACoada pc){
if (CoadaGoala (pc))
{eroare ("Coada Goala !!!");
return 0;
}
else
return pc->elem[pc->prim];
}
void AfisareCoada (ACoada pc){
/* se creaza o copie a cozii curente, pentru a utiliza functiile
definite pentru operare coada (ScotElC, CapCoada, CoadaGoala) */
TCoada c=*pc;
ACoada pt=&c;
int nvl=0;
/* Numar de valori afisate pe linie */
if (CoadaGoala(pt))
eroare("Coada Goala !!!\n");
else
while (!CoadaGoala (pt))
{printf ("%d -> ", CapCoada (pt));
ScotElC (pt);
if (++nvl % 10 == 0)
printf("\n");
}
printf("NULL\n");
}
void Afisare_Poz_Prim_Ultim (ACoada pc){
printf("Cei doi indecsi (referinte), prim si ultim: %d(p), %d(u)\n",
pc->prim, pc->ultim);
}
void main (void){
TCoada c, *pc;
char optiune[20];
ElTip n;
clrscr();
printf("Exemplu de coada realizata utilizand un tablou.\n");
printf("Coada este alcatuita din numere intregi.\n");
InitCoada (pc);
printf("\nOptiuni Int, Ext, Afis coada, Cap coada, Poz p/u, Stop: ");
gets(optiune);
while (optiune[0] != '\0' && optiune[0] != 's' && optiune[0] != 'S'){
switch(optiune[0]){
10

case 'i':
case 'I':if (CoadaPlina(pc)){
eroare("\n\tCOADA PLINA !!! ELIMINATI...\n");
break;}
printf("Introd. nr. (CTRL-Z, Enter-Stop Introd): ");
while (scanf("%d", &n) == 1){
AdaugElC (pc, n);
if (CoadaPlina (pc)){
eroare("Coada PLINA !!! ELIMINATI.....\n");
break;
}
printf("Introd. nr. (CTRL-Z, Enter-Stop Introd): ");
}
fflush(stdin);
break;
case 'e':
case 'E': optiune[0]='D';
while ( !CoadaGoala (pc) &&
(optiune[0] == 'D' || optiune[0] == 'd') )
{printf ("Se extrage primul element din coada: %d\n",
CapCoada (pc));
ScotElC (pc);
if (!CoadaGoala (pc)){
printf("Continuati extragerea (Da/Nu): ");
gets(optiune);
fflush(stdin);
}
}
if (CoadaGoala (pc))
printf("Coada Goala !!! INTRODUCETI ELEMENTE \n");
break;
case 'c':
case 'C': if (!CoadaGoala (pc))
printf("Afisare element din capul cozii: %d\n",
CapCoada (pc));
else
eroare("Coada Goala !!!\n");
break;
case 'a':
case 'A':AfisareCoada (pc);
break;
case 'p':
case 'P': Afisare_Poz_Prim_Ultim (pc);
break;
}
printf("\nOptiuni Int, Ext, Afis coada, Cap coada, Poz p/u, Stop: ");
gets(optiune);
}
11

}
Structurile de date utilizate anterior (tablouri, sau structuri, uniuni) nu sunt structuri de date
dinamice, deoarece ramn fixe n ce privete dimensiunea lor pe toat durata lor de via, adic pe
durata execuiei funciei respective. Astfel de exemple sunt tablourile sau structurile, ale cror
dimensiuni pot fi cunoscute de la declararea lor.
Sunt ns situaii n care structurile de date trebuie s se modifice ca form sau ca mrime pe
parcursul programului; astfel de structuri sunt listele, arborii, grafurile i mulimile disjuncte. Deoarece
pentru astfel de structuri numrul de componente i legturile dintre acestea nu sunt dinainte fixate, ele
se creaz dinamic, pe durata execuiei programului.
Variabilele statice sunt toate variabilele declarate n seciunile declarative ale unei funcii sau
ale unui bloc, avnd aceeai durat de via cu cea a funciei sau blocului respectiv n care au fost
declarate.
Variabilele dinamice se genereaz i se distrug dinamic pe parcursul execuiei programului.
Spre deosebire de variabilele statice, variabilele dinamice nu au un nume (identificator), referirea la ele
fcndu-se n mod indirect prin intermediul variabilelor pointer (referin).
O variabil referin (pointer) are ca valoare o adres a unei variabile dinamice. Variabilele
pointer se folosesc pentru a creea variabile dinamice (a aloca memorie pentru acestea) i pentru a le
distruge cnd nu mai sunt necesare (deci pentru a elibera memoria alocat).
O structur de date dinamice este constituit dintr-un numr de elemente referite prin pointeri.
Se pot introduce noi elemente sau suprima cele vechi, n timpul execuiei programului. Adresele reale
ale elementelor nu au importan, deoarece legturiile logice dintre elemente sunt stabilite de pointeri i
nu prin poziia lor relativ din memorie, ca n cazul tablourilor.
Fiecare element al unei structuri dinamice poate conine unul sau mai multe cmpuri cu referire
(pointeri) la alte elemente, care pot fi de orice tip (simplu, tablou, structur sau pointer). De exemplu,
componentele unei listei sunt structuri cu dou cmpuri: o valoare de tip ntreg i un pointer la
urmtoarea component:
struct intrare
{
int valoare ;
struct intrare *urmator ;
};
Crearea efectiv a variabilei dinamice, deci alocarea de memorie, se face utiliznd una dintre
funciile malloc(), calloc() sau new (C++). Distrugerea unei variabile dinamice, deci eliberarea
spaiului de memorie ocupat de aceasta, se realizeaz cu una dintre funciile free() sau delete (C++).
Aceste funcii sunt definite dup cum urmeaz.
void *malloc(size_t dimensiune), aloc un spaiu continuu de dimensiune octei, returnnd un pointer
de tipul void la nceputul blocului alocat, dac s-a realizat alocarea cu succes, sau pointerul NULL dac
nu se poate aloca memorie;
void *calloc (size_t n , size_t dim), aloc spaiu continuu pentru n elemente de date, fiecare necesitnd
dim octei. Spaiul alocat este iniializat cu zero. Dac s-a alocat, returneaz un pointer la nceputul
spaiului alocat, altfel returneaz pointerul NULL.
Cnd se termin operarea cu memoria ce a fost alocat dinamic, cu funciile malloc() sau calloc(),
spaiul de memorie poate fi eliberat (cedat sistemului) prin aplicarea funciei free():
void *free (void *pointer), elibereaz blocul de memorie referit de pointer, ce a fost alocat de una
dintre funciile calloc( ) sau malloc( ).
Aceste funcii se afl n fiierul antet standard <stdlib.h> (i <alloc.h>).

12

n C++ pentru operaii de alocare / eliberare de memorie se utilizeaz operatorii (funciile) new
i delete.
Operatorul new ncearc s creeze un obiect de tipul specificat (nume) prin alocarea, dac e
posibil, de sizeof (nume) octei n memoria disponibil (denumit heap). Durata de alocare e pn la
eliberare, cu operatorul delete sau pn la sfritul programului.
Dac alocarea se face cu succes, new returneaz un pointer ctre aceast zon, altfel un pointer
nul. Trebuie testat pointerul returnat, la fel ca la funcia malloc. Dar, spre deosebire de aceasta, new
calculeaz dimensiunea spaiului de memorie necesar (sizeof(nume)) fr a fi nevoie de utilizarea
explicit a operatorului. De asemenea pointerul returnat este de tipul corect, pointer la nume, fr s
fie necesar prezena operatorului de conversie: (nume *)malloc( sizeof( nume)). Sintaxa sa de utilizare
este:
ptr_nume = new nume;
Respectiv
delete ptr_nume;
Utilizarea variabilelor dinamice asigur o administrare eficient a spaiului de memorie.

1.1 Liste simplu nlnuite (stive i cozi)


Aceasta este cea mai simpl structur de date dinamice. O list este o colecie de elemente de
informaie (denumite i noduri) aranjate ntr-o anumita ordine. Lungimea listei este dat de numrul de
noduri din list. Operaiile curente cu liste trebuie s permit, la un moment dat, s determinm care
este primul/ultimul nod n list i care este predecesorul/succesorul (dac exist) pentru un nod dat.
S considerm urmtoarea list nlnuit de tip stiv (LIFO-Last In First Out, adic ultimul
element introdus n list este primul ce poate fi extras):
primul
NULL
X

care pornete de la o singur variabil, pointerul primul i trei elemente de tip st_litera. ntr-o lista de
tip stiv elementele se pot extrage n ordinea invers introducerii n list. S vedem cum se poate creea
prin program o astfel de structur. n primul rnd declaraiile pentru o astfel de structur vor fi:
struct st_litera
{
char car;
struct litera *urmator;
};
struct st_litera *vr, *primul;
Iniial lista este vid, deci putem scrie:
primul = NULL;
n continuare este nevoie s inserm un element n aceast list. n primul rnd trebuie creat
acest element:
vr = (struct st_litera *) malloc ( sizeof ( struct st_litera));
primul
vr

Dup aceasta trebuie actualizate cmpurile noului element:


vr -> car = Z;
13

primul

vr
Z

vr -> urmator = primul;


primul

vr
NULL

Z
Valoarea curent pentru primul este NULL i efectul acestei instruciuni este de a pune NULL la
vr -> urmator. Pentru ca variabila primul s refere primul element din list vom realiza atribuirea:
primul = vr;/* acum i variabila primul va referi adresa referit de vr */
vr
NULL
Z

primul

Pentru a insera un nou element (caracterul Y) n list vom proceda n aceeai manier:
vr = (struct st_lista *) malloc ( sizeof ( struct st_lista ));
vr -> car = Y;
vr -> urmator = primul;
i se obine:
primul

vr
NULL
Y

iar dup instruciunea:


primul = vr; /* primul va referi caracterul referit de vr, adic Y */
primul

vr
NULL
Y

n mod asemntor se introduce n lista, de tip stiv, i elementul urmtor pentru a obine lista
iniial, prezentat la nceputul paragrafului.
S utlizm, n continuare, aceast tehnic de inserare pentru a crea o list nlnuit cu o
structur de tip stiv. Vom scrie programul pentru citirea unui ir de caractere, memorarea lor ntr-o
astfel de list i tiprirea lor n ordine invers.
/* stivacar.c - programul construieste o stiva de caractere, pe care apoi o tipareste */
14

#include <stdio.h>
struct st_lista
{
char car;
struct st_lista *urmator;
};
void main()
{
struct st_lista *vr, *primul;
char c;
primul = NULL;
printf("\nProgramul construieste si afiseaza o stiva de caractere.\n");
printf("Introduceti caractere (CTRL-Z, Enter-pentru sfarsit):\n");
fflush(stdin);
while ((c=getchar()) != EOF)
if (c!='\n')
{vr=(struct st_lista *) malloc(sizeof(struct st_lista));
if (vr == NULL)
{ /* daca pointerul returnat de functia malloc() este
NULL */
printf("Memorie plina !!!!!!\n"); /* se tipareste un
mesaj de eroare */
break;
/* si se termina executia ciclului de citire
caractere */
}
vr->car=c;
vr->urmator=primul;
primul=vr;
}
vr=primul; /* initializare pointer curent, vr, pentru a referi
primul caracter */
while (vr != NULL)
{
printf("%c -> ", vr->car);
vr=vr->urmator;
}
printf(" NULL");/* tiparire sfarsit lista caractere sau lista vida daca
primul era NULL */
}
S considerm acum aceeai problem, dar cu condiia de a introduce n list i de a tipri
numai caracterele distincte. Pentru aceast situie vom defini o funcie ntreag, pe care o vom numi
distinct, care va returna valoarea 1 (adevrat) dac caracterul este diferit de restul listei (deci nu apare
ntre caracterele introduse n list pn n acel moment) i va returna 0 (fals) dac caracterul este gsit
n list.
/* stivcdif.c - programul construieste o stiva de caractere distincte, pe care apoi o tipareste */
#include <stdio.h>
15

struct st_lista
{
char car;
struct st_lista *urmator;
};
void main()
{
struct st_lista *vr, *primul;
char c;
int distinct (struct st_lista *prim, char ch);
primul = NULL;
printf("\nProgramul construieste si afiseaza o stiva de caractere.\n");
printf("Introduceti caractere (CTRL-Z, Enter-pentru sfarsit):\n");
fflush(stdin);
while ((c=getchar()) != EOF)
if (c != '\n' && (distinct(primul, c)))
{
vr=(struct st_lista *) malloc(sizeof(struct st_lista));
if (vr == NULL)
{
/* daca pointerul returnat de functia
malloc() este NULL */
printf("Memorie plina !!!!!!\n"); /* mesaj de
eroare */
break; /* si se termina executia ciclului de citire
caractere */
}
vr->car=c;
vr->urmator=primul;
primul=vr;
}
vr=primul;
/* initializare pointer curent, vr, pentru a referi
primul caracter */
while (vr != NULL)
{
printf("%c -> ", vr->car);
vr=vr->urmator;
}
printf(" NULL");/* tiparire sfarsit lista caractere sau lista vida daca
primul era NULL */
}
int distinct (struct st_lista *prim, char ch)
{
struct st_lista *ptr;
int gasit;
ptr=prim;
/* initializare pointer curent cu primul din lista */
gasit=0;
/* presupunem ca nu l-am gasit inca */
while (ptr != NULL && !gasit)
/* cat timp nu s-a terminat lista si nu
l-am gasit */
{
/* se continua parcurgerea listei */
16

gasit=(ptr->car == ch);
ptr=ptr->urmator;
}
return (!gasit);
}
S considerm acum un alt tip de list nlnuit, i anume una de tip FIFO (First In First Out),
adic primul intrat n list este i primul care poate fi extras. O astfel de structur mai este denumit i
coad sau coad (ir) de ateptare, ntruct elementele se introduc pe la un capt i sunt accesibile pe la
cellalt capt, deci la un moment dat este accesibil numai prima component introdus n list.
Vom considera de aceast dat problema construirii unei liste nlnuit format din cuvinte i
apoi tiprirea acestor cuvinte n ordinea n care au fost introduse. Vom defini dou funcii: una pentru
ataarea unui element (cuvnt) la sfritul listei, iar cea de-a doua pentru extragerea, adic afiarea
cuvintelor din lista de tip coad, cu eliberarea spaiului de memorie alocat dinamic pentru aceast
structur.
/* coadacuv.c - programul construieste o coada de cuvinte,
pe care o afiseaza, la terminarea introducerii de cuvinte,
cu eliberarea spatiului alocat pentru aceasta structura */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#define SIR_VID ""
#define DIMCV 25
struct st_cuvant
{
char *cuvant;
/* pointer la sirul de caractere (cuvant) */
struct st_cuvant *urmator;
};
int ncl=0;
/* numar de cuvinte afisate pe linie */
struct st_cuvant * atasare_coada (struct st_cuvant *ultim, char *sirc)
{
struct st_cuvant *vr;
vr=(struct st_cuvant *) malloc(sizeof(struct st_cuvant));
if (vr) /* daca s-a putut crea un pointer se continua atasarea */
{
vr->cuvant=strdup(sirc);
vr->urmator=NULL;
ultim->urmator=vr;
}
return (vr);
}
void extragere ( struct st_cuvant * primu)
{
struct st_cuvant *ptr;
while (primu)
{ptr=primu; /* memorat pentru a elibera spatiul referit de el */
17

printf (" %s -> ", primu->cuvant);


ncl++;
if (ncl%5==0)
printf("\n");
primu=primu->urmator;
free(ptr);
/* eliberarea spatiului alocat pointerului */
}
printf ("NULL\n");/* sfarsit lista sau lista vida daca primu == NULL */
}
void main()
{
struct st_cuvant *primul, *ultimul;
char cuv[DIMCV];
clrscr();
printf("Programul construieste o lista de tip coada cu cuvinte de la"
" tastatura\n");
printf("Programul ia sfarsit cu un cuvant vid, adica RETURN,"
" la inceput de linie.\n");
printf("Cuvant de introdus in lista:");
gets(cuv);
primul=(struct st_cuvant *) malloc(sizeof(struct st_cuvant));
primul->cuvant=strdup(cuv);
primul->urmator=NULL;
ultimul=primul;
printf("Cuvant de introdus in lista:");
gets(cuv);
while (strcmp(cuv, SIR_VID))
{
if(!(ultimul=atasare_coada (ultimul, cuv)))
break;
printf("Cuvant de introdus in lista:");
gets(cuv);
}
extragere (primul);
}
O alt operaie necesar n aplicaii cu liste nlnuite este introducerea unui element n orice
punct din list, astfel nct elementele listei s respecte o anumit ordine. Vom defini o funcie care
insereaz un nou element, marcat prin referina nou, dup un element din list, marcat prin variabila
pointer curent.
void insereaza_dupa (struct st_orice_tip *curent; struct st_orice_tip *nou)
{nou -> urmator = curent -> urmator;
curent -> urmator = nou;
}
Aceast funcie se utilizeaz cnd avem o variabil pointer (cum este pointerul curent) dup
care se insereaz noul (nou) element al listei. Inserarea se poate face i naintea unui element din list,
dar este mai dificil deoarece lista este unidirecional i nu ne putem referi la predecesorul unui
18

element, ci numai la succesorul su. Aceast problem se poate rezolva n dou moduri: fie prin
parcurgerea listei de la nceput i determinarea poziiei unde se face inserarea, fie prin inserarea dup
elementul respectiv (curent), urmat de interschimbarea coninuturilor celor dou variabile, mai puin a
legturilor (referinelor).
n primul caz funcia pentru inseare va fi urmtoarea:
void insereaza_inainte (struct st_orice_tip *curent, struct st_orice_tip *nou,
struct st_orice_tip *primul)
{
/* se insereaza elementul nou in fata celui curent */
struct st_orice_tip aici; /* va reprezenta locul unde se insereaza nou */
if ( curent == primul ) /* testeaza daca trebuie inserat in fata primului element */
{
/* daca da, se pune primul in lista */
nou -> urmator = curent;
/* deci curent devine primul */
primul = curent;
}
else
{/* daca nu, i se determina pozitia de inserare in lista */
aici = primul;
/* care va fi intre aici si curent */
while ( aici -> urmator != curent )
aici = aici -> urmator; /* aici va fi predecesorul lui curent*/
aici -> urmator = nou;
/* si se insereaza intre cei doi pointeri */
nou -> urmator = curent;
}
}
Utiliznd cea de-a doua metod, rezut urmtorea funcie:
void insereaza_inainte (struct st_orice_tip *curent, struct st_orice_tip *nou)
{
/* se insereaza elementul nou in fata celui curent , prin inserarea
lui nou dupa curent si apoi se inverseaza valorile referite de acesti
doi pointeri, astfel incat valorea lui nou sa fie in fata lui curent */
tip_info val_info; /* variabila de acelasi tip cu tipul valorilor referite de pointeri */
nou -> urmator = curent -> urmator; /* se insereaza nou dupa curent */
curent -> urmator = nou;
val_info = curent -> info; /* dupa care se interschimba valrile referite de cei */
curent -> info = nou -> info; /* doi pointeri, astfel ca in lista, valorile sa fie in */
nou -> info = val_info;
/* ordinea dorita */
}
Dac lista este foarte mare, cea de-a doua funcie este mai bun, deoarece nu parcurge lista,
ceea ce ar lua destul timp. Dac, ns, lista este scurt, dar fiecare element are asociat mult
informaie, adic fiecare component este destul de mare, este preferabil prima funcie.
Cea de-a doua metod nu poate fi folosit dac structurile asociate componentelor conin i alte
variabile referin (pointeri).
Operaia invers celei de inserare este cea de eliminare din list a unui element. Eliminarea
elementului curent, chiar dac este primul din list, se face astfel:
void eliminare_elem_curent (struct st_orice_tip *curent, struct st_orice_tip *primul)
{
struct st_orice_tip *elimin; /* pointer local la elementul de eliminat (curent)*/
if ( curent == primul )
/* daca trebuie eliminat chiar 'primul' */
19

primul = curent -> urmator; /* devine 'primul' campul 'urmator' */


else
{
/* altfel trebuie determinata pozitia elementului de eliminat */
elimin = primul;
while ( elimin -> urmator != curent )
elimin = elimin -> urmator;
elimin -> urmator = curent -> urmator;/* elimnarea propriu-zisa */
}
free (curent);
}
Eliminarea unui element situat dup un element marcat prin variabila referin curent se face
astfel:
void eliminare_elem_dupa (struct st_orice_tip *curent)
{struct st_orice_tip *elimin; /* elementul de eliminat */
elimin = curent -> urmator; /* salvarea referintei la urmatorul celui ce va fi eliminat */
curent -> urmator = elimin -> urmator;/* refacerea legaturii ocolind elementul eliminat */
free (elimin);
/* eliberare spatiu de memorie ocpat de element */
}
S considerm un alt exemplu, i anume s construim o list ordonat cresctor de numere
ntregi, distincte (copiile nu se mai insereaz n list). Inserarea elementului curent n list se va face
naintea primului element ce are n cmpul de informaie o valoare mai mare dect cea din elementul de
inserat, i dup cel ce conine o valoare mai mic (dac exist, altfel valoarea de inserat va deveni
prima). Inserarea naintea elementului respectiv se poate face folosind una dintre metodele anterioare,
sau ntruct tot parcurgem lista de la nceput pentru fiecare element nou, putem s utilizm dou
variabile referin la dou elemente succesive din list, notat anterior i curent, care vor referi
elementul curent i cel anterior lui. Dup afiarea listei, memoria alocat este eliberat (utiliznd
funcia free()).
/* listord.c - construiete o lista ordonata de numere intregi, distincte */
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
struct st_numar {
int numar;
struct st_numar *next;
};
int nvl=0;
/* numarul de valori afisate pe linie */
void main()
{
struct st_numar *inserare (struct st_numar *, int );
struct st_numar *prim, *vr;
int nr;
clrscr();
printf("Programul construieste o lista ordonata, de numere distincte.\n");
printf("Introducerea numerelor se termina cu o valoare nenumerica.\n");
printf("Numar de inserat in lista: ");
20

prim=(struct st_numar *) malloc(sizeof (struct st_numar));


scanf("%d", &prim->numar);
/* citire numar de introdus in lista */
prim->next=NULL;
printf("Numar de inserat in lista: ");
while (scanf("%d", &nr)==1){
prim=inserare (prim, nr);
printf("Numar de inserat in lista: ");
}
/* tiparirea listei introduse cu eliberarea memoriei alocate */
printf("\nLista de numere ordonata este:\n");
while (prim)
{vr=prim; /* variabila auxiliara pentru a apela functia free() */
printf("%d -> ", vr->numar);
nvl++;
if (nvl%8==0)
/* daca s-au tiparit 8 valori pe linie */
printf("\n"); /* se trece pe o linie noua */
prim=prim->next;
free(vr); /* eliberare memorie alocata pentru vr */
}
printf("NULL\n"); /* tiparire sfarsit de lista */
}
struct st_numar *inserare (struct st_numar *primul, int n){
/* daca nu se mai poate aloca memorie se rutrneaza poiterul NULL,
altfel se returneaza pointerul la primul numar din lista,
care se poate modifica daca se insereaza un numar mai mic
decat primul din lista */
struct st_numar *anterior, *curent, *vr;
vr=(struct st_numar *) malloc(sizeof(struct st_numar));
if (vr == NULL){
printf("\nNu mai introduceti numere!!!\nMEMORIE PLINA!!!\n");
return (primul);
}
else
{
vr->numar=n;
anterior=NULL;
/* initializare pointeri 'anterior' si 'curent' */
curent=primul; /* pentru det. pozitie element de inserat, vr */
while (curent != NULL && vr->numar > curent->numar)
{
anterior=curent;
curent=curent->next;
} /* vr trebuie inserat intre anterior si curent */
if(vr->numar == curent->numar) /* daca numarul exista in lista */
{free(vr); /* elibereaza spatiul alocat variabilei 'vr' */
return (primul); /* nu mai insereaza si termina inserarea */
}
vr->next=curent;
if (anterior == NULL) /* se insereaza in fata primului din lista */
primul=vr; /* este pus primul in lista */
21

else
anterior->next=vr; /* este pus dupa 'anterior' */
return (primul);
}
}
Tratarea recursiv a unei liste
Lista este un exemplu de structur de date recursiv (n exemplul anterior, struct st_numar),
ntruct un cmp al ei (next) face referire la definiia structurii ( struct st_numar * ).
n al doilea rnd o list nlnuit poate fi definit recursiv, astfel: lista poate fi vid sau poate
consta dintr-un element ce conine o referin la list. O alt caracteristic de recursivitate a unei liste
este aceea c se pot scrie funcii recursive pentru tratarea listelor. n acest caz corpul funciei conine, n
principiu, definiia anterioar i o instruciune if, care este necesar pentru a separa tratarea operaiilor
necesare unei liste vide, de cealalt situaie corespunztoare tratrii unui singur element al listei i apoi
apelul recursiv al funciei pentru tratarea restului listei. Vom relua problema citirii unui ir de cuvinte
(un text) i afiarea lor n ordinea citirii, utiliznd pentru cele dou operaii, ataare element n list i
afiare un element din list, dou funcii recursive.
/* listrec1.cpp - programul construieste o lista de cuvinte, introduse
de la tastatura, pe care apoi le afiseaza, utilizand functii recursive:
atsare - pentru construire lista, si
afisare - pentru afisare lista;
Varianta 2: elementul din lista contine adresa cuvantului citit,
pentru care se aloca spatiu (strdup) la citire; */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#define SIR_VID ""
#define DIMC 25
int nvl=0;
struct st_cuvant
{
char *cuvant; /* pointer la sirul de caractere (cuvant) */
struct st_cuvant *urmator;
};
int atasare (struct st_cuvant * vr)
{ /* citeste un cuvant, creaza o variabila dinamica vr si o ataseaza la lista */
char cuv[DIMC], *pc;
if (vr->urmator==NULL) /* daca referinta este NULL se ceaza un pointer */
{
vr->urmator=(struct st_cuvant *)malloc(sizeof(struct st_cuvant));
if (vr->urmator==NULL)
return (0);
else
{
gets(cuv);
if (!strcmp(cuv,SIR_VID))
22

{vr->urmator=NULL;
return(0);
}
else
{if(!(pc=strdup(cuv)))
{vr->urmator=NULL;
return(0);
}
else
{
vr=vr->urmator;
vr->urmator = NULL;
vr->cuvant=pc;
return (1);
}
}
}
}
else

/* daca s-a putut crea un pointer se continua atasarea */


return(atasare (vr->urmator));

}
void afisare (struct st_cuvant * curent)
{
if (curent)
{
printf ("%s -> ", curent -> cuvant);
nvl++;
if(nvl%5==0)
printf("\n");
afisare (curent -> urmator);
}
}
void main()
{
struct st_cuvant *primul;
char c[DIMC];
clrscr();
printf("Programul construieste o lista de tip coada cu cuvinte de la"
" tastatura\n");
printf("Programul ia sfarsit cu un cuvant vid, adica RETURN,"
" la inceput de linie.\n");
gets(c);
primul->cuvant=strdup(c);
primul->urmator=NULL;
while (atasare(primul));
afisare (primul);
printf("NULL\n");
}
23

Alte operaii uzuale asupra unei liste simplu nlnuite, de tip coad sau stiv, sunt descrise n
continuare.
Determinarea lungimii unei liste.
typedef struct st_tip_info TINF;
typedef struct st_lista
{ TINF info;
struct st_lista* urm;
} TEl, * TLista, ** ALista; /* tipurile: element lista, lista si adresa lista*/
size_t LungL (TLista L)
{if (!L)
return (0);
return (1+LungL(L->urm));
}
Afiarea coninutului unei liste n ordine invers:
void AfisInv (TLista L)
{ if (!L)
return;
AfiInv (L->urm);
printf ("%s, ", L->info);
/* am presupus ca lista contine cuvinte*/
}
Cutarea unui element n list, care ndeplinete o anumit condiie descris de funcia de
prelucrare (f, de tipul TFPrelEl) care returneaz o anumit valoare transmis ca parametru (Gasit).
TLista PrimEl (TLista L, TFPrelEl f, int Gasit)
/* intoarce adresa primului gasit sau NULL */
{ for (; L != NULL; L = L->urm)
if ( f (L->info) == Gasit )
return L;
return NULL;
}
TLista UltimEl (TLista L, TFPrelEL f, int Gasit)
/* intoarce adresa ultimului gasit sau NULL */
{ TLista ultim = NULL;
for (; L != NULL; L = L->urm)
if ( f (L->info) == Gasit )
ultim = L;
return u;
}
Prototipul funciei transmis ca parametru (pointer la funcie) este urmtorul:
typedef int (* TFPrelEl)(const void *p,...); /* pointer la functie de prelucrare element
lista - p - adresa element -*/
Cutarea unei legturi (adrese) spre o celul:
ALista CautaEl (ALista aL, TFPrelEl f, int Gasit)
24

/* intoarce adresa legaturii spre celula functia primeste adresa unei celule */
{ TLista a = *aL, p = NULL; /* elementul actual, predecesor */
while (a)
{ if (f(&a->info) == Gasit)
break;
p = a; a = p-> urm;
}
if (!p)
return aL;
/* oprire la inceput */
return &(p->urm);
/* oprire in interior */
}

1.2. Liste dublu nlnuite


Pentru a simplifica operaia de inserare ntr-o list nlnuit, nainte sau dup componenta
curent se poate utiliza o list dublu nlnuit. ntr-o astfel de list fiecare component conine, pe
lng informaia propriu-zis, dou referine: una la componenta anterioar i una alta la componenta
urmtoare; structura unei liste dublu nlnuite de caractere va fi:
struct st_tip_lista
{
st_tip_info
info;
struct st_tip_lista *anterior, *urmator;
};
Pentru parcurgerea eficient, n oricare din cele dou sensuri, este necesar accesul la cele dou
extremiti ale listei (primul i ultimul). Pentru o astfel de list putem defini un element (o celul) care
s conin adresele primului i ultimului element din list (n cmpurile respective, anterior i urmtor),
pentru a permite accesul la cele dou capete ale listei. n acest caz, elementul de acces la list conine
cele dou adrese (primul i ultimul) iar cmpul informaie nu este folosit; ultimul element din list va
conine n cmpul urmator pointerul NULL, dup cum primul element ca conine n cmpul anterior
pointerul NULL.
Se observ c n aceast abordare exist diferene de tratare n cazul operaiilor asupra listei
(inserare / eliminare) la extremiti, deoarece acestea afecteaz fie cmpul primul, fie cmpul ultimul.
Pentru o tratare uniform se poate adopta soluia de organizare ca list circular (cu santinel).
Cmpul info al celulei santinel (de nceput, start, a listei circulare) poate fi utilizat pentru
pstrarea adresei sau valorii unor informaii cu caracter general, cum ar fi referina la funcia de
verificare a relaiei de ordine n cazul n care elementele din list sunt ordonate, dup un anumit criteriu
(funcie).
Dac, prin construcie, se leag ntre ele, ultima component cu prima se obine o list dublu
nlnuit circular. Lista circular vid se consider format dintr-un element legat la el nsui, fr a
conine informaie.
Pentru aceast structur funciile de inserare a unui element nou, nainte sau dup elementul
curent, devin:
void inserare_dupa (struct st_tip *curent; struct st_tip *nou)
{/* inserare element nou dupa elementul curent */
nou -> urmator = curent -> urmator;
nou -> anterior = curent;
curent -> urmator -> anterior = nou;
25

curent -> urmator = nou;


}
void inserare_inainte ( struct st_tip *curent; struct st_tip *nou)
{/* inserare element nou inainte de elementul curent */
nou -> urmator = curent;
nou -> anterior = curent -> anterior;
curent -> anterior -> urmator = nou;
curent -> anterior = nou;
}
Iar procedura de eliminare a unui element, curent, dintr-o astfel de list dublu nlnuit va fi:
void eliminare (struct st_tip *curent)
{
curent -> anterior -> urmator = curent -> urmator;
curent -> urmator -> anterior = curent -> anterior;
}
Parcurgerea unei liste circulare nu este la fel de simpl ca n cazul parcurgerii unei liste
nlnuite n care exist referina NULL i care reprezint o condiie de oprire a parcurgerii listei (de
terminare a ei). Parcurgerea listei circulare presupune memorarea punctului de plecare (santinela,
denumit n continuare start), printr-un pointer, i compararea pointerului curent din list cu pointerul
de start. Parcurgerea ia sfrit cnd poziia curent ajunge n punctul de plecare.
Parcurgerea listei de la nceput:
for ( x = start->urmator; x != start; x = x->urmator )
{. . . .}
Parcurgerea listei de la sfrit:
for ( x = start->anterior; x != start; x = x->anterior )
{. . . .}
De exemplu, s definim o funcie care determin de cte ori se repet, ntr-o list circular ce
conine caractere, deja creat, caracterul aflat n cmpul respectiv al variabilei referin (pointer) start.
Funcia va primi caracterul cutat n santinel (start->car) i va returna caracterul cutat (referina
acestuia) i numrul su de apariii n lista circular (numar_aparitii_car).
struct st_litera
{
char car;
struct st_litera *urmator;
};
int numar_aparitii_car (struct st_litera *start, char *pcar)
{
int
numar = 0;
struct st_litera *curent;
/* pointerul cu care se parcurge lista */
*pcar=start->car;
/* se transmite prin adresa primita, caracterul cautat*/
curent=start->urm;
while (curent != start)/* cat timp referinta la urmatorul din lista */
{
/* nu a ajuns la santinela/ start */
if (start->car == curent->car) /* se numara daca este caracterul */
numar++;
26

curent=curent->urm;
}
return (numar);
}
Se poate constata c listele circulare dublu nlnuite sunt mai uor de prelucrat (operaii de
eliminare, inserare etc.) dect listele simplu nlnuite, deoarece pentru operaii simple nu este necesar
o parcurgere prealabil. n schimb listele circulare sunt mai voluminoase (deoarece fiecare eleement
conine o referin n plus) i gestionarea lor este mai lent deoarece ea trebuie s actualizeze mai multe
referine (pointeri).

1.3. Arbori
Aceste structuri de tip arbore sunt foarte des ntlnite:
- structura informaiei nregistrat pe un suport magnetic (disc), cu directoare i
subdirectoare, este un arbore generalizat (sau multici, ntruct are mai muli descendeni,
subarbori);
- cuprinsul unei cri (arbore multici);
- organizarea administrativ a societilor comerciale, universiti, etc.;
- structura aparatelor de orice fel (acestea au uniti funcionale, compuse la randul lor din
subansamble);
- programul meciurilor dintr-un turneu eliminatoriu (arbore binar);
- arborele unei expresii aritmetice (arbore binar);
Variabilele referin pot fi folosite pentru reprezentarea de structuri mai generale dect listele.
Astfel pot fi reprezentate grafurile orientate: nodurile grafului sunt reprezentate prin structuri iar arcele
sunt reprezentate prin referine. un caz particula al grafurilor l reprezint arborii. Un arbore este format
dintr-un nod radacin, cruia ii este ataat un numar finit de subarbori. Orice nod dintr-un arbore poate
fi privit ca rdacina a unui (sub)arbore.
Arborele are dou proprieti: substructurile legate la orice nod sunt distincte i exist un nod
numit rdcin din care este accesibil orice nod parcurgnd un numr finit de arce.
Dac un nod a conine o referin la un alt nod b, atunci a este ascendentul lui b, iar b
descendentul lui a. Nodul rdcin nu are ascendeni, iar nodurile terminale, numite i frunze, nu au
descendeni.
Ca i o list un arbore este o structur recursiv, ce poate fi definit astfel: un arbore este fie vid,
fie constituit dintr-un nod ce conine referine la arbori disjunci. Iat un exemplu de arbore.

27

Un arbore multici (denumit i arbore generalizat) este un arbore n care nodurile pot avea mai
mult de 2 subarbori; orice arbore multici poate fi transformat n arbore binar.
Un caz particular al arborilor l constituie arborele binar, caracterizat prin faptul c fiecare nod
are cel mult doi descendeni, deci are referine la cel mult doi subarbori. Exemple de arbori binari sunt:
- arborele genealogic, avnd ca nod rdcin om persoan, iar ca noduri descendente cei doi prini,
nodurile descendente ale acestora cei patru bunici etc.
- rezultatele unui turneu eliminator se reprezint tot printr-un arbore binar, avnd ca nod rdcin pe
ctigtor, nodurile descendente ale acestuia pe cei doi finaliti, urmtoarele noduri descendente
sunt cei patru semifinaliti etc.
ntr-un arbore binar fiecare nod se reprezint printr-o structur avnd minim trei cmpuri: un cmp ce
conine informaia din nod i cele dou referine la cei doi subarbori, notai stng (st) i drept (dr).
struct st_arbore
{
tip_informatie info;
struct st_arbore *st, *dr;
}TNod, *AArb;
Parcurgerea unui arbore binar poate fi definit recursiv pentru fiecare nod al acestuia:
- prelucrarea informaiei din nod;
- parcurgerea subarborele stng;
- parcurgerea subarborele drept;
Modificnd ordinea acestor operaii se pot defini 6 moduri diferite de parcurgere a unui arbore. Dac
ns impunem ca parcurgerea s nceap ntotdeauna cu subarborele stng naintea celui drept rmn
numai 3 moduri de parcurgere (n adncime):
- parcurgere n preordine, denumit i rdcin-stng-dreapta RSD, care prelucreaz mai nti
informaia din nod i apoi parcurge subarborii (bineneles n ordinea stnga-dreapta);
- parcurgere n inordine, sau SRD, n care ordinea este: parcurgere subarbore stng, prelucreaz
nodul, parcuregere subarbore stng;
- parcurgere n postordine, sau SDR, n care ordinea este: parcurgere subarbori dreapta-stnga,
prelucreaz informaia din nod.
S considerm urmtoarea structur de arbore binar, pentru a exemplifica cele trei moduri de
parcurgere (n adncime) a unui astfel de arbore.

28

Cele trei moduri de parcurgere a arborelui vor parcurge arborele n


urmtoarele secvene:
- preordine (RSD)

E B A D C F H G I

- inordine (SRD)

A B C D E F G H I

- postordine (SDR)

A C D B G I H F E

S considerm o alt structur de arbore binar, reprezentnd o expresie aritmetic, i s


percurgem (n adncime) acest arbore (prin cele trei metode).

preordine (RSD): + ( * ( a , + ( b , c ) ) , / ( d , e ) )
inordine (SRD): ( ( a * ( b + c ) ) + ( d / e ) )
postordine (SDR): ( ( a , ( b , c ) + ) * , ( d , e ) / ) +
29

Expresiile aritmetice pot fi reprezentate utiliznd arbori binari, respectnd urmtoarele reguli:
- fiecare operaie aritmetic corespunde unui nod neterminal, care conine operaia respectiv,
iar subarborii si reprezint operanzii si, n ordinea, primul operand este cel din stnga i
apoi cel din dreapta;
- fiecare nod terminal conine o variabil sau o constant;
- rdcina conine ultima operaie ce se efectueaz.
Parcurgerea arborelui n postordine va furniza forma polonez asociat unei expresii aritmetice,
ca n exemplul prezentat anterior.
Se pot descrie funcii recursive de parcurgere a unui arbore binar pentru oricare dintre cele trei
moduri. Iat, de exemplu, funcia pentru pacurgerea n preordine (RSD).
void preordine (AArb arbore)
{
if (arbore != NULL)
{
prelucreaza ( arbore);
preordine (arbore -> st);
preordine (arbore -> dr)
}
}
Modificnd ordinea operaiilor se pot obine i celelate dou modri de parcugere. Vom descrie
n continuare alte funcii des utilizate n aceste aplicaii. Prima este o funcie de inserare a unei valori
printre valorile unui arbore binar ordonat (sau de cutare, cum mai este denumit); informaiile asociate
nodurilor pot fi numere, scalari, iruri de carctere ordonate alfabetic sau alte structuri ordonate dup o
anumit cheie. Inserarea unei noi valori presupune adugarea unui nod frunz la arbore. Locul de
inserare depinde de poziia relativ a informaiei acestui nod fa de informaiile deja existente n
arbore. Dac arborele este NULL nseamn c am gsit poziia corespunztoare pentru noul nod, altfel
se compar informaia de inserat cu informaiile referite de arbore: dac sunt mai mici se continu
cutarea n subarborele stng, dac sunt mai mari se continu cutarea n subarborele dreapta.
int inserare (AArb arbore; tip_informatie val)
{
if ( val < arbore -> info )
if (arbore->st)
/* exista aceasta ramura ? */
inserare ( arbore -> st , val ); /* exista, se continua cautarea pe ramura stanga */
else
{
/* se insereaza aici, intrucat ramura nu exista; se creaza nodul */
arbore->st = ( struct st_arbore ) malloc ( struct st_arbore);
if ( arbore->st == NULL)
/* nu mai este memorie disponibila */
return ( 0 );
else
{arbore=arbore->st; /* daca nu este NULL se asociaza valoarea */
arbore -> st = NULL; /* si se initializeaza referintele */
arbore -> dr = NULL;/* stanga si dreapta cu NULL */
arbore -> info = val;
return ( 1 );
}
}
30

else if ( val > arbore -> info )


if (arbore->dr)
/* exista aceasta ramura ? */
inserare ( arbore -> dr , val );/* exista, continua cautarea pe ramura dreapta */
else
{
/* se insereaza aici, intrucat ramura nu exista; se creaza nodul */
arbore->dr = ( struct st_arbore ) malloc ( struct st_arbore);
if ( arbore->dr == NULL) /* nu mai este memorie disponibila */
return ( 0 );
else
{arbore=arbore->dr; /* daca nu este NULL se asociaza valoarea */
arbore -> st = NULL; /* se initializeaza referintele subarbori */
arbore -> dr = NULL;/* stanga si dreapta cu NULL */
arbore -> info = val;
return ( 1 );
}
}
else
return ( 1 ); /* nodul exista deja; se poate adauga un camp care sa
contorizez numarul de aparitii a valorii respective */
}
O alt operaie des utilizat este cea de cutare a unei informaii (val) ntr-un arbore. Vom
descrie o funcie care caut, cauta, i care returneaz referina ctre un nod al arborelui ce conine
informia cutat, sau returneaz NULL dac un astfel de nodnu se afl n arbore.
AArb cauta ( AArb arbore, tip_informatie val)
{int gata = 0;
do
{
if ( arbore == NULL)
gata = 1;
else
{
if ( val < arbore -> info)
arbore = arbore -> st;
else if ( val > arbore -> info)
arbore = arbore -> dr;
else
gata = 1;
}
}while ( ! gata );
return ( arbore );
}
Funcia poate fi definit i recursiv.
AArb cauta ( AArb arbore, tip_informatie val)
{
if ( arbore == NULL)
return ( NULL );
else
31

if ( val < arbore -> info)


return ( cauta( arbore -> st) );
else if ( val > arbore -> info)
return ( cauta( arbore -> dr) );
else
return ( arbore );
}
Operaia de eliminare a unui nod, care nu este nod frunz, dintr-un arbore binar este mai
dificil, deoarece trebuie s ne asigurm c arborele rmne tot binar.
Un arbore binar de cutare (ordonat) este o structur de date mult mai bun pentru memorarea i
cutarea datelor. Cu un astfel de arbore, cu N noduri, timpul necesar pentru inserarea sau eliminarea
unui nod este proporional cu log2N, n timp ce durata necesar acelorai operaii ntr-un tablou,
efectund o cutare liniar este prporional cu N.
S considerm un alt exemplu, i anume un program care citete un text i afieaz lista de
cuvinte din text precum i numrul de apariii pentru fiecare cuvnt din text. Vom rezolva aceast
problem, utiliznd pentru memorarea cuvintelor un arbore binar de cutare, n locul unui tablou.
Cutarea ntr-un astfel de arbore este mult mai rapid i n plus va tipri cuvintele n ordine alfabetic
fr s le mai sortm, ntruct arborele este astfel construit nct ele sunt n ordine alfabetic. n
schimb, aceast metod va necesita mai mult spaiu de memorie, deoarece fiecare nod va conine, pe
lng cuvntul respectiv i numrul su de apariii i dou referine.
/* arbrecc.c - arbore, construit recursiv, ce contine cuvinte, si numarul
de aparitie a acestora, citite de la tastatura (cate un cuvant pe linie)
introducerea cuvintelor se termina cu un cuvant vid (Enter la inceputul
liniei), si apoi sunt afisate cuvintele in ordine alfabetica */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <conio.h>
#define LUNGMAX 20
struct st_arbore
{
char *cuvant; /* pointer la cuvantul citit */
int nrap; /* numarul de aparitii */
struct st_arbore *st, *dr;
};
int nle=0;
void actualizare (struct st_arbore * arb , char *cuv){
/* Functia actualizeaza arborele binar.
Daca cuvantul nu a mai fost citit se creaza un nou nod,
altfel se incrementeaza contorul de aparitii al
nodului corespunzator cuvantului citit anterior.
Se determina pe care ramura a arborelui se va insera
cuvantul, sau daca exista deja */
if (strcmp(cuv, arb->cuvant) < 0)
if (arb->st)
actualizare (arb->st, cuv);
32

else

{
arb->st=(struct st_arbore *) malloc (sizeof(struct st_arbore)) ;
arb=arb->st;
arb->cuvant=cuv;
arb->nrap=1;
arb->st=NULL;
arb->dr=NULL;
}
else if (strcmp(cuv, arb->cuvant) >0 )
if(arb->dr)
actualizare (arb->dr, cuv);
else {
arb->dr=(struct st_arbore *) malloc (sizeof(struct st_arbore)) ;
arb=arb->dr;
arb->cuvant=cuv;
arb->nrap=1;
arb->st=NULL;
arb->dr=NULL;
}
else
arb->nrap=arb->nrap + 1;
}
void tiparire_arbore (struct st_arbore *arbo)
/* functia afiseaza arborele de cautare ce contine cuvinte si
numarul lor de aparitii */
{
if (arbo!=NULL)
{
tiparire_arbore (arbo->st) ; /* tiparire arbore stanga */
printf ("%s (ap: %d)\n", arbo -> cuvant, arbo -> nrap ) ;
nle++;
if (nle%24==0){
printf("Apasati o tasta pentru a continua afisarea!\n");
getch();
}
tiparire_arbore (arbo->dr) ; /* tiparire arbore dreapta */
}
}
void main ()
{struct st_arbore *arb;
char cuvant[LUNGMAX] ;
clrscr();
printf("Introduceti cuvinte, care vor fi apoi tiparite in ordine"
" alfabetica:\n");
gets(cuvant) ;
arb=(struct st_arbore *) malloc (sizeof(struct st_arbore)) ;
/* am presupus ca nu se returneaza de catre malloc
valoarea NULL */
arb->cuvant=strdup(cuvant);
33

arb->nrap=1;
arb->st=NULL;
arb->dr=NULL;
gets(cuvant) ;
while (strcmp(cuvant, ""))
{actualizare (arb, strdup(cuvant));
gets(cuvant);
}
printf("Lista ordonata a cuvintelor (numar aparitii):\n");
tiparire_arbore (arb);
}

Ali termeni utilizai n legtur cu arborii sunt:


ordinul (gradul) unui nod, reprezint numrul de subarbori ataai nodului;
noduri interne, sunt nodurile care au subarbori;
noduri externe (noduri terminale sau frunze), sunt nodurile fr subarbori (cu ordin 0);
nivelul unui nod este distana la care se afl fa de rdcin (rdcina are nivelul 0);
nlimea unui arbore este nivelul maxim din arbore.
arbore complet de ordin k toate frunzele se afl pe acelai nivel i toate nodurile interne au
ordinul k; un astfel de arbore, de nlime h, are (k(h+1)-1)/(k-1) noduri, dintre care (kh-1)/(k-1)
noduri interne si kh frunze;
strmo al unui nod x orice nod aflat pe calea (unic) de la rdcin pn la nodul x (rdcina
este strmoul tuturor celorlalte noduri);
descendent al unui nod x orice nod aflat n arborele cu rdcina x;
tat / fiu noduri aflate la distana 1 (numite i stramo / descendent direct);
frai fiii aceluiai nod.

Legat de aceste noiuni se pot realiza operaiile urmtoare.


Numrul de noduri ntr-un arbore:
int NrNoduri (AArb r) /* numarul de noduri din arborele r -*/
{ if (!r)
return 0;
/* arbore vid => 0 noduri */
return 1 + NrNoduri(r->st) + NrNoduri(r->dr);
}
Numrul de niveluri ntr-un arbore (nlimea = numr niveluri 1)
int NrNiv (AArb r) /* numar niveluri (0 pentru arbore vid) */
{ int ns, nd;
if (!r)
return 0;
ns = NrNiv (r->st); nd = NrNiv (r->dr);
return 1 + (ns >= nd ? ns : nd);
}
Numrul de frunze dintr-un arbore.
#define Frunza(a) ((a)->st == NULL && (a)->dr == NULL)
int NrFrunze (AArb a)
{ if (!a)
34

return 0;
if (Frunza(a))
return 1;
return NrFrunze (a->st) + NrFrunze (a->dr);
}
Numrul de noduri de pe nivelul n
int NNN (AArb r, int n) /* numar noduri de pe nivelul n */
{ if (!r)
return 0;
if (n == 0)
return 1;
return NNN(r->st, n-1) + NNN(r->dr, n-1);
}
Gsirea valorii maxime dintr-un arbore nevid
int MaxArb (AArb a)
{ int m = a->info, t;
if (a->st != NULL) { t = MaxArb (a->st);
if (t > m) m = t;
}
if (a->dr != NULL) { t = MaxArb (a->dr);
if (t > m) m = t;
}
return m;
}
Determinarea nivelului pe care se gsete o anumit valoare x.
int NivVal (AArb a, int x)
/*(-1 daca nu exista)*/
{ int r;
if (!a) return -1;
if (a->info == x) return 0;
r = NivVal (a->st, x);
if (r < 0)
{ r = NivVal (a->dr, x);
if (r < 0) return -1;
}
return r + 1;
}
O problem (operaie) puin mai delicat, dect cele prezentate anteror, este cea a eliminrii
unui nod dintr-un arbore binar de cutare. Problema const n a determina poziia n arbore a unei chei,
x, ceea ce este simplu ntr-un arbore de cutare, iar apoi s se elimine nodul cu cheia respectiv. n
principiu avem dou situaii, dup cum nodul respectiv are ambii succesori (subarbori), sau numai pe
unul dintre ei. Mai exist i cazul cnd nu are succesori, dar aici eliminarea nu este o problem, doar se
elibereaz spaiul de memorie alocat nodului respectiv i se terge referina din nodul precedent.
n situaia n care nodul de eliminat nu are dect subarborele drept (deci cel stng este vid), dac
a este adresa nodului cu cheia x (cel de eliminat), atunci se atribuie lui a adresa descendentului drept,
35

ceea ce permite pstrarea proprietii arborelui (cea de cutare). Se procedeaz similar i pentru cazul
n care nodul de eliminat nu are dect subarborele stng (deci cel drept este vid), adic se atribuie lui a
adresa descendentului stng. Cele dou situaii sunt prezentate n figura urmtoare.

Din arborele iniial (cel din stnga) a fost eliminat nodul cu cheia 6, i se obine arborele din
dreapta.
S considerm aceeai problem pentru eliminarea nodului ce conine valoarea 4, din arborele
din figura urmtoare (n partea stng nainte de eliminare, i dreapta dup eliminare).
9

Arborele s-a obinut prin mutarea valorii maxime din subarborele stng valorii 4, adic a valorii
3 n locul valorii 4, i eliminarea frunzei pe care a fost valoarea 3. Mai exist i varianta n care
valoarea minim de pe subarborele drept al nodului ce conine valoarea 4, adic valoarea 5 se mut n
locul valorii 4, i se elimin nodul n care a fost valoarea 5. Ambele variante vor furniza acelai arbore
SRD, la listare.
Dac nodul de eliminat, a, cel ce conine cheia x, are ambii subarbori nevizi, atunci procedeul
de eliminare decurge dup cum urmeaz. Se determin cea mai mare valoare din subarborele stng,
adic nodul (frunza) cel mai din dreapta din subarborele stng, ce conine valoarea maxim din acest
subarbore, imediat mai mic dect cheia x, din arbore. Se va pune aceast valoare n locul lui x, iar
36

frunza respectiv se terge. Sau se poate proceda similar pentru subarborele stng, adic s determinm
cea mai mic valoare din acesta, care va fi valoarea imediat mai mare dect x, din arbore. ntruct
algoritmul este recursiv vom defini o funcie recursiv pentru eliminare. ntregul program este
prezentat n continuare.
/* arbelnod.cpp - programul construieste recursiv si tipareste un arbore
de numere intregi, utilizand functiile recursive: 'inserare' si 'listare'.
Arborele contine pe langa valorile intregi ordonate (SRD)
si numarul de aparitii pentru valorile ce se repeta.
Programul permite elimiarea unei anumite valori (chei), adica
un nod din arbore ce contine cheia, pastrand arborele de cautare (SRD). */
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
typedef struct tnod
{int nr;
int
ap;
struct tnod *pstg;
struct tnod *pdr;
} ARB;
int nel = 0;
ARB *e;
void inserare (ARB * &v, int val){
ARB *pc;
int gasit;
if (!v) {v = new ARB; v->nr=val; v->ap=1;
v->pstg=NULL; v->pdr=NULL;}
else if (v->nr > val)
inserare(v->pstg, val);
else if (v->nr < val)
inserare(v->pdr, val);
else v->ap++;
}
void inlocuire_nod(ARB * &n){ // n - nodul de eliminat
// se inlocuieste cu maximul de pe subarborele stang (acum maximul din n),
// care este pe ramura dreapta a lui 'n',
// sau cu minimul de pe subarborele drept (adica minimul din arborele n),
// rezultatul final fiind acelasi, adica un arbore de cautare
if (n->pdr)
inlocuire_nod(n->pdr);
else {e->nr=n->nr;
// e - nodul care se elimina (continutul sau)
e->ap=n->ap; // se actualizeaza valorile lui 'e' cu maximul din
e=n;
// arborele nodului 'n' , ramura dreapta
n=n->pstg; // pastrez legatura la ramura stanga a nodului
free(e);
// pentru care elimin ramura dreapta, a carei
}
// valori s-au mutat in nodul 'eliminat' (inlocuit)
}
void elimina_nod (ARB * &a, int x){// a-arbore, x-valoarea de eliminat
if (!a) printf("\nCheia cautata %d nu este in arbore!\n", x);
else if (x < a->nr)
// se cauta pozitia valorii 'x'
37

elimina_nod (a->pstg, x);


else if (x > a->nr)
elimina_nod (a->pdr, x);
else // s-a gasit pozitia lui 'x'
{e=a;
if (!a->pstg){ // are subarborele stang vid
a=a->pdr;
// se conecteaza subarborele drept
free(e);
// si eliberez spatiul alocat
}
// nodului eliminat
else if (!a->pdr){
// are subarborele drept vid
a=a->pstg; // se conecteaza subarborele stang
free(e);
// si eliberez spatiul alocat
}
// nodului eliminat
else
inlocuire_nod(a->pstg); // sau cu parametru 'a->pdr'
}
// cu modificarile respective in functia de inlocuire
}
void listare_SRD (ARB *v){
if (v) {if (v->pstg)
listare_SRD (v->pstg);
printf("%3d(%d) -> ", v->nr, v->ap);
nel++;
if (nel%7==0)
printf("\n");
if (v->pdr)
listare_SRD(v->pdr);
}
}
main(){
ARB *pl;
int valoare;
pl = NULL;
clrscr();
printf("Se construieste un arbore de cautare si permite elimarea unui nod.\n");
printf ("\nIntroduceti un sir de valori, terminat cu EOF.\n");
printf ("Valoare:");
while ( scanf ("%d", &valoare) != EOF) {
inserare (pl,valoare);
printf ("Valoare:");
}
/* afisare arbore */
printf("\nListare arbore SRD:\n");
listare_SRD (pl);
printf ("NULL\n");
printf("\nValoare de eliminat: ");
scanf("%d", &valoare);
elimina_nod(pl, valoare);
/* afisare arbore dupa eliminare */
printf("\nListare arbore SRD, dupa eliminarea valorii %d:\n", valoare);
38

nel=0; // initializare contor afisare pe ecran


listare_SRD (pl);
printf ("NULL\n");
printf("Terminare dupa apasarea unei taste!\n");
getch();
}
Arbori generalizai
S considerm acum cazul arborilor generalizai, adic arbori n care fiecare nod poate conine
referine ctre mai mult de doi subarbori, fr ca numrul lor s fie constant. Dac numrul subarborilor
poate crete cu o valoare finit, este comod de utilizat un tablou de referine care se refer ctre aceti
subarbori. Dac ns fixm numrul de noduri la o valoare prea mare, numeroase referine nu vor
utiliza spaiul de memorie rezervat. O soluie a acestei probleme este de a ataa fiecrui nod al arborelui
o list nlnuit a descendenilor si. Fiecare nod conine dou referine, una ctre fiul su iar
cealalt ctre fratele su cel mai apropriat. Una din aceste referine poate fi NULL. Vom reprezenta,
n acest mod, primul arbore, prezentat la ncepulul paragrafului; legturile de descenden sunt
reprezentate (desenate) vertical, iar cele de fraternitate, pe orizontal. Practic, n acest mod, s-a
transformat un arbore generalizat ntr-unul binar:

39

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