Sunteți pe pagina 1din 12

7 Structuri dinamice de date.

Liste

Organizarea de tip listă corespunde unei structurări lineare a datelor, în


sensul că la nivelul fiecărei componente există suficientă informaţie pentru
identificarea următoarei componente a colecţiei. Datele unei mulţimi structurate
prin intermediul listelor sunt referite de obicei prin termenii de noduri, celule,
componente etc.

7.1 Reprezentarea listelor


Reprezentarea unei liste poate fi realizată static prin intermediul structurii de
date vector. În acest caz ordinea componentelor este dată de ordinea pe domeniul de
valori corespunzător indexării şi, în consecinţă, următoarea componentă este implicit
specificată. Memorarea unei mulţimi de date {d1, d2,…, dn} prin intermediul unei
structuri statice poate fi realizată în limbajul C utilizând un masiv unidimensional.
Principalele dezavantaje ale utilizării reprezentării statice rezidă din
volumul de calcule necesare efectuării operaţiilor de inserare/eliminare de noduri şi
din necesitatea păstrării unei zone de memorie alocată, indiferent de lungimea
efectivă a listei.
Aceste dezavantaje pot fi eliminate prin opţiunea de utilizare a structurilor
dinamice. Componentele unei liste dinamice sunt omogene, de tip articol. Fiecare
nod, considerat separat, este o structură eterogenă, conţinând o parte de informaţie
şi câmpuri de legătură care permit identificarea celulelor vecine. Câmpurile de
legătură sunt reprezentate de date de tip referinţă (adresă).
În cazul listelor cu un singur câmp de legătură (simplu înlănţuite), valoarea
câmpului indică adresa nodului următor, în timp ce în cazul listelor cu dublă
legătură (dublu înlănţuite), valorile memorate în câmpurile de legătură sunt
adresele componentelor care preced şi, respectiv, urmează celulei. În ambele
situaţii, câmpul de legătură pentru indicarea celulei următoare corespunzător
ultimei componente a listei are valoarea NULL în cazul listelor „deschise” (lineare)
Structuri dinamice de date. Liste

şi respectiv indică adresa primei componente din listă în cazul listelor „închise”
(circulare).
Declararea tipurilor de date C pentru definirea structurilor de liste dinamice
simplu şi respectiv dublu înlănţuite este:
a) Listă simplu înlănţuită b) Listă dublu înlănţuită

typedef struct nod{ typedef struct nod{


tip_informatie inf; tip_informatie inf;
struct nod *leg; struct nod *ls, *ld;
} list, *lista; } list, *lista;

unde tip_informatie este numele tipului de date C utilizat pentru memorarea


fiecărei date din mulţimea{d1, d2,…, dn}.
În cele ce urmează vom considera că tip_informatie este tipul C int.

7.2 Operaţii primitive asupra listelor


Accesul la informaţia stocată într-o variabilă de tip listă revine la
efectuarea următoarelor operaţii primitive: regăsirea nodului (dacă există) care
corespunde unei chei date (condiţie impusă asupra valorii câmpului de informaţie),
inserarea unei noi componente în listă, eliminarea componentei (componentelor) cu
proprietatea că valorile câmpurilor de informaţie satisfac o anumită cerinţă şi
înlocuirea câmpului de informaţie corespunzător unei componente printr-o
informaţie dată (modificată).
Accesarea componentelor unei liste reprezentată printr-o structură statică
poate fi realizată atât secvenţial, cât şi direct, utilizând valorile indicelui considerat
pentru indexare, în timp ce accesarea componentelor unei liste dinamice se
realizează de regulă numai secvenţial, începând cu prima componentă şi
continuând cu următoarele, pe baza valorilor câmpurilor de legătură.
Convenţional, numim cap al listei dinamice pointerul a cărui valoare este adresa
primei componente a listei. În continuare ne vom referi exclusiv la liste dinamice, studiul
listelor reprezentate prin intermediul vectorilor fiind propus cititorului.
1. Parcurgerea datelor memorate într-o listă
Funcţia C parc implementează parcurgerea unei liste dinamice în varianta
simplu înlănţuită şi în cazul listelor dublu înlănţuite. Se presupune că declaraţiile
de tip pentru definirea structurilor de liste menţionate anterior sunt globale, relativ
la procedurile descrise în continuare.
a) Lista reprezentată prin structură dinamică simplu înlănţuită
void parc(lista cap)
{ if(cap)
{ printf("%i ",cap->inf);
parc(cap->leg);
}
}
Programarea calculatoarelor

b) Lista reprezentată prin structură dinamică dublu înlănţuită

void parc(lista cap)


{ if(cap)
{ printf("%i ",cap->inf);
parc(cap->ls);
}
}

2. Regăsirea unei date într-o colecţie memorată într-o listă


Funcţia C cauta calculează adresa nodului în care este găsit elementul
căutat. Dacă valoarea căutată nu se regăseşte printre elementele listei, funcţia
returnează valoarea NULL.
a) Lista reprezentată prin structură dinamică simplu înlănţuită
lista cauta(lista cap,int info)
{ if(cap==NULL)return NULL;
else if(cap->inf==info) return cap;
else return cauta(cap->leg,info);
}

b) Lista reprezentată prin structură dinamică dublu înlănţuită


lista cauta(lista cap,int info)
{ if(cap==NULL)return NULL;
else if(cap->inf==info) return cap;
else return cauta(cap->ls,info);
}

3. Inserarea unei date într-o listă


Includerea unei noi componente într-o listă poate fi realizată, în funcţie de
cerinţele problemei particulare, la începutul listei, după ultima componentă din
listă, înaintea/după o componentă cu proprietatea că valoarea câmpului de
informaţie îndeplineşte o anumită condiţie.
Deoarece prin inserarea unei componente se poate ajunge la depăşirea
spaţiului disponibil de memorie, este necesară verificarea în prealabil dacă este
posibilă inserarea sau nu (dacă se poate aloca spaţiu de memorie pentru
componenta de inserat). În continuare ne vom referi la liste dinamice simplu
înlănţuite. Operaţiile de inserare în cazul listelor dinamice dublu înlănţuite pot fi
realizate similar cazului listelor simplu înlănţuite, cu specificarea ambelor câmpuri
de adresă ale nodurilor.
Pentru exemplificarea operaţiei de inserare sunt prezentate funcţiile de
inserare la începutul listei, inserare după ultimul element al listei şi inserarea unei
celule după un nod cu informaţie dată.

Inserarea la începutul listei


Funcţia C inserare_la_inceput returnează valoarea 1 dacă adăugarea unui
nou element este posibilă (spaţiul de memorie este suficient pentru o nouă alocare),
altfel returnează 0. În cazul în care inserarea este posibilă, prin apelul funcţii este
realizată adăugarea unui nou nod la începutul listei.
Structuri dinamice de date. Liste

int inserare_la_inceput(lista *cap,int info)


{ lista nou;
if(nou=(lista)malloc(sizeof(list)))
{ nou->inf=info;
nou->leg=*cap;
*cap=nou;
return 1;
}
return 0;
}

Inserarea după ultima componentă a unei liste


Funcţia C inserare_la_sfarsit returnează 1 dacă şi numai dacă este posibilă
o inserare, altfel calculează 0. Pentru inserarea unui nou nod în listă după ultima
celulă este necesar calculul ultimului nod al listei, notat p.

int inserare_la_sfarsit(lista *cap,int info)


{ lista nou;
if(nou=(lista)malloc(sizeof(list)))
{ nou->leg=NULL;nou->inf=info;
if(cap==NULL)*cap=nou;
else
{ for(lista p=*cap;p->leg;p=p->leg);
p->leg=nou;
}
return 1;
}
return 0;
}

Inserarea unei informaţii după o celulă cu informaţie cunoscută


Inserarea unui nou nod într-o listă identificată prin variabila cap după o
celulă p cu informaţie cunoscută, infod, poate fi realizată astfel. Este apelată funcţia
de căutare cauta, care calculează nodul p cu proprietatea că informaţia memorată în
p este infodat. Dacă p este adresa vidă sau dacă spaţiul de memorie disponibil nu
este suficient, inserarea nu poate fi realizată. În caz contrar, este inserat un nou nod
între celulele p şi p->leg.

int inserare_dupa_informatie(lista cap,int info,int infod)


{
lista nou,p;
if(nou=(lista)malloc(sizeof(list)))
if(p=cauta(cap,infod)){
nou->inf=info;
nou->leg=p->leg;
p->leg=nou;
return 1;
}
return 0;
}
Programarea calculatoarelor

4. Eliminarea unei date dintr-o listă


Modificarea conţinutului unei liste prin eliminarea uneia sau mai multor
componente poate fi descrisă secvenţial, astfel încât este suficient să dispunem de o
procedură care realizează eliminarea unei singure componente.
Criteriile de eliminare pot fi formulate diferit, cele mai uzuale fiind: prima
componentă, ultima componentă, prima componentă care îndeplineşte o anumită
condiţie, respectiv componenta care precede/urmează primei componente care
îndeplineşte o condiţie dată.
În aceste cazuri este necesară verificarea existenţei în lista considerată a
componentei ce trebuie eliminată. Verificarea asigură şi testarea faptului că lista
prelucrată este vidă sau nu.
Informaţia i ataşată nodului eliminat din listă reprezintă dată de ieşire
pentru orice modul de eliminare, în cazul în care i nu este cunoscută înaintea
eliminării (de exemplu, atunci când este solicitată eliminarea unei celule care
conţine o informaţie dată) .
Eliminarea unui nod p poate fi realizată logic sau fizic. Eliminarea logică a
celulei p este efectuată excluzând p din lista dinamică prin setarea legăturii nodului
care precede p pe adresa succesorului lui p, dacă p nu este adresa primului element
al listei, cap, respectiv prin atribuirea adresei primului element al listei cu
cap->leg, în caz contrar.
Eliminarea cu ştergere fizică unui nod p presupune redenumirea acelui nod
în scopul eliberării memoriei ocupate de p şi efectuarea operaţiilor descrise în
cadrul procesului de eliminare logică.
În continuare sunt prezentate următoarele tipuri de eliminări, cu ştergere
fizică.

Eliminarea primei componente a unei liste


Funcţia C elimina_de_la_inceput returnează 1 dacă lista nu este vidă, deci
eliminarea primului nod este posibilă, altfel returnează 0. Dacă lista conţine măcar
un nod, este eliminată prima celulă.

Int elimina_de_la_inceput(lista *cap,int *info)


{ if(*cap)
{ lista aux=*cap;
*info=aux->inf;
*cap=(*cap)->leg;
free(aux);
return 1;
}
return 0;
}

Eliminarea ultimei componente a unei liste


Similar operaţiei de inserare a unui nod după ultima celulă a unei liste,
eliminarea ultimului nod presupune determinarea acelei celule p cu proprietatea că
p->leg este NULL. Funcţia C elimina_ultim returnează 1 dacă lista nu este vidă,
Structuri dinamice de date. Liste

caz în care este eliminat cu ştergere ultimul nod al listei. Dacă lista este vidă,
funcţia calculează valoarea 0.
Int elimina_ultim(lista *cap,int *info)
{ if (*cap)
{ if((*cap)->leg)
{ for(lista p=*cap;p->leg->leg;p=p->leg);
*info=p->leg->inf;
free(p->leg);
p->leg=NULL;
}
else
{ *info=(*cap)->inf;
free(*cap);
*cap=NULL;
}
return 1;
}
return 0;
}

Eliminarea primei celule a unei liste care are informaţia egală


cu o informaţie dată
Pentru realizarea acestei operaţii se poate proceda astfel. Sunt calculate aux
şi p, unde aux este nodul care precede celulei cu informaţie dată în lista din care
este efectuată eliminarea (funcţia C cautaprecedent) şi p=aux->leg.. Dacă p este
NULL, atunci eliminarea este imposibilă. Dacă aux este NULL, atunci eliminarea
revine la extragerea cu ştergere a primului nod din listă, altfel este eliminată celula
p, succesoare a lui aux în listă. Funcţia C elimina_informatie implementează
operaţia de eliminare a unui nod cu informaţie dată, info, din lista identificată prin
parametrul cap.
lista cautaprecedent(lista cap,int info, lista *aux)
{ lista p;
if(cap==NULL)return NULL;
else { for(p=NULL,*aux=cap;(*aux)&&
((*aux)->inf-info);p=*aux,*aux=(*aux)->leg);
if((*aux)==NULL)return NULL;
return p;
}
}

int elimina_informatie(lista *cap,int info)


{ lista aux,p;
p=cautaprecedent(*cap,info,&aux);
if(aux==*cap)
{ *cap=(*cap)->leg;
free(aux);
return 1;
}
else
if(p)
{ p->leg=aux->leg;
Programarea calculatoarelor

free(aux);
return 1;
}
return 0;
}

Eliminarea nodului care succede primei componente al cărei câmp


de informaţie este cunoscut
Funcţia C elimina_dupa_informatie returnează valoarea 1 dacă eliminarea
este posibilă, altfel calculează valoarea 0. În situaţia în care informaţia infodat a
fost găsită în cîmpul corespunzător nodului nodul p (p nu este NULL) şi p are
succesor în listă, este realizată eliminarea nodului p->leg.
int elimină_dupa_informatie(lista cap,int *info, int
infodat)
{ lista aux,p;
p=cauta(cap,infodat);
if((p)&&(p->leg))
{ aux=p->leg;
p->leg=aux->leg;
*info=aux->inf;
free(aux);
return 1;
}
return 0;
}

7.3 Liste circulare


În anumite situaţii este preferabilă renunţarea la structura de tip linear a
listelor şi utilizarea unei legături de la ultima componentă către capul listei,
rezultând structura de listă circulară.
Principalul avantaj al utilizării acestui tip de structură rezidă din posibilitatea
de accesare oricărui alt element al listei pornind din orice element. Dacă nodul
căutat este situat după nodul curent, este iniţiat un proces de căutare similar listelor
lineare. În caz contrar, nodul poate fi accesat prin parcurgerea listei de la primul
său element, care, în procesul de căutare, este atins după parcurgerea în întregime a
listei, începând de la nodul curent.
În continuare sunt prezentate funcţiile C pentru realizarea unor operaţii de
bază în lucrul cu liste circulare.

#include<stdio.h>
#include<conio.h>
#include<alloc.h>

typedef struct nod


{ int inf;
struct nod *leg;
} list, *lista;
Structuri dinamice de date. Liste

int inserare_la_inceput(lista *,int);


int stergere_la_inceput(lista *,int *);
int inserare_la_sfarsit(lista *,int);
int stergere_la_sfarsit(lista *,int *);
void parc(lista);
lista cauta(lista,int);

void main()
{
clrscr();
int n,info;
lista cap=NULL;
printf("Numarul de noduri:");
scanf("%i",&n);
printf("Introduceti informatiile\n");
for(int i=0;i<n;i++){
scanf("%i",&info);
if(inserare_la_inceput(&cap,info));
else
{printf("\n Spatiu insuficient \n");
return;
}
}
printf("\nLista rezultata\n");
parc(cap);
printf("\n\nLista dupa extragerea primului
element:\n");
if(stergere_la_inceput(&cap,&info)) parc(cap);
else printf("\nEroare: lista vida");
printf("\n\nInformatia nodului de introdus la
sfarsit:");
scanf("%i",&info);
if(inserare_la_sfarsit(&cap,info)){
printf("Lista rezultata\n");
parc(cap);
}
else
printf("\n Spatiu insuficient \n");
printf("\n\nLista dupa extragerea ultimului
element:");
if(stergere_la_sfarsit(&cap,&info)){
printf("\nInformatia extrasa %i\nLista
rezultata:",info);
parc(cap);
}
else printf("\nEroare:Lista vida");
getch();
}

void parc(lista cap)


{
lista p=cap;
if(cap){
printf("%i ",cap->inf);
for(p=p->leg;p-cap;p=p->leg)
Programarea calculatoarelor

printf("%i ",p->inf);
}
else printf("\nLista vida");
}

lista cauta(lista cap,int info)


{
if(cap==NULL)return NULL;
if(cap->inf==info) return cap;
for(lista p=cap->leg;p!=cap;p=p->leg)
if(p->inf==info) return p;
return NULL;}
int inserare_la_inceput(lista *cap,int info)
{
lista nou,ultim;
if(nou=(lista)malloc(sizeof(list))){
nou->inf=info;
nou->leg=*cap;
if(*cap){
for(ultim=*cap;ultim->leg!=(*cap);ultim=ultim->leg);
ultim->leg=nou;
}
else nou->leg=nou;
*cap=nou;
return 1;
}
return 0;
}
int stergere_la_inceput(lista *cap,int *info)
{
if(*cap){
lista aux=*cap;
*info=aux->inf;
for(lista ultim=*cap;
ultim->leg!=(*cap);ultim=ultim->leg);
if(ultim==(*cap)) *cap=NULL;
else{
*cap=(*cap)->leg;
ultim->leg=*cap;
}
free(aux);
return 1;
}
return 0;
}

int inserare_la_sfarsit(lista *cap,int info)


{
lista nou,ultim;
if(nou=(lista)malloc(sizeof(list))){
nou->leg=*cap;nou->inf=info;
if(*cap==NULL){
*cap=nou;
(*cap)->leg=*cap;
}
Structuri dinamice de date. Liste

else{
for(ultim=*cap;ultim->leg!=(*cap); ultim=ultim->leg);
ultim->leg=nou;
}
return 1;
}
return 0;
}

int stergere_la_sfarsit(lista *cap,int *info)


{
if (*cap){
if((*cap)->leg!=(*cap)){
for(lista pultim=*cap;
pultim->leg->leg!=(*cap);pultim=pultim->leg);
*info=pultim->leg->inf;
free(pultim->leg);
pultim->leg=(*cap);
}
else{
*info=(*cap)->inf;
free(*cap);
*cap=NULL;
}
return 1;
}
return 0;
}

7.4 Stive şi cozi


Aşa cum a rezultat din subcapitolele precedente, operaţiile de inserare şi
eliminare sunt permise la oricare dintre componentele unei liste. O serie de aplicaţii
pot fi modelate utilizând liste lineare în care introducerea şi respectiv eliminarea
informaţiilor este permisă numai la capete. În acest scop au fost introduse tipurile
de listă stivă şi coadă prin impunerea unui tip de organizare a aplicării operaţiilor
de inserare şi eliminare.

7.4.1 Stiva

Se numeşte stivă o listă organizată astfel încât operaţiile de inserare şi


eliminare sunt permise numai la prima componentă. Acest mod de organizare
corespunde unei gestiuni LIFO (Last In First Out) a informaţiei stocate.
Operaţiile de bază efectuate asupra unei stive pot fi realizate similar
cazului listelor dinamice lineare, ţinând cont că inserarea/extragerea unui element
sunt posibile numai în prima poziţie (vezi modulul de inserare la începutul unei
liste şi respectiv funcţia de extragere a primului nod dintr-o listă, prezentate
în §7.2).
Programarea calculatoarelor

7.4.2 Coada

Se numeşte coadă o listă organizată astfel încât operaţia de inserare este


permisă la ultima componentă, iar operaţia de eliminare este permisă numai la
prima componentă. Acest mod de organizare corespunde unei gestiuni FIFO (First
In First Out) a informaţiei stocate.
Implementarea unei liste de tip coadă poate fi efectuată atât printr-o
structură statică (masiv unidimensional), cât şi printr-o structură dinamică de tip
listă. Pentru optimizarea operaţiilor de inserare/extragere, în cazul implementării
cozilor prin structuri dinamice lineare, este necesară utilizarea a două informaţii:
adresa primei componente şi adresa ultimei componente. Aceste informaţii pot fi
menţinute explicit prin utilizarea a doi pointeri sau prin utilizarea unui pointer şi a
unei structuri de listă circulară.
O variantă alternativă de implementare a unei liste de tip coadă dinamică
este obţinută prin considerarea unei liste circulare, cu memorarea adresei ultimului
element.
În continuare sunt prezentate operaţiile de inserare şi extragere a unei
informaţii dintr-o listă liniară de tip coadă.

#include<stdio.h>
#include<conio.h>
#include<alloc.h>
typedef struct nod{
int inf;
struct nod *leg;
} list, *lista;

int inserare(lista *,lista *,int);


int extragere(lista *,lista *,int *);
void parc(lista);

void main()
{
clrscr();
int n,info;
lista cap=NULL,ultim=NULL;
printf("Numarul de noduri:");
scanf("%i",&n);
printf("Introduceti informatiile\n");
for(int i=0;i<n;i++){
scanf("%i",&info);
if(inserare(&cap,&ultim,info));
else
{printf("\n Spatiu insuficient \n");
return;
}
}
Structuri dinamice de date. Liste

printf("\nCoada rezultata\n");
parc(cap);
printf("\n\nCoada dupa o extragere:\n");
if(extragere(&cap,&ultim,&info)) parc(cap);
else printf("\nEroare: Coada vida");
getch();
}

void parc(lista cap)


{
if(cap){
printf("%i ",cap->inf);
parc(cap->leg);
}
}

int extragere(lista *cap,lista *ultim,int *info)


{
if(*cap){
lista aux=*cap;
*info=aux->inf;
if((*ultim)==(*cap)) *cap=*ultim=NULL;
else *cap=(*cap)->leg;
free(aux);
return 1;
}
return 0;
}

int inserare(lista *cap,lista *ultim,int info)


{
lista nou;
if(nou=(lista)malloc(sizeof(list))){
nou->inf=info;nou->leg=NULL;
if(*cap==NULL) *cap=*ultim=nou;
else{
(*ultim)->leg=nou;
(*ultim)=nou;
}
return 1;
}
return 0;}

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