Sunteți pe pagina 1din 7

Programming Techniques Costin Badica

Dinamic Structures Cosmin Stoica Spahiu

Lucrarea 2 – Liste dinamice


Introducere în alocarea dinamică
Orice algoritm lucrează cu date (numere întregi, reale, şiruri de caractere, etc.). Prin
tipul unei date se înţelege o mulţime de elemente numite valori, pe care este posibil să
le primească o variabilă de acel tip.
Pe mulţimea valorilor unui tip se definesc operaţiile asociate tipului (ex: adunare
scădere, împărţire întreagă, împărţire reală…). Pentru fiecare tip se defineşte modul în
care se memorează valorile sale.
Exemplu: pentru int, valorile se memorează utilizând codul complementar şi
se folosesc 2 octeţi consecutivi.
O variabilă se caracterizează prin: tipul ei, nume şi adresă (convenim să
numim adresă numărul de ordine al primului octet din cei p octeţi consecutivi
necesari, din memoria internă).
Din punctul de vedere al unui programator, memoria calculatorului se prezintă
ca o succesiune de octeţi, fiecare octet având o adresă binară bine stabilită.
Utilizatorul nu are acces direct la adresa variabilei, aceasta fiind alocată în mod
automat de compilatorul limbajului de programare.
Se cunosc două forme de alocare a memoriei de către programator: statică şi
dinamică.
Utilizând forma de alocare statică, va trebui precizat în program exact toate
variabilele de care este nevoie. De exemplu, în cazul vectorilor, nu se ştie întotdeauna
de la început câte componente are. Este nevoie să se declare de o dimensiune
acoperitoare (de ex, 50, dacă în mod curent este nevoie doar de 10-20 componente).
Se observă că restul de variabile, deşi nu sunt folosite niciodată, pentru ele se
păstrează spaţiu în memoria calculatorului. Dar dacă într-o anumită situaţie este
nevoie de 51 de variabile, atunci se va genera eroare deoarece se depăşeşte numărul
maxim declarat. Aceste probleme sunt rezolvate folosind alocarea dinamică.
Utilizând forma de alocare dinamică, în timpul rulării programului, în funcţie
de necesităţi, se alocă memorie suplimentară sau se renunţă la ea.
Pentru alocarea dinamică se utilizează tipul de date referinţă. Se consideră
secvenţa de program:

struct Lista_numere {
int numar; numar adr_urm
Lista_numere *adr_urm; (100) (110..10)
}
Lista_numere *list;

Variabila st este o variabilă de tip referinţă. Ea reţine adrese de înregistrări. La


rândul ei, o înregistrare are două câmpuri: număr care reţine un număr întreg
(informaţia utilă) ; şi adr_urm care reprezintă adresa unei alte înregistrări (de obicei
adresa următoarei înregistrări).
Astfel o înregistrare conţine atât informaţie utilă cât şi adrese ale altor
înregistrări.
Pentru a se rezerva spaţiu pentru o înregistrare se foloseşte funcţia
malloc(size) sau calloc(size).
list = (Lista_numere *) malloc(sizeof(Lista_numere));

1
Programming Techniques Costin Badica
Dinamic Structures Cosmin Stoica Spahiu

ATENTIE:
Va trebui inclus header-ul malloc.h

Practic atunci când avem nevoie de o nouă variabilă, vom aloca spaţiu pentru
ea. Nu este necesar să avem declarate *list1, *list2… *listn pentru fiecare variabilă de
care avem nevoie, deoarece avem posibilitatea ca fiecare înregistrare să reţină adresa
unei alte înregistrări.
Dacă am avea o lista de 5 variabile, practic este necesar doar reţinerea primei
adrese deoarece adresa variabilei 2 o reţinem în adr_urm din prima variabilă, adresa
variabilei 3 o reţinem în adr_urm din cea de-a doua variabilă… etc.
Atunci când nu ne mai este necesară o variabilă, ea se şterge (se eliberează
memoria rezervată pentru ea) folosind funcţia free ( ).
free(list);

Observaţii:
list → se referă la adresa la care se găseşte variabila list
list–>numar → se referă la câmpul numeric (informaţia utilă) care are informaţia
memorată în variabila list
list–>adr_urm → se referă la adresa unei alte înregistrări
list–>adr_urm–>numar→ semnifică variabila numar care se găseşte în înregistrarea care are
adresa adr_urm al înregistrării cu adresa list.

Lista liniară simplu înlănţuită


O listă liniară simplu înlănţuită este o structură de forma:

nr 1 adr_urm2 nr 2 adr_urm3 ……. nrn NULL


adr1 adr2 adr n
Semnificaţia notaţiilor folosite este următoarea:
• adr1, adr2, …. adrn reprezintă adresele de memorie ale celor n înregistrări
• adr_urm2, adr_urm3, … adr_urmn reprezintă câmpurile din înregistrările
st, în care se reţine adresa următoarei înregistrări. Practic adr_urm2 =
adr2 ; adr_urm3 = adr3 ; …. ; adr_urmn = adrn
• NULL semnifică faptul că în câmpul respectiv nu există nici o adresă,
deci după această înregistrare nu mai există nici o alta

Exemplu de aşezare în memoria calculatorului a unei liste:


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12.
13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. Pentru cazul nostru avem:
25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. adr1 = 15 (poziţia la care se găseşte primul
37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48.
49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. octet din înregistrare: câmpul numar. De
61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. exemplu 100)
………….. adr_urm2 = adr2 = 43 (poziţia în memorie
a următoarei variabile)
335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346.
347. 348. 349. 350. 351. 352. 353. 354. 355. 356. 357. 358.
adrn = 362
359. 360. 361. 362. 363. 364. 365. 366. 367. 368. 369. 370. La poziţia 363 se găseşte valoarea NULL
371. 372. 373. 374. 375. 376. 377. 378. 379. 380. 381. 382.
383. 384. 385. 386. 387. 388. 389. 390. 391. 392. 393. 394.

2
Programming Techniques Costin Badica
Dinamic Structures Cosmin Stoica Spahiu

Observăm că spaţiul alocat pentru aceste înregistrări nu este consecutiv. El este


alocat acolo unde se găseşte memorie liberă, neocupată cu alte date.
Denumirea de „simplu înlănţuită” provine de la faptul că fiecare element al listei
conţine o singură adresă, şi anume adresa următorului element din listă.
Operaţiile pe care le putem face cu această structură de date sunt următoarele:
creare, listare, adăugare, ştergere.

a) Crearea listei
Se va cere numărul n de înregistrări. Se creează o primă înregistrare având ca
informaţie utilă nr.1 Variabila prim, va reţine adresa primei înregistrări din listă.
Pentru fiecare i cuprins între 2 şi n se adaugă câte o nouă înregistrare listei. Variabila
ultim reţine adresa ultimei înregistrări din listă, pentru a-i completa câmpul adresă. Se
procedează astfel deoarece atunci când am creat o înregistrare, nu se cunoaşte adresa
înregistrării care urmează.
void function creare()
{ int i;
Lista_numere *temp;

prim = (Lista_numere *)malloc (sizeof(Lista_numere));


//se alocă spaţiu pentru prima înregistrare
prim->numar = 1;
prim->adr_urm = NULL;
//iniţial după prima înregistrare nu mai urmează nici o altă
înregistrare. Ea este şi prima şi ultima
ultim = prim;
//se păstrează în ultim ultima înregistrare din listă
for (i=2; i<=n; i++) {
temp = (Lista_numere *)malloc (sizeof(Lista_numere));
//alocarea de spaţiu pentru înregistrarea nr. i
temp->numar = i;
//reţinerea informaţiei utile (numărul propriuzis)
temp->adr_urm = NULL;
//aceasta înregistrare se adaugă ultima, la sfârşitul listei
ultim->adr_urm = temp;
ultim = temp;
//Acum s-a refăcut legătura dintre vechea ultimă înregistrare şi
înregistrarea nou creată. Variabila temp devine ultima
înregistrare.
iniţial:
nr 1 adr_urm2 …. nri-1 NULL nri NULL
prim ultim temp

după refacerea legăturii:


nr 1 adr_urm2 …. nri-1 adr_urmi-1 nri NULL
prim ultim

}//end for
}

b) Listarea
Am precizat faptul că prim reţine adresa primei înregistrări. Pentru a nu
deteriora această valoare, o vom memora în variabila temp. Cât timp nu am ajuns la

3
Programming Techniques Costin Badica
Dinamic Structures Cosmin Stoica Spahiu

sfârşitul listei, vom tipări informaţia utilă şi încărcăm în temp adresa înregistrării
următoare
void function Listare()
{
Lista_numere *temp;
temp = prim;
while (temp != NULL)
//cat timp nu s-a ajuns la sfârşit
{
printf(”numarul este: %d ”,temp->numar;
temp = temp->adr_urm;
//temp devine înregistrarea următoare
}//end while
}

c) Adăugarea în listă
Se consideră că dorim să adăugăm o nouă înregistrare după înregistrarea nr 2.
Pentru aceasta va trebui să ne poziţionăm pe înregistrarea după care dorim să
facem adăugarea, alocarea spaţiului pentru noua înregistrare, completarea informaţiei
utile pentru ea, completarea adresei următoarei înregistrări după cea nou creată care
va fi adresa următoarei înregistrări după înregistrarea pe care suntem poziţionaţi,
câmpul adresă al înregistrării pe care suntem poziţionaţi va lua valoarea noii
înregistrări.
nr 1 adr_urm2 nr2 adr_urm3 nr3 adr_urm4 ….
prim adr2 adr3
nrx adr_urm3
înreg. nouă
adr_x

nr 1 adr_urm2 nr2 adr_urmx nr3 adr_urm4 ….


prim adr2 adr3

înreg. nouă nrx adr_urm3


adr_x

void function Adaugare( )


{
int nr;
Lista_numere *temp, *nou;
scanf(”%d”, &nr);
temp = prim;
//ne vom poziţiona pe cea de-a doua înregistrare (temp devin
înregistrarea 2)
temp = temp->adr_urm;
//se aloca spaţiu pentru noua înregistrare
nou = (Lista_numere *)malloc (sizeof(Lista_numere));
nou->numar = nr;
//se creează legătura verde (din desenul 2) care este de fapt fosta
legătură către elementul 3 (legătura verde din desenul 1)
nou->adr_urm = temp->adr_urm;
//se creează şi legătura roşie
temp->adr_urm = nou;

4
Programming Techniques Costin Badica
Dinamic Structures Cosmin Stoica Spahiu

d) Ştergerea din listă


Pentru ştergere se procedează în mod diferit dacă se şterge prima înregistrare
sau una diferită de aceasta.
Dacă dorim să ştergem prima înregistrare, se salvează conţinutul acesteia într-o
variabilă temp, variabila prim va deveni cea de-a doua înregistrare, se şterge variabila
temp.
Dacă dorim să ştergem o altă înregistrare decât prima, se face poziţionarea pe
înregistrarea care urmează a se şterge, câmpul de adresă al înregistrării precedente
capătă valoarea câmpului de adresă al înregistrării curente, eliberăm spaţiul rezervat
înregistrării curente.
nr 1 adr_urm2 nr2 adr_urm3 nr3 adr_urm4 nr3 adr_urm5 ….
prim adr2 adr3 adr4

nr 1 adr_urm2 nr2 adr_urm4 nr3 adr_urm4 nr3 adr_urm5 ….


prim adr2 adr3 adr4

Observăm că este necesar să reţinem şi înregistrarea dinaintea înregistrării de


şters pentru a-i putea modifica câmpul adresă
void function Stergere(int nr)
//primeşte ca parametru înregistrarea de şters
Lista_numere *temp, *parinte;
int i;
//cazul când se şterge prima înregistrare
if (i == 1) {
temp = prim;
prim = prim->adr_urm;
free(temp);
} //end if
else {
temp = prim;
//ne poziţionăm pe înregistrarea numărul nr (cea care se şterge). În
variabila părinte se retine înregistrarea anterioară celei care se
şterge.
for (i=0; i<nr; i++) {
parinte = temp;
temp = temp->adr_urm;
} //end for
//creăm legătura verde(din desenul 2)
parinte->adr_urm = temp->adr_urm;
free(temp);
} //end else
}

5
Programming Techniques Costin Badica
Dinamic Structures Cosmin Stoica Spahiu

Lista liniară dublu înlănţuită


O listă dublu înlănţuită este o structură de forma:
NULL nr 1 adr_urm2 adr_spate1 nr 2 adr_urm3 ……. adr_spaten-1 nrn NULL
adr1 adr2 adr n
Fiecare înregistrare va reţine atât adresa următoarei înregistrări, cât şi adresa
înregistrării de dinaintea ei.
Operaţiile uzuale care se pot face cu o listă dublu înlănţuită sunt: adăugare la dreapta,
adăugare la stânga, ştergere la stânga, ştergere la dreapta, listare de la dreapta la
stânga, listare de la stânga la dreapta.
Structura unei înregistrări are următoarea formă:
struct Lista_numere {
int numar;
Lista_numere *next_adr;
Lista_numere *last_adr;
}
Lista_numere *list;

a) Crearea listei
Se va cere numărul n de înregistrări. Se creează o primă înregistrare având ca
informaţie utilă nr.1 Variabila prim, va reţine adresa primei înregistrări din listă.
Pentru fiecare i cuprins între 2 şi n se adaugă câte o nouă înregistrare listei. Variabila
ultim reţine adresa ultimei înregistrări din listă, pentru a-i completa câmpul adresă.
void function creare()
{
int i;
Lista_numere *temp;

prim = (Lista_numere *)malloc (sizeof(Lista_numere));


//se alocă spaţiu pentru prima înregistrare
prim->numar = 1;
prim->next_adr = NULL;
prim->last_adr = NULL;
//iniţial după prima înregistrare nu mai urmează nici o altă
înregistrare. Ea este şi prima şi ultima
ultim = prim;
//se păstrează în ultim ultima înregistrare din listă
for (i=2; i<=n; i++) {
temp = (Lista_numere *)malloc (sizeof(Lista_numere));
//alocarea de spaţiu pentru înregistrarea nr. i
temp->numar = i;
//reţinerea informaţiei utile (numărul propriuzis)
temp->next_adr = NULL;
//se crează legătura dintre noua înregistrare si ultima (săgeata
verde din desen)
temp->last_adr = ultim;
//aceasta înregistrare se adaugă ultima, la sfârşitul listei,
deci se face şi legătura roşie
ultim->next_adr = temp;
ultim = temp;
//Acum s-a refăcut legătura dintre vechea ultimă înregistrare şi
înregistrarea nou creată. Variabila temp devine ultima
înregistrare.
iniţial:

6
Programming Techniques Costin Badica
Dinamic Structures Cosmin Stoica Spahiu

NULL nr 1 adr_urm2 …. adr_spaten nr 2 NULL nrn NULL


prim ultim temp
după refacerea legăturii:

NULL nr 1 adr_urm2 …. adr_spaten-1 nr 2 adr_urmn adr_spaten-1 nrn NULL


prim ultim

}//end for
}

In funcţia de creare s-a implementat adăugarea la stânga.


Funcţiile de ştergere şi listare se fac analog ca la listarea simplu înlănţuită,
ţinându-se cont de faptul că în cazul de faţă trebuie să se facă actualizarea a două
legături în loc de una singură.
Existenţa a două legături are avantajul ca permite deplasarea în listă atât spre
dreapta (temp = temp->next_adr), cât şi spre stânga (temp = temp->last_adr). În felul
acesta la ştergere nu mai este nevoie să se reţină părintele, ci pur şi simplu se scrie
temp->last_adr.

Probleme propuse
1. Să se realizeze o listă simplu înlănţuită, unde funcţiile de creare, adăugare,
listare şi ştergere să fie implementate recursiv.
2. Să se implementeze pentru lista dublu înlănţuite operaţiile de adăugare(stânga,
dreapta), ştergere şi listare
3. Să se implementeze o listă circulară, dublu înlănţuită.
4. O listă se numeşte circulară, dacă ultima înregistrare va avea o legătură spre
prima, iar prima spre ultima, ca în figura:

adr_spat nr1 next_ad


next_adr1

adr_spate

adr1 adr2
nr

nr
adr_spate

next_adr3

adr5

adr3
next_ad adr_spat
nr adr4 nr
adr_spat next_ad

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