Sunteți pe pagina 1din 34

Ll4 la SDA pentru gr. Ti 171-173, FCIM.

Tema “Analiza prelucrării structurilor de date cu liste ”


Sarcina şi obiectivele:
1. de studiat şi însuşit materialul teoretic pentru evidenţierea esenţialului prelucrării structurilor
de date cu liste în elaborarea modelelor soluţiei, analizând exemplele din text;
2. să se selecteze problemele din compartimentul “3 şi Anexe” şi să se elaboreze organigramele
(pentru funcţiile principale) şi programele cu liste (declarări, parcurgeri, etc.), pentru aprofundare şi
rularea programelor în limbajul C să se elaboreze scenariile succinte de soluţionare prin respectiva tehnică
de prelucrare cu calculele de verificare şi explicaţii.
3. să se analizeze tehnica modelării şi programării eficiente pentru diverse compartimente ale
diferitor situaţii cu diverse argumentări şi modele de structuri abstracte, incluzând fişiere cu teste de
verificare şi vizualizări.
Consideraţii teoretice:
1. Consideraţiile teoretice generale. Tipuri de date abstracte Tipul de date abstract este o entitate manipulata doar
prin operatiile ce definesc acel tip. Avantajele utilizarii tipurilor de date abstracte sunt:
- Programele devin independente de modul de reprezentare a datelor. Modul de reprezentare poate fi modificat, fara însa a
afecta restul programului (de exemplu, o multime poate fi implementata printr-un tablou sau printr-o lista ordonata, dar
partea de program ce foloseste operatorii tipului abstract rămâne neschimbata).
- Se previne violarea accidentala a datelor. Utilizatorul tipului abstract este forţat sa manipuleze datele doar prin
intermediul operatorilor ce compun tipul abstract, astfel reducându-se riscul unei distrugeri a datelor.
Dupa cum este exemplificat în continuare, în C tipurile abstracte sunt realizate folosind fişiere. Acest mod are desigur
propriile sale limitari. Mentionam doar doua din ele: nu se pot defini tablouri de tipuri abstracte si nu se pot transmite
parametri având ca si tip un tip abstract.
Pentru utilizarea eficientă a unui calculator trebuie ca relaţiile structurale existente in mulţimea datelor şi modul de acces
la aceste date să se reflecte în metodele de reprezentare şi manipulare a structurilor de date în cadrul sistemelor de calcul.
Vom face o introducere a celor mai importante noţiuni despre structurile informaţiei: proprietăţi statice şi dinamice ale
diferitelor tipuri de structuri liniare, precum şi algoritmi eficienţi pentru crearea, modificarea, regăsirea şi ştergerea
informaţiilor cu o anumită structură în interiorul sistemelor de calcul. Structurile de date arborescente vor fi introduse in
materialul care trateză despre aplicaţiile arborilor binari (Ll5). În continuare vom denumi anumiţi termeni care vor fi utilizaţi
în acest material. Astfel informaţiile conţinute într-un tabel constant dintr-o mulţime de noduri (numite şi înregistrări, entităţi
sau articole). Fiecare nod se compune din unul sau mai multe cuvinte consecutive din memoria calculatorului, fiind împărţite
în părţi componente numite cîmpuri. De exemplu, un nod poate ocupa un singur cuvînt din memorie avînd un singur cîmp de
lungime egală cu lungimea întregului cuvînt din memorie. Să presupunem acum că elementele tabelului reprezintă nişte cărţi
de joc: nodurile sunt formate de cîte un cuvînt, divizat în patru cîmpuri: MARCA, CULOARE, RANG şi URMĂTOR:
MARCA CULOARE RANG URMĂTOR
Am presupus că un cuvînt al calculatorului nostru se compune din cinci octeţi, cîmpul URMĂTOR ocupînd ultimii doi
octeţi. Adresa unui nod, numită şi pointerul sau referinţa acelui nod reprezintă locaţia din memorie a primului cuvînt
aparţinînd nodului (în caz cînd acesta se compune din mai multe noduri consecutive) sau a unicului cuvînt aparţinînd
nodului. Adresa se consideră ca adresă relativă faţă de o locaţie de bază, dar pentru simplitate vom considera adresa ca adresă
absolută a locaţiei din memorie. Conţinutul oricărui cîmp al unui nod poate reprezenta numere, caractere alfabetice, referinţe
la alte noduri sau orice altă informaţie dorită de programator. În exemplul dat, să presupunem că dorim să reprezentăm un
pachet de cărţi de joc, adoptînd următoarele convenţii: MARCA=1 pentru cartea respectivă cu faţa în jos, MARCA=0 pentru
cartea cu faţa în sus; CULOARE=1,2,3 SAU 4 pentru treflă, caro, cupă şi respectiv pică; RANG=1,2, ... 13, pentru as, doi, ...
rege; URMĂTOR este o referinţă la cartea care urmează în pachet. Un pachet de cărţi constînd din cinci cărţi: rege de treflă,
patru de treflă, as de cupă (cu faţa în jos), şase de pică şi valet de caro în această ordine se pot reprezenta în calculator în
modul următor:
Locaţiile din memorie utilizate sunt în acest caz 102, 366, 242, 244 şi
102 0 1 13 366
164. Aceste adrese pot fi schimbate în funcţie de memoria
disponibilă, cu condiţia de a cunoaşte prima adresă ( în cazul nostru
366 102 ) 0şi respectînd
1 4
regula 242
ca fiecare carte să conţină o referinţă la
cartea următoare ,corespun- zător ordinii din pachet. Referinţa
242 1 „Λ ”3 din nodul
1 164 244semnifică referinţa nulă, care nu
specială
corespunde nici unui alt nod şi apare în nodul 164 deoarece cartea
244 valet 0de caro4este ultima
6 164 din pachet.
carte
164 0 2 12 Λ

fig 1.1
În acest material vom folosi litera lambda din alfabetul grecesc pentru reprezentarea referinţei nule. În memoria
calculatorului, Λ va fi reprezentată printr-o valoare uşor de recunoscut care nu poate reprezenta adresa unui nod, de exemplu
un număr negativ sau numărul zero.
Introducerea de referinţe sau pointeri către alte noduri reprezintă o idee foarte utilă pentru reprezentarea structurilor
complexe.
Astfel toate referinţele la noduri într-un program se realizează direct cu ajutorul variabile- lor referinţe sau indirect
prin cîmpurile referinţe ale altor noduri.
Referirea la cîmpurile din cadrul nodurilor se realizează simplu indicînd numele cîmpu- lui urmat de o referinţă la
nodul respectiv, între paranteze. Pentru exemplul dat avem RANG (ÎNCEPUT) =13; URMĂTOR (244)=164;
CULOARE(URMĂTOR(ÎNCEPUT))=1.
Următorul algoritm numără cîte cărţi de joc conţine pachetul:
1.N ← 0, X← ÎNCEPUT ( N este o variabilă întreagă şi X este o variabilă de tip referinţă ).
2.Dacă X=Λ, stop ( N reprezintă numărul de cărţi din pachet ).
3.N← N +1, X← URMĂTOR(X) şi mergi la pasul 2.
Să notăm utilizarea de nume simbolice atît pentru variabile ( ÎNCEPUT, N, X ) cît şi pentru cîmpuri ( URMĂTOR ).
Dacă P este numele unui cîmp iar L ≠ Λ este o referinţă, atunci P(L) este o variabilă fără ca P să fie la rîndul lui variabilă.
Structura de date aferentă unui anumit algoritm poate avea consecinţe serioase atît asupra memoriei ocupate
(complexitate spaţiu) cît şi asupra timpului de calcul (complexitate timp).

1.1 LISTE LINIARE


Lista liniara este o structura de date in care fiecare element are un succesor unic, (exceptie face ultimul element al listei).

Ex: Intr-o lista de articole carre cuprind date despre angajatii unei firme, cheia o poate reprezenta numele
angajatului sau marca, etc...
listele generale au asociate patru operatii elementare :
• Inserarea
• Stergerea
• Cautarea
• Traversarea
Inserarea reprezinta o oparatie prin care un nou element este introdus in lista.
Intr-o lista generala, inserarea unui element se face la inceputul, in interiorul sau la sfarsitul ei. Deoarece, intr-o lista
neordonata, nu conteaza pozitia unde se efectueaza inserarea, se obisnuieste ca operatia sa se efectueze la sfarsitul ei. In acest
fel elementele pot fi gasite in ordinea in care au fost ele introduse (inserate). Acesta este si motivul pentru care Inserarea
reprezinta o oparatie prin care un nou element este introdus in lista.
Intr-o lista generala, inserarea unui element se face la inceputul, in interiorul sau la sfarsitul ei. Deoarece, intr-o lista
neordonata, nu conteaza pozitia unde se efectueaza inserarea, se obisnuieste ca operatia sa se efectueze la sfarsitul ei. In acest
fel elementele pot fi gasite in ordinea in care au fost ele introduse (inserate). Acesta este si motivul pentru care listele
neordonate se mai numesc si liste cronologice.
Stergerea reprezinta o operatie care permite eliminarea unui element al listei. Aceasta operatie cuprinde:
• Cautarea elementului care trebuie eliminat
• Stergerea efectiva a elementului respectiv
Daca lista este reprezentata cu ajutorul unui vector, atunci elementele ce succed elementul care trebuie sters vor fi
deplasate spre stanga cu o pozitie. Dupa aceasta operatie elementul fiind inlocuit cu succesorul sau.
Cautarea reprezinta operatia prin care se urmareste gasirea unui element intr-o structura.
In general algoritmul de cautare secventiala (element cu element) poate fi aplicat indiferent de tipul listei sau de modul ei
de reprezentare. Daca avem de-a face cu o lista ordonata, reprezentata ca vector, se poate folosi algoritmul de cautare binara.
Traversarea accesarea secventiala a tuturor elementelor din structura. Ea poate fi privita ca un caz special de cautare,
deoarece consta in regasirea tuturor elementelor structurii. Traversarea consta in exiistenta in algoritm a unei bucle. La
fiecare iteratie este accesat cate un element. Bucla se incheie cand aceasta operatie a fost efectuata asupra ultimului element.
Obs. Este necesar sa mentionam ca reprezentarea listelor liniare cu ajutorul vectorilor are unele dezavantaje majore.
Astfel, operatia de inserare sau stergere a unui singur element implica operatii de deplasare ce se efectueaza asupra unui
ansamblude elemente. Inserarea pe pozitia a doua a unei liste ce contine 1000 de elemente conduce la efectuarea a 999 de
operatii de deplasare.
Stiva reprezinta o lista liniara in care operatiile de introducere si extragere se efectueaza la unul din capetele structurii.
Primul element intalnit la acest capat poarta numele de varf. Orice ansamblu liniar asupra caruia putem actiona prin
introducerea sau extragerea unui element, doar la unul din capete, reprezinta o stiva.
La nivelul memoriei interne stivei i se aloca o zona fixa, care "exista" pe tot parcursul executiei programului. In cadrul
acestui spatiu de memorie locatiile sunt "ocupate" sau "eliberate", conform operatiilor de introducere extragere descrise prin
program. Datorita restrictiei asupra operatiilor de introducere si extragere, stiva se mai numeste si structura LIFO.
Coada - lista liniara in care operatiile de introducere se efectueaza printr-un capat al structurii, iar operatiile de extragere
se efectueaza la celalalt capat. Elementul care poate fi extras dintr-o coada se numeste varf, iar ultimul se numeste baza.
Aceasta restrictionare a operatiilor asigura procesarea elementelor in ordinea in care au fost "primite". Cu alte cuvinte o
coada este o structura FIFO.
Ca si stiva, coada reprezinta o structura semistatica. Ea are alocata, la nivelul memoriei interne, o zona fixa care exista pe
tot parcursul executiei programului.
Dezvoltarea structurii se efectueaza in interiorul acestui spatiu de memorie, in urma operatiilor de adaugare si eliminare.
Acestea conduc la ocuparea si eliberarea locatiilor de memorie existente in interiorul zonei rezervate. Ca deosebire intre
manipularea unei stive si manipularea unei cozi, lucrul cu o coada implica "monitorizarea" atat a varfului cat si al Asupra
cozii se pot efectua patru operatii elementare :
• Adaugarea - aceasta operatie permite adaugarea unui element in coada, ca succesor al bazei curente. Dupa introducere
baza va deveni acest nou element. Ca si in cazul stivei, trebuie sa avem in vedere ca operatia nu se poate efectua cand coada
este plina, deci cand spatiul rezervat acesteia sete in totalitate folosit.
• Eliminarea - aceasta operatie permite extragerea varfului cozii. Dupa aceasta operatie, varful va deveni elementul
succesor celui eleiminat. Operatia nu se poate efectua cand coada este vida.
• Accesarea varfului cozii - aceasta operatie permite accesarea varfului cozii, fara ca acesta sa fie eliminat. Trebuie
mentionat ca, in cazul in care coada este vida, operatia nu poate fi efectuata.
• Accesarea bazei cozii - aceasta operatie permite accesarea (citirea) bazei cozii, fara ca elementul sa fie eliminat.
Operatia nu se poate efectua asupra unei cozi vide.
Obs. Coada accepta mai multe forme de reprezentare. De exemplu poate fi implementata cu ajutorul unui tablou
unidimensional sau, daca are un numar extrem de mare de elemente, se poate folosi un fisier.
Daca luand in discutie modul de reprezentare al unei cozi cu ajutorul unui vector, operatia de adaugare a unui element
intr-o coada vida conduce la plasarea acestuia pe prima componenta a tabloului. Elementul astfel introdus devine atat baza,
cat si varf. Fiecare operatie de adaugare conduce la plasarea elementului pe pozitia urmatoare bazei curente. In operatia de
eliminare (stergere), varful inainteaza cu o pozitie. Observam ca in urma operatiilor succesive de adaugare si eliminare,
elementele cozii migreaza de la prima componenta a vectorului spre ultima. O coada este plina cand varful se afla pe prima
pozitie in vector, iar baza pe ultima pozitie. Cand datele sunt stocate cu rapiditate in coada, exista tendinta de avansare catre
finalul vectorului, ajungadu-se la situatia in care ultimul element din vector este ocupat, dar coada nu este plina, deoarece
exista componente libere la inceputul acestuia. Cand datele sunt grupate la finalul vectorului si avem nevoie sa gasim un loc
pentru adaugarea unui nou element, o solutie ar fi deplasarea in bloc a tuturor elementelor la inceputul vectorului.
O solutie mult mai eficienta este folosirea unui vector circular. El reprezinta o structura de date conceptuala in cadrul
careia ultimul element are ca succesor logic primul element. In aceasta situatie, adaugarea unui element intr-o coada a carei
baa se afla pe ultima pozitie a vectorului se va efectua pe prima pozitie.
Deci o listă liniară este o mulţime ordonată constînd din n ≥ 0 noduri (pentru n=0 lista este vidă ). Aceste noduri se noteză
da exemplu X[1], X[2], ..., X[n], unde indicele care apare în paranteze ne arată poziţia nodului în cadrul listei; astfel dacă
n>0 , X[1] este primul nod; pentru 1< k < n , nodul X[k] este precedat de nodul X[k-1] şi este urmat de nodul X[k+1]; X[n]
este ultimul nod. Desigur pentru n=1 nodul X[1] este atît primul cît şi ultimul nod, iar pentru n=2 X[1] este primul nod, X[2]
este ultimul nod şi nu există alte noduri intermediare.
Operaţiile pe care trebuie să le efectuăm asupra listelor liniare pot include următoarele:
- accesul la nodul X[k] din listă pentru a examina sau modifica conţinutul cîmpurilor sale;
- ştergerea nodului X[k] din listă;
- înserarea unui nou nod înainte (după) nodul X[k];
- determinarea numărului de noduri dintr-o listă;
- sortarea nodurilor unei liste în ordine crescătoare (descrescătoare) a conţinuturilor numitor cîmpuri ale
nodurilor;
- căutarea în listă a nodului (nodurilor) cu o valoare particulară a unui anumit cîmp;
- copierea unei liste liniare;
- combinarea a două sau mai multor liste într-una singură;
O anumită aplicaţie pe calculator rareori apelează la toate aceste operaţii în întreaga lor generalitate şi există mai multe
căi de a reprezenta liste liniare, în funcţie de clasele de operaţii care se vor efectua cel mai frecvent.
Nu există o metodă unică de reprezentare pentru listele liniare pentru care toate aceste operaţii să fie eficiente, fiecare
metodă avînd anumite avantaje, dar şi dezavantaje, care o deosebesc de celelalte.
De aceea vom face distincţiile intre tipurile de liste liniare în funcţie de principalele operaţii de efectuat.
1.2 ALOCAREA SECVENŢIALĂ
Calea cea mai simplă de a păstra o listă liniară în memoria calculatorului este de a pune elementele listei în locaţii
succesive, nod după nod. În acest fel avem LOC(X[j+1])=LOC(X[j])+c, unde LOC(V) reprezintă locaţia din memorie a
variabilei-pointer V şi c reprezintă numărul de cuvinte alocate unui nod, presupunînd că toate nodurile listei ocupă acelaşi
spaţiu de memorie. Rezultă că LOC(X[j])=L+cj, unde L este o constantă numită adresa de bază.
1.3 STIVE ŞI COZI
Listele liniare pentru care accesul la nodul precum şi înserările sau ştergerile de noduri au loc numai la capete, deci la
primul sau la ultimul nod sunt frecvent întîlnite şi ele poartă nume speciale:
- o stivă este o listă liniară pentru care toate înserările şi ştergerile (şi în general orice acces) sunt făcute la un singur
capăt (fixat dinainte) al listei.
- o coadă este o listă liniară pentru care toate înserările sunt făcute la unul din capetele listei, iar toate ştergerile sunt
făcute la celălalt capăt al listei (aceste două capete sunt fixate dinainte, astfel încît toate înserările se fac la un acelaşi
capăt, iar ştergerile la celălalt capăt). În cazul unei stive manipulăm întotdeauna informaţia înserată cel mai recent. În
cazul unei cozi procedăm invers: putem avea acces la informaţia cea mai veche, deci nodurile părăsesc lista în
aceeaşi ordine în care au intrat, existînd o analogie cu o coadă de aşteptare a unor subiecţi care trebuie serviţi.
Stivele se mai numesc şi liste cu deplasare în jos (push-down) sau liste de tip ultimul sosit-primul ieşit, iar cozile se
mai numesc memorii circulare sau liste primul sosit-primul ieşit.
Stivele se folosesc frecvent în organizarea datelor prelucrate de algoritmii recursivi.
Pentru aceste structuri de date vom folosi următoarea terminologie (vezi figura )
Un nod se înserează sau se extrage din vîrful unei stive; baza unei stive este ultimul nod accesibil şi nu va putea fi extras
decît după ce toate celelalte elemente au fost extrase.
În cazul cozilor vom utiliza denumirile de faţa şi spatele cozii: nodurile se adaugă la spate şi părăsesc coada prin faţă.
Vom scrie A←x cînd A este o stivă pentru a reprezenta inserarea valorii variabilei x în vîrful stivei A sau cînd A este o
coadă pentru înserarea lui x la spatele cozii.
În mod analogic notaţia x←A este folosită pentru a reprezenta atribuirea valorii din vîrful stivei A, respectiv din faţa
cozii A variabilei x şi apoi ştergerea acestei valori din A.
Notaţia x←A nu are nici o semnificaţie atunci cînd stiva (coada) A este vidă, adică nu conţine nici o valoare.
Dacă utilizăm o stivă în alocarea secvenţială vom folosi o variabilă T numită pointerul stivei, care ia ca valori numere
naturale. Cînd stiva este vidă, se face T=0. Dacă presupunem că X[1],... ,X[M] este spaţiul total care poate fi utilizat de stivă,
operaţiile de înserare şi respectiv de ştergere din stivă se scriu astfel:
● X←Y (inserţia lui Y în stiva X).
● Y←X (extragerea vîrfului stivei X după ce valoarea existentă acolo a fost atribuită variabilei Y).
Dacă vrem să lucrăm cu ocoadă în alocare secvenţială trebuie să utilizăm doi pointeri F şi S (pentru faţa şi respectiv
spatele cozii). Dacă F=S atunci prin convenţie coada este vidă. Să presupunem că spaţiul disponibil pentru a forma coada
constă din nodurile X[1],... ,X[M]. Deoarece atît la inserţii cît şi la extrageri din coadă indicii F şi S ambii cresc (sau ambii
descresc pentru o altă notaţie posibilă a feţii şi spatelui cozii) rezultă că ei pot lua valorile 0 sau M+1, deci lucrul cu lista se
întrerupe, deşi coada poate să nu fie în situaţiile de depăşire sau subdepăşire. Pentru a rezolva această situaţie vom presupune
că nodurile X[1],... ,X[M] sunt aranjate pe un cerc astfel încît după X[M] urmează X[1]. Deci vom folosi coada ca o coadă
circulară.
Dacă avem două stive de dimensiuni variabile care împart un acelaşi spaţiu de memorie, putem lucra eficient făcînd
listele să crească una către cealaltă: Stiva 1 creşte către dreapta şi Stiva 2 către stînga (vezi figura 1.2). În acest caz situaţia de
depăşire apare numai atunci cînd listele folosesc împreună tot spaţiul de memorie disponibil.

Stiva 1 Spaţiu disponibil Stiva 2


↑ ↑ ↑ ↑
baza 1 vîrf 1 vîrf 2 baza 2
fig.1.2
Însă nu există nici o cale de a memora trei sau mai multe liste secvenţiale de dimensiuni variabile într-un calculator
astfel încît situaţia de depăşire să apară numai atunci cînd dimensiunea totală a tuturor listelor depăşeşte spaţiul total
disponibil şi fiecare listă are o locaţie fixă pentrul nodul său de la bază.
În acest caz se poate face loc pentru un nou nod pentru înserare într-o listă L1 deja plină luînd acest spaţiu de la o listă
L2 care nu este plină prin deplasarea la dreapta sau la stînga (după cum L2 se găseşte la dreapta sau la stînga lui L1) a
conţinutului cuvintelor din nodurile situate în şi între cele două liste. Ordinea în care se face această deplasare trebuie să evite
pierderea de informaţii.
1.4 ALOCAREA ÎNLĂNŢUITĂ
Ideea alocării înlănţuite a fost deja expusă cînd am prezentat un mod de reprezentare în calculator a cărţilor de joc:
fiecare nod are un cîmp suplimentar în care se păstrează adresa nodului următor sau Λ pentru ultimul nod al listei.
Cîteva comparaţii între alocarea secvenţială şi alocarea înlănţuită se impun de la sine:
●Alocarea înlănţuită necesită spaţiu suplimentar de memorie pentru pointeri (referinţe).
●Este uşor de extras un nod din cadrul unei liste înlănţuite. De exemplu, pentru a şterge al II-lea nod din lista
referită de primul este suficient să copiem referinţa din al II-lea nod peste referinţa din primul nod. Pentru alocarea
secvenţială o asemenea ştetrgere implică în general deplasarea unei părţi din listă în locaţii diferite. O concluzie similară se
obţine pentru înserarea unui nuo nod în interiorul unei liste liniare.
●Accesul la diferite părţi din listă este mult mai rapid în cazul alocării secvenţiale. Aşa cum am văzut, locaţia
nodului k din listă în cazul alocării secvenţiale este o funcţie liniară de k, deci pentru a obţine accesul la acest nod se
consumă un timp constant. În cazul alocării înlănţuite acest acces necesită k iteraţii urmărind referinţele a k-1 noduri. Astfel
utilizarea memoriei înlănţuite este mai eficientă cînd parcurgerea listei se face secvenţial şi nu aleatoriu.
●Alocarea înlănţuită permite o mai mare flexibilitate în ceea ce priveşte reunirea a două sau mai multor liste într-o
singură listă sau desfacerea unei liste în mai multe părţi.
În cele ce urmează vom presupune că un nod ocupă un singur cuvînt care este divizat în două cîmpuri INFO şi
LEG:
INFO LEG

Folosirea alocării înlănţuite implică existenţa unei liste a spaţiului disponibil numită în continuare lista DISP (sau stiva
DISP, deoarece ea funcţionează ca o stivă).
Mulţimea tuturor nodurilor care sunt utilizate la un moment dat se înlănţuie într-o listă liniară iar variabila-referinţă
DISP va păstra adresa nodului din vîrful stivei DISP sau va fi egală cu Λ dacă stiva este vidă.
Operaţia notată X←DISP extrage nodul din vîrful stivei DISP şi stabileşte ca variabila-referinţă X să se refere la acest
nod extras, care este astfel rezervat pentru utilizarea ulterioară. Ea constă din următoarele operaţii elementare: dacă DISP=Λ,
atunci depăşire; astfel X←DISP, DISP←LEG(DISP).
Operaţia inversă DISP←X transferă nodul adresat de X înapoi în vîrful stivei nodurilor disponibile: LEG(X)←DISP,
DISP←X.
INSERŢIE: P←DISP; INFO(P)←Y, LEG(P)←T, T←P.
EXTRAGERE: Dacă T=Λ atunci subdepăşire; altfel P←T, T←LEG(P), Y←INFO(P); DISP←P.
- 1.5. Modalităţile de prelucrare ale listelor în C.

1.5.1. Definirea sructurii în C


Sructura este un obiect compus , in care se includ elemente de orice tip , in afara de functii. In comparatie de
masiv tipul structurii se determina in mod:
Struct {lista determinarilor}
In structura este obligator sa fie indicat cel putin un element. Determinarea structurii are forma :
<tipul de date > descrierea.
Unde <tipul de date> indica tipul structurii pentru obiecte , determinate in descriere. In cel mai simplu caz
descrierile prezinta din sine identificatori sau massivului
Exemplu: struct {double x,y} s1,s2,sm[9];
struct { int year; char moth, day;} date1, date2;

Variabilele s1 ,s2 se determina ca structuri, fiecare di ele este compusa din doua componente x si y. Variabila
sm se determina ca masiv din noua structuri . Fiecare din doua variabile date1, date2 este compusa din trei
componenti year , moth, day. Mai exista si alta metoda unirea numelui si tipului, care este bazata in folosirea tega
structurii.Teg structura este analogic teg. Teg structura se determina in modul urmator :
struct <teg > { lista descrierilor }, unde teg este indentificator.
In exemplu de mai jos in dentificatorul student se descrie ca teg structura :
struct student {char name [25]; int id, age; char prp;…}
Deci Teg-ul structurii se foloseste pentru anuntul sructurilor.
Determinarea elementelor de tip structura se aseamana determinarilor acestor tipuri, însa exista o deosebire
orecare. La determinarea tipului structura si componentilor lui nu se elibereaza memorie, si nu se permite
initializare lor . Cu alte cuvinte structura tip ne este obiect.
Daca tipul structura este determinat si se stie numele, atunci formatul determinarii structurii
concrete( obiectul structura tip ) va capata aspectul :
struct nume _ tip _ structura lista _ structur ;
unde lista _ structura – lista de alegere utilizării numelor (identificatorilor).
Determinarea structurilor ( determinarea structurii tip , dupa care se foloseste numele pentru determinarea
structurii ) in afara de aceasta in limbajul C mai exista doua sheme de determinare . In primul rind structurele pot
fi determinate concomitent cu determinarea tipului structură:
struct nume _ tip _structura
{ determinarea elementelor }
lista _ structurelor;
Exemplul concomitent determina tip structura si structurele (obiectelor);
struct student
{ char name [15];
char surname [20];
int year ;
} student _1, student _2, student_3;
Aici este determinat tipul structura cu numele student si trei structuri concrete student _1 , student _2 , student _3 , care sint
obiecte depline . In fiecare din aceste trei structuri intra elemente , care permit , nume (name) , prenumele , cursul (year) la
care se afla studentul .
Exemplul pentru reprezentarea numerelor complexe
struct complex { double re, im; };
adunarea numerelor complexe va arata în felul următor:
struct complex add( c1, c2 ) struct complex c1, c2;
{ struct complex sum; sum.re = c1.re + c2.re; sum.im = c1.im + c2.im; return sum; }
struct complex a = { 12.0, 14.0 }, b = { 13.0, 2.0 };
main(){ struct complex c; c = add( a, b ); printf( "(%g,%g)\n", c.re, c.im ); }
1.5.2. Structuri recursive La capitolul privitor la recursivitate, aceasta a fost ilustrata ca o proprietate
specifica algoritmilor. Sa revedem definitia recursivitatii:
Un obiect sau un fenomen se defineste in mod recursiv daca in definitia sa exista o referire la el insusi.
Se va prezenta in continuare extinderea acestei proprietati asupra tipurilor de date, in forma structurilor de date
recursive.
Prin analogie cu algoritmii, prin structura recursiva se intelege o structura care are cel putin o componenta de acelasi
tip cu structura insasi. Si in acest caz poate exista recursivitate directa sau indirecta.
Pentru evitarea structurilor infinite, in definitia recursiva trebuie sa existe o conditie de care depinde prezenta efectiva a
componentei sau componentelor recursive.
Structurile recursive pot fi implementate in C numai in forma unor structuri dinamice, deoarece o structura nu poate
avea un camp de acelasi tip structura, ci doar pointer la structura.. Deci definitia unei structurii recursive recursiva va deveni:
o structura care are o componenta de tip pointer la structura insasi.
Limitarea structurilor recursive se realizeaza prin existenta valorii NULL pentru pointeri.
La modul general, o structura recursiva se defineste:
struct recursiva{ // reprezinta un nod al unui arbore generalizat
    tip1 camp1; // m campuri de tipuri oarecare
    tip2 camp2;
    ...
    tipm campm;
    struct recursiva *p1, *p2, ..., *pn; // L
    // n campuri de tip pointer la structura
}; Cu extensiile aduse de C++, cum numele unei structuri este nume de tip, linia L devine:
       recursiva *p1, *p2, ..., *pn;

In continuare discutia se va referi la structurile recursive care au o singura componenta pointer la structura.

Structura de date lista


Lista este o structura dinamica, situata in memoria centrala, in care toate elementele sunt de acelasi tip;
numarul de elemente este variabil, chiar nul. De remarcat diferentele fata de definitia tabloului: tabloul este o
structura statica, situata in memoria centrala, in care toate elementele sunt de acelasi tip; numarul de elemente
este constant.

O alta definire a listei este:

O lista L este o secventa de zero sau mai multe elemente, numite noduri, toate fiind de acelasi tip de baza T.

L=a1,a2,...,an (n>=0)

Daca n>=1, a1 se spune ca este primul nod al listei, iar an, ultimul nod. Daca n=0, lista este vida. Numarul de
noduri se numeste lungimea listei.
Un nod al listei liniare care apare ca o structura recursiva, avand o componenta de tip pointer la
structura, reprezentand legatura ( inlantuirea ) spre nodul urmator. Lista in care fiecare nod are o sinfura
inlantuire se numeste lista simplu inlantuita.
    struct nod{
        TipCheie cheie;
        TipInfo info;
        struct Nod* urmator;
  };
  struct nod* inceput;
  //pentru o scriere mai compacta se pot defini tipurile:
  typedef struct nod Nod, *pNod;
  pNod inceput;
Caracteristica unei astfel de structuri consta in prezenta unei singure inlantuiri. Campul cheieserveste la identificarea
nodului, campul urmatore pointer de inlantuire la nodul urmator, iar cel infocontine informatia utila.

Variabilainceputindica spre primul nod al listei. In unele situatii in locul lui inceputse utilizeaza un nod fictiv, adica
o variabila de tip struct Nod cu campurile cheie si info neprecizate, dar campul urmatorindicand spre primul nod
al listei.

De asemenea uneori este util a se pastra pointerul spre ultimul nod al listei.
O varianta este a listelor circulare la care dispare notiunea de prim, ultim nod, lista fiind un pointer ce se plimba
pe lista

 Liste ordonate si reorganizarea listelor

a)Cautarea intr-o lista neordonata; tehnica fanionului


Se considera o lista simplu inlantuita, cu nodurile de tip Nod. Daca inceput indica spre primul nod al listei, iar
ordinea cheilor in lista este aleatoare, cautarea unei chei implica traversarea listei. Functia booleana gasit
returneaza valoarea pointerului spre nodul cu cheia egala cu cea cautata, daca un astfel de nod exista si
valoarea NULL in caz contrar:  
pNod gasit(TipCheie val){
          pNod poz;
          poz=inceput;
          while (poz!=NULL)
               if (poz->cheie==val)
                   return poz;
               else 
                   poz=poz->urmator;
          return poz; // cu valoarea NULL 
Cautarea se poate perfectiona prin utilizarea metodei fanionului, lista prelungindu-se cu un nod fictiv numit fanion,
la creare lista continand acest unic nod. In functia gasit, inainte de baleierea listei, informatia cautata se
introduce in cheia nodului fanion, astfel incat va exista cel putin un nod cu cheia cautata:  
pNod fanion;
pNod gasit(TipCheie val){
          pNod poz;
          for(poz=inceput,fanion->cheie=val;poz->cheie!=val;
              poz=poz->urmator);
          if(poz==fanion)
                return NULL;
          return poz;
b)Crearea unei liste ordonate; tehnica celor doi pointeri

In continuare se prezinta o metoda foarte simpla pentru crearea unei liste ordonate, tipurile pNod si Nod fiind cele
definite anterior. Lista se initializeaza cu doua noduri fictive pointate de doua variabile pointer, inceput
sifanion:  
pNod inceput, fanion;
 
void init(void);
     inceput=(pNod)malloc(sizeof(Nod));
     fanion=(pNod)malloc(sizeof(Nod));
     inceput->urmator=fanion;
}
Pentru introducerea unei noi chei in lista, pastrand ordonarea, se va scrie o functie gasit, care daca gaseste cheia
in lista returneaza valoarea 1 si pointerii p1 spre nodul gasit si p2 spre cel anterior, respectiv in cazul negasirii
cheii, valoarea 0 si pointerii p1 si p2 spre nodurile intre care trebuie facuta insertia:  
int gasit(TipCheie val, pNod* p1, pNod* p2){
          for(*p2=inceput,*p1=(*p2)->urmator,fanion->cheie=val;

              (*p1)->cheie<=val;p2=p1,*p1=(*p1)->urmator);
          return *p1!=fanion && (*p1)->cheie==val;
Fragmentul de program care insereaza o noua cheie este:  
    pNod p1,p2,p3;
    TipCheie val;
          ...
    if (!gasit(val,&p1,&p2)){
            p3=(pNod)malloc(sizeof(Nod)); //creare nod nou
            p2->urmator=p3; //legatura de la nodul anterior la cel nou
            p3->cheie=val;
                                 //completare p3->info
            p3->urmator=p1; //legatura de la noul nod la cel urmator
    }
Pentru tiparirea cheilor dintr-o lista ordonata astfel creata, pointerul care parcurge nodurile trebuie sa fie initializat
cu valoarea pointerului spre primul nod efectiv al listei, urmator celui inceput, iar parcurgerea listei se face pana
la intilnirea nodului fanion:  
    pNod p;
    for(p=inceput->urmator;p!=fanion;p=p->urmator)
      //prelucrarea nodului indicat de p
Uniunile:
struct a{ int x, y; char *s; } A;
union b{ int i; char *s; struct a aa; } B;
union all{ char *s; int i; double f; } x;
x.i = 12 ; printf("%d\n", x.i); x.f = 3.14; printf("%f\n", x.f); x.s = "Hi, there"; printf("%s\n", x.s);
printf("int=%d double=%d (char *)=%d all=%d\n", sizeof(int), sizeof(double), sizeof(char *), sizeof x);
Deci Lista este o mulţime finită şi ordonată de elemente de acelaşi tip. Elementele listei se numesc noduri.
Listele pot fi organizate sub formă statică, de tablou, caz în care ordinea este implicit dată de tipul tablou
unidimensional, sau cel mai des, sub formă de liste dinamice, în care ordinea nodurilor este stabilită prin pointeri. Nodurile
listelor dinamice sunt alocate în memoria heap. Listele dinamice se numesc liste înlănţuite, putând fi simplu sau dublu
înlănţuite.
Prin definitie, o multime dinamica de structuri recursive de acelasi tip, pentru care sunt definite una sau mai
multe relatii de ordine cu ajutorul unor pointeri din compunerea structurilor respective, se numeste lista inlantuita.
Elementele unei liste se numesc noduri.
Daca intre nodurile unei liste exista o singura relatie de ordine, atunci lista se numeste simplu inlantuita. In mod analog,
lista este dublu inlantuita daca intre nodurile ei sunt definite doua relatii de ordine.
O lista este n-inlantuita daca intre nodurile ei sunt defi-nite n relatii de ordine.

Aspecte ale implementarii listelor liniare simplu înlantuite. O solutie de implementare a listelor liniare este sub forma
unei înlantuiri de elemente cu aceeasi structura, aflate în memorie la diverse adrese si legate între ele prin intermediul
pointerilor. Scopul utilizarii listelor este de a economisi spatiu de memorie, motiv pentru care se foloseste alocarea dinamica
în locul celei statice (utilizata în cazul tablourilor). Accesul la un element al listei se poate face doar secvential, parcurgând
elementele aflate înaintea sa în înlantuire.
Pentru a exploata avantajul listelor în ceea ce priveste economia de spatiu de memorie, trebuie acordata o atentie
deosebita operatiilor asupra listei. În general, asupra unei liste se pot face operatii de insertie/adaugare de noduri, stergere de
noduri si parcurgerea nodurilor.

Pentru a putea folosi o lista, este necesar sa fie retinuta adresa de început a listei (adresa primului nod). Ne vom referi în
continuare la aceasta adresa prin pointerul prim.

În cazul parcurgerii listei, operatia este relativ simpla. Cu ajutorul unui pointer auxiliar se pleaca de la prim si se
urmareste legatura spre nodul urmator, pointerul auxiliar primind pe rând adresa nodului urmator.

O operatie mai complicata este introducerea unui nod nou în lista. Se disting trei situatii: introducere la începutul listei,
introducere la sfârsit si introducere între doua noduri. Dupa alocarea spatiului de memorie necesar noului nod, presupunem
ca acesta este referit prin pointerul temp. În figurile 1, 2 si 3 se prezinta cele trei cazuri de adaugare a noului nod. Ordinea
operatiilor a fost marcata prin 1, 2, 3.

Figura 1: Organigrama adăugării unui nod la începutul listei

Figura 2: Organigrama adăugării unui nod la sfârsitul listei


Figura 3: Organigrama adăugării unui nod la mijlocul listei
În situatia stergerii unui nod din lista apar de asemenea cele trei situatii anterior descrise (nod de la începutul, de la
sfârsitul sau din mijlocul listei). Principala grija a programatorului în cazul stergerii unui nod trebuie sa fie eliberarea
spatiului de memorie alocat nodului care a fost sters si mentinerea integritatii listei (prin stergerea nodului, sa nu se "rupa"
lista). În figurile 4, 5 si 6 se prezinta cele trei situatii de stergere.

Figura 4: Organigrama ştergerii unui nod de la începutul listei

Figura 5: Organigrama ştergerii unui nod de la sfârsitul listei

Figura6: Organigrama ştergerii unui nod de la mijlocul listei

În continuare se vor prezenta principalele operaţii asupra listelor simplu înlănţuite în C .


1.5.1. Listele simplu înlănţuite
Pentru organizarea elementele în formă de listă simplu lănţuită, se utilizează structurile, care sînt legate cîte o
componentă în lanţ, începutul căreia (prima structură) este indicat de pointerul dl. Structura, care defineşte elementul listei,
conţine în afară de componenta informaţională şi un pointer la următorul element din listă. Descrierea acestui tip de structură
cu autoreferire şi pointerul în cauză se face în modul următor:
typedef struct nod // structura cu autoreferire
{
float val; // valoarea componentei informaţionale
struct nod *urm ; // pointerul la următorul element din lanţ
} DL;
DL *p; // pointerul la elementul curent
DL *prim; // pointerul la începutul listei

1.5 Reprezentarea listelor în memorie Spatiul de memorie ocupat de lista poate fi alocat static (printr-un
tablou) sau dinamic (folosind pointeri).
In primul caz, elementele vecine din lista ocupa pozitii alaturate in memorie.
Accesul la un element din lista, parcurgerea listei sau adaugarea unui element la sfarsitul listei se fac cu
usurinta, in timp ce inserarea si stergerea de elemente din mijlocul listei sunt operatii costisitoare, deoarece
presupun deplasarea unor elemente.
O lista alocata static este definita printr-un tablou de elemente reprezentand pointeri la informatia utila si prin
pozitiile elementului curent si a ultimului element. 

In cazul alocarii dinamice a memoriei, elementele listei pot sa nu fie vecine, ci dispersate in intreaga memorie disponibila.
Legarea intre ele a elementelor aceleiasi liste se face prin pointeri, care se adauga informatiei utile din elemente.
Structura LISTA este reprezentata prin lungimea ei si prin trei pointeri: la inceputul listei, la sfarsitul listei si la elementul
curent din lista.
Pentru ca operatiile de inserare si stergere sa se faca la fel pentru orice element din lista, s-au folosit doua elemente false
(intalnite si sub numele de elemente santinele), plasate la inceputul si la sfarsitul listei (inaintea primului, respectiv dupa
ultimul element din lista). In acest fel toate elementele utile din lista au atat predecesor cat si succesor.
Un element din lista poate contine o singura legatura - la elementul urmator (lista simplu inlantuita) sau doua legaturi- la
elementul urmator si la cel precedent (lista dublu inlantuita).
Structura element (sau celula) contine pentru o lista simplu inlantuita un pointer la informatia utila si informatia de
legatura (pointerul la elementul urmator).

Pentru alocarea memoriei elementelor listei în limbajul C se utilizează funcţiile malloc(sizeof(DL)) sau
calloc(l,sizeof(DL)). În C++ se utilizează operatorul de alocare new, apare sub forma:
pointer_la_nume = new nume [ iniţializator];
care încearcă să creeze un obiect nume prin alocarea unui număr egal cu sizeof(nume) de octeţi în memoria heap, adresa
acestuia este returnată şi asignată variabilei pointer_la_nume. În cazul, în care alocarea nu este efectuată cu succes, se
returnează valoarea NULL.
Operatorul de eliberare delete este apelat printr-o instrucţiune de forma
delete pointer_la_nume ;
eliberează memoria alocată, începînd cu adresa conţinută de pointer_la_nume. De exemplu:

Limbajul C Limbajul C++


p=malloc(sizeof(DL)); p=new(DL);
p->val=10; p->val=10;
p->n=NULL; p->n=NULL;
dl=malloc(sizeof(DL)); dl=new(DL));
dl->val=7; dl->n=p; dl->val=7; dl->n=p;
În ultimul element al listei pointerul la elementul vecin are valoarea NULL.
1.5.1.1 Structura unui nod este următoarea:
typedef struct tip_nod { int cheie; /* câmp neobligatoriu */
alte câmpuri de date utile;
struct tip_nod *urm; /* legătura spre următorul nod */
} TIP_NOD;

Organigrama fiind modelul grafic al listei simplu înlănţuite este prezentat în fig. 1.5.1.

Fig. 1.5.1. Model de listă simplu înlănţuită


Pointerii prim şi ultim vor fi declaraţi astfel:
TIP_NOD *prim, *ultim;
1.5.1.2 Crearea unei liste simplu înlănţuite
Crearea unei liste simplu înlănţuite se va face astfel:
a) Iniţial lista este vidă:
prim = 0; ultim = 0;
b) Se generează nodul de introdus:
n=sizeof(TIP_NOD);
p=(TIP_NOD *)malloc(n); /* rezervare spaţiu de memorie în heap*/
citire date în nodul de adresă p;
c) Se fac legăturile corespunzătoare:

p->urm = 0; /*nodul este ultimul în listă */


if(ultim != 0) ultim->urm = p; /* lista nu este vidă */
else prim = p;
/* nodul p este primul introdus în listă */
ultim=p;
1.5.1.3 Accesul la un nod al unei liste simplu înlănţuite În funcţie de cerinţe, nodurile listei pot fi accesate secvenţial,
extrăgând informaţia utilă din ele. O problemă mai deosebită este găsirea unui nod de o cheie dată şi apoi extragerea
informaţiei din nodul respectiv. Căutarea nodului după cheie se face liniar, el putând fi prezent sau nu în listă.
O funcţie de căutare a unui nod de cheie “key” va conţine secvenţa de program de mai jos; ea returnează adresa
nodului respectiv în caz de găsire sau pointerul NULL în caz contrar:
TIP_NOD *p;
p=prim;
while( p != 0 ) if (p->cheie == key)
{
/* s-a găsit nodul de cheie dată */
/* el are adresa p */
return p;
}
else p=p->urm;
return 0; /* nu există nod de cheie = key */

1.5.1.4 Inserarea unui nod într-o listă simplu înlănţuită

Nodul de inserat va fi generat ca la paragraful 1.5.1; se presupune că are pointerul p.

Dacă lista este vidă, acest nod va fi singur în listă:


if (prim == 0) {
prim=p; ultim=p; p->urm=0;
}

Dacă lista nu este vidă, inserarea se poate face astfel:


a) înaintea primului nod
if(prim != 0) {
p->urm = prim; prim = p;
}

b) după ultimul nod:


if (ultim != 0) {
p -> urm = 0; ultim -> urm = p; ultim = p;
}
c) înaintea unui nod precizat printr-o cheie “key”:
- se caută nodul de cheie “key”:
TIP_NOD *q, *q1;
q1=0; q=prim;
while(q!=0)
{
if(q->cheie==key) break;
q1=q; q=q->urm;
}
- se inserează nodul de pointer p, făcând legăturile corespunzătoare:
if(q!=0) { /*nodul de cheie “key” are adresa q */
if (q==prim) {
p->urm=prim; prim=p;
}
else {
q1->urm=p; p->urm=q;
}
}
d) după un nod precizat printr-o cheie “key”:
- se caută nodul având cheia “key”:
TIP_NOD *q;
q=prim;
while(q!=0) {
if(q->cheie==key) break;
q=q->urm;
}
- se inserează nodul de adresă p, făcând legăturile corespunzătoare:
if (q !=)0) { /* nodul de cheie “key” are adresa q */
p -> urm = q -> urm;
q -> urm=p;
if (q == ultim) ultim = p;
}

1.5.1.5 Ştergerea unui nod dintr-o listă simplu înlănţuită La ştergerea unui nod se vor avea în vedere următoarele
probleme: lista poate fi vidă, lista poate conţine un singur nod sau lista poate conţine mai multe noduri.
De asemenea se poate cere ştergerea primului nod, a ultimului nod sau a unui nod dat printr-o cheie “key”.
a) Ştergerea primului nod
TIP_NOD *p;
if(prim!=0) { /* lista nu este vidă */
p=prim; prim=prim->urm;
elib_nod(p);
/*eliberarea spaţiului de memorie */
if(prim==0) ultim=0;
/* lista a devenit vidă */
}
b) Ştergerea ultimului nod
TIP_NOD *q, *q1;
q1=0; q=prim;
if(q!=0) { /* lista nu este vidă */
while(q!=ultim)
{
q1=q; q=q->urm;
}
if(q==prim) {
prim=0; ultim=0;
}
else {
q1->urm=0; ultim=q1;
}
elib_nod(q);
}
c) Ştergerea unui nod de cheie “key”
TIP_NOD *q, *q1;
/* căutare nod */
q1=0; q=prim;
while (q!=0)
{
if(q->cheie == key) break; /* s-a găsit nodul */
q1=q; q=q->urm;
}
if(q != 0) { /* există un nod de cheie “key” */
if (q == prim) {
prim=prim_>urm;
elib_nod(q);
/*eliberare spaţiu */
if( prim==0) ultim=0;
}
else {
q1->urm=q->urm;
if(q==ultim) ultim=q1;
elib_nod(q); /* eliberare spaţiu */
}

Ştergerea unei liste simplu înlănţuite. În acest caz, se şterge în mod secvenţial fiecare nod:
TIP_NOD *p;
while( prim != 0) {
p=prim; prim=prim->ultim;
elib_nod(p);
/*eliberare spaţiu de memorie */
}
ultim=0;
1.5.2. Operaţii asupra listelor simplu lănţuite. Lista are următoarea formă:

Fiecare element al listei simplu lănţuite reprezintă o structură alcătuită din două componente: val – folosit pentru
componenta informaţională şi p pentru pointer la următorul element din lista lănţuită. Pointerul dl indică adresa de alocare
pentru primul element al listei. Pentru toate operaţiile asupra listei se va utiliza următoarea descriere a structurii elementelor
listei:
typedef struct nod
{ float val; struct nod * urm; } NOD;
int i,j;
NOD * prim, * r, * p;
Pentru executarea operaţiilor pot fi utilizate următoarele fragmente de program:
1) formarea listei simplu lănţuite:
float x=5; int n=1; p=new(nod); r=p; p->urm=NULL; p->val=x; prim=p;
while (p->val !=0) { p=new(nod); n++; r->urm=p; p->urm=NULL; p->val=x-1.0*n; r=p;}
2) tiparul elementului j:
r=prim;j=2;
while(r!=NULL && j<n-1) { if (r==NULL) printf("\n nu este elementul %d ",j);
else printf("\n elementul %d este egal cu %f ",j++,r->val); r=r->urm; }
3) tiparul ambilor vecini ai elementului determinat de pointerul p :

p=prim;
if((r=p->urm)==NULL) printf("\n nu are vecin din dreapta");else printf("\n vecinul din dreapta este %f", r->val);
if(prim==p) printf("\n nu are vecin din stanga" );
else { r=prim;
while( r->urm!=p ) r=r->urm; printf("\n vecinul de stanga este %f", r->val); }
4) eliminarea elementului, care este succesorul elementului în cauză, la care indică pointerul р

p=prim;
if ((r=p->urm)==NULL) printf("\n nu este succesorul "); p->urm=r->urm; delete(r->urm);
5) insertarea noului element cu valoarea newval=100 după elementul determinat de pointerul p:

r=new(NOD);
r->urm=p->urm; r->val=100; p->urm=r;
Numărul de operaţii necesare pentru executarea operaţiilor indicate asupra listelor lănţuite se evaluează de următoarele
relaţii: pentru operaţiile de tipul 1 şi 2 - Q=l; pentru operaţii de tipul 3 şi 4 - Q=1; pentru operaţii de tipul 5 - Q=l.
Exerciţiul 1. Să se analizeze programul pentru crearea unei liste simplu înlănţuite cu preluarea datelor de
la tastatură. Sfârşitul introducerii datelor este marcat standard (CTRL+Z). După creare, se va afişa conţinutul
listei apoi se va elibera memoria ocupată. Analizaţi şi construiţi organigrama
#include<stdio.h>
#include<conio.h>
#include<alloc.h>
#define NEW (NOD*)malloc(sizeof(NOD));
struct lista { int info; lista *next; };
typedef struct lista NOD;
NOD* creare() { NOD *prim,*ultim,*q; int nr;
printf(" primul nr="); scanf("%i",&nr); // se citeşte primul număr
prim=(NOD*)malloc(sizeof(NOD)); prim->info=nr; // se creează primul nod
prim->next=NULL; ultim=prim; // se creează ultimul nod
printf("\n urmatorul nr sau tastati CTRL+Z pentru iesire) = "); scanf("%i",&nr); // se citeşte al doilea număr
while(!feof(stdin)) // atâta timp cât nu introducem CTRL+Z
{ q=(NOD*)malloc(sizeof(NOD)); q->info=nr; // se creează nodul următor
q->next=NULL; ultim->next=q; ultim=q; // se creează ultimul nod
printf("\n urmatorul nr sau tastati CTRL+Z pentru iesire)= "); scanf("%i",&nr); // se citeşte numărul următor
} return prim; }
void afis(NOD* prim) { printf("\n"); printf("\n Lista este");
while(prim!=NULL) // atâta timp cât p este diferit de NULL
{ printf("\t \n%i",prim->info); // se afişează nodul curent
prim=prim->next; // se trece la următorul nod
} }
void stergere (NOD* prim) { NOD *p;
while(prim) // atâta timp cât prim este diferit de NULL
{ p=prim; // p devine primul nod
prim=prim->next; // prim devine următorul nod
free(p); // se eliberează nodul curent
} }
void main() { NOD *prim; int a,b,c,d,e,f,g; clrscr();
// Creare
prim=creare(); afis(prim);
// Eliberare memorie
stergere(prim); getch();
}
1.5.3 Organizarea prelucrării listelor dublu lănţuite
Lista dublu lănţuită este o listă în care fiecare element conţine doi pointeri: unul la precedentul element, altul – la
succesorul element din listă.
O componenta a unei liste dublu inlantuite se declara ca o data structurata de tip inregistrare, formata din trei campuri:
informatia propriu-zisa (care poate fi de orice tip: numeric, caracter, pointer, tablou, inregistrare) si informatiile de legatura
(adresa la care e memorata urmatoarea componenta si adresa la care e memorata precedenta componenta). Ultima
componenta va avea informatia de legatura corespunzatoare urmatoarei adrese NULL (sau 0), cu semnificatia ca dupa ea nu
mai urmeaza nimic (retine adresa „nici o adresa” a urmatoarei componente).La fel si in cazul primei componente pentru
campul adresa precedenta.
 
0 0
adr informatia adr
   prec utila urm 
 
  Conform celor enuntate anterior memorarea si prelucrarea acestor structuri de date este similara cu cea a listelor liniare
simplu inlantuite, ba chiar unele prelucrari (cum ar fi stergerea si inserarea inainte se simplifica avand intr-un mod facil acces
la componenta anterioara)
Lista dublu lănţuită este o listă, în care fiecare element al listei conţine doi pointeri: unul la precedentul element,
altul – la succesorul element din listă. Lista dublu lănţuită în program se poate determina cu ajutorul următoarelor descrieri:
typedef struct ndd
{ float val; /* valoarea informaţională a componentei */
struct ndd * succesor; /* pointer la succesorul element al listei n*/
struct ndd *precedent; /* pointer la precedentul element al listei m*/
} NDD;
NDD * prim, * p, * r;
Interpretarea grafică a listei F=< 2,5,7,1 > ca listă dublu lănţuită este următoarea:

Insertarea noului element cu valoarea newval după elementul determinat de pointerul p, se efectuează de operatorii
r=new(NDD);
r->val=newval;
r->succesor=p->succesor;
(p->succesor)->precedent=r;
p->=r;

Eliminarea elementului urmat de elementul, la care indică pointerul p, se


efectuează în modul următor:
p->succesor=r;
p->succesor=(p->succesor)->succesor;
( (p->succesor)->succesor )->precedent=p;
delete r;

Lista liniară este ciclică, dacă ultimul element al listei indică la primul element, iar pointerul dl indică la ultimul element
al listei. Schema listei ciclice pentru lista F=< 2,5,7,1 > este următoarea:

La rezolvarea problemelor pot apărea diferite tipuri de liste lănţuite.


Exerciţiul 2. Exemplu de program de prelucrare a listei dublu lantuita. Analizaţi şi construiţi
organigrama
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
typedef struct lista //definim tipul de date LISTA care este o lista dublu lantuita
{ char x; //fiecare element contine cate un element de tip char
struct lista *next,*anti; // si legaturile la urmatorul si la precedentul elemnte
}LISTA; //tipul de date care este creat acuma
LISTA *r,*p; // doi pointeri care ne vor ajuta la crearea si parcurgerea listei
int nr; LISTA *prim; // pointer la primul element al listei
void add(char y) { if(!prim) //daca nu exista primul element
{ prim=new LISTA; //atunci el se creaza
if(!prim)printf("\a\n Memorie insuficienta !!!"); // daca nu s-a putut crea atunci mesaj de eroare
prim->x=y; //se initializeaza campul x din Lista cu valoarea variabilei y transmisa ca parametru
prim->next=NULL; // nu exista urmatrul elemnt
prim->anti=NULL; // nu exista elemntul anterior
} else { r=new LISTA; // daca exista elemente in lista atunci se creaza altul, r
if(!r)printf("\a\n Memorie insuficienta !!!"); // daca nu s-a alocat memorie atunci va fi afisat un mesaj de erare
r->x=y; // se initializeaza campul x din pointerul r, creat recent
p=prim; // de la inceputul listei
while(p->next) p=p->next; // parcurgem pana la ultimul
r->next=NULL; // de la elementul creat acuma, r nu exista elemente dupa el
r->anti=p; // se face legatura dintre elementul r cu ultimul element
p->next=r; // se realizeaza legatura inversa, dintre ultimul element cu r, cel care va deveni ultimul
} }
void schimba(char z,char y) // functie care va shimba caracterul din variabila z cu caracterul din y
{ int d=0; // variabila care va contine numarul de modificari efectuate
p=prim; // incepand cu primul element se parcurge lista si
while(p) // cat timp nu s-a ajuns la ultimul element
{ if(p->x==z) // si daca elementul dat x este egal cu variabila y
{ p->x=y; //atunci el se modifica
d++; // se modifica si numarul de modificari cu 1
} p=p->next; //se trece la urmatorul element
} if(!d) printf("\n Nu exista in lista caracterul %c !!!",z); // se afiseaza daca nu s-a facut nici o modificare
else printf("\n\n Caracterul %c a fost modificat in \'%c\' de %d ori. ",z,y,d); // se afiseaza numarul de modificari
}
void print() //functia de afisare a listei
{ p=prim; // de la primul element
printf("\n\nLista contine:\n"); while(p) // cat timp nu s-a ajuns la ultimul element
{ if(p)printf("%c",p->x); // afiseaza elementul dat
p=p->next; // se trece la urmatorul element
} getch(); }
void salveaza_invers(char fis[]) // functie de salvare a listei intr-un fisier fis
{ FILE *f; // pointer la fisier, pentru lucrul cu fisiere
f=fopen(fis,"w"); // deschiderea fisierului dat pentru creare si scrierea datelor in fisier
if(!f){ printf("\n\nEroare la deschidere a fisierului");return;} // daca nu s-a deschis din motive de acces
p=prim; // incepand cu primul element
while(p) // pana la ultimul
{r=p; // la iesire din ciclu r va fi ultimul element
p=p->next; // se deplaseaza la urmatorul element
} while(r!=NULL) // daca r este diferit de NULL, adica este element creat in memorie
{ if(r) fprintf(f,"%c",r->x); // atunci se scrie in fisier caracterul dat
r=r->anti; // se deplaseaza cu un element in urma
} fclose(f); // se inchide fisierul
printf("\n\n Datele au fost scrise in fisier ..."); }
void print_invers() // functie care afiseaza lista in forma inversa
{ p=prim; // de la primul element
printf("\n\nLista contine:\n"); while(p) // cat timp nu s-a ajuns la ultimul element
{r=p; // se duce la
p=p->next; // ultimul element
} while(r!=NULL) // si parcurgand in sens invers, spre primul element
{ if(r) printf("%c",r->x); // si afiseaza caracterele in forma inversa
r=r->anti; // se deplaseaza spre inceputul listei
} getch(); // se asteapta introducerea unul caracter de la tastatura
}
void main() { int i; char c; // caracterul citit de la tatstatura si clrscr(); // se curata ecranul
clrscr(); printf("\nIntroduceti sirul de caractere terminandu-se cu \'$\' :\n");
do{ c=getche(); // se preia un caracter de la tastatura si se afiseaza, si apoi se atribuie lui c
if(c=='$') break; // daca c este egal cu $ atunci se iese din ciclu
else add(c); // daca nu se iese din ciclu atunci se adauga in lista
}while(c!='$'); // se citeste cate un caracter pana cand c este diferit de $
print(); // se afiseaza lista in ordinea introdusa
print_invers(); // se afiseaza in ordinea inversa
schimba('x','f'); // toate caracterele 'x' se modifica in 'f'
print(); // se afiseaza lista modificata
salveaza_invers("out.txt"); // se salveaza in fisierul "out.txt" datele in forma iversa
getch(); // se asteapta introducerea unul caracter de la tastatura
}
1.5.4 LISTE CIRCULARE
O listă circulară este o listă liniară înlănţuită cu proprietatea că ultimul său nod nu are cîmpul de legătură egal cu Λ, el
conţinînd o referinţă la primul nod.
Presupunem că nodurile au cîte două cîmpuri, INFO şi LEG. Variabila de referinţă PTR face referire (are ca valoare
adresa) unui nod din lista circulară. LEG(PTR) este adresa nodului următor. Dacă LEG(PTR)=PTR atunci lista circulară
constă dintr-un singur nod, care se înlănţuie cu el însuşi. Lista vidă este reprezentată de PTR=Λ.
Algoritmii de inserţie şi extragere sunt următorii:
INSERŢIE: P←DISP; INFO(P)←Y; dacă PTR=Λ, atunci PTR←P şi LEG(P)←P; altfel LEG(P)←LEG(PTR),
LEG(PTR)← P.
EXTRAGERE: Dacă PTR=Λ atunci subdepăşire; altfel P←LEG(PTR), Y←INFO(P), LEG(PTR)←LEG(P); dacă
PTR=P, atunci PTR←Λ; DISP←P.
Prin utilizare de liste circulare alte operaţii importante devin foarte eficiente, de exemplu ştergerea unei liste circulare,
adică punerea unei liste circulare accesate de pointerul PTR în stiva DISP:
Dacă PTR≠Λ, atunci P←DISP, DISP←LEG(PTR), LEG(PTR)←P.
Se observă că DISP şi LEG(PTR) şi-au interschimbat valorile. La fel de simplă este şi operaţia de reunire a două liste
circulare într-o singură listă circulară.
1.5.2 Definirea tipului abstract stiva. În funcţie de metoda de acces la elementele listei liniare pot fi cercetate
următoarele tipuri de liste liniare: stive, cozi şi cozi de tip vagon. Stiva este o consecutivitate de elemente de acelaşi tip –
variabile scalare, tablouri, structuri sau uniuni. Stiva reprezintă o structură dinamică, numărul de elemente a căreia variază.
Dacă stiva n-are elemente, ea este vidă. Stiva este un tip special de lista în care toate insertiile si suprimarile de noduri au loc
la un singur capat. Acest capat se numeste vârful stivei.
Tipul abstract stiva pe care îl definim contine urmatorii operatori:
1. Initializarea stivei.
2. Verificarea faptului ca stiva e plina.
3. Verificarea faptului ca stiva e goala.
4. Introducerea unui element în vârful stivei.
5. Eliminarea elementului din vârful stivei.
6. Furnizarea elementului din vârful stivei fara a-l elimina.
Stiva este o listă simplu înlănţuită bazată pe algoritmul LIFO (Last In First Out), adică ultimul nod introdus este primul
scos. Modelul stivei, care va fi avut în vedere în continuare, este prezentat în fig.1.6.1.

Fig. 1.6.1. Model de stivă

Fiind o structură particulară a unei liste simplu înlănţuite, operaţiile principale asupra unei stive sunt:
- push - pune un element pe stivă; funcţia se realizează conform paragrafului 2.3.a., adică prin inserarea unui
nod înaintea primului;
- pop - scoate elementul din vârful stivei; funcţia se realizează conform paragrafului 2.4.a., adică prin ştergerea
primului nod;
- clear - ştergerea stivei; funcţia se realizează conform paragrafului 2.5.
În concluzie, accesul la o stivă se face numai pe la un capăt al său.

1.5.3. Cozi Coada este o listă simplu înlănţuită, bazată pe algoritmul FIFO (First In First Out), adică primul element
introdus este primul scos. Modelul cozii care va fi avut în vedere în consideraţiile
următoare, este prezentat în fig.1.7.1.
Fig.1.7.1. Model de coadă

Se introduce
un element
nou
Deci coada are două capete, pe la unul se introduce un element, iar de la celalalt capăt se scoate un element.
Operaţiile importante sunt:
- introducerea unui element în coadă - funcţia se realizează prin inserarea după ultimul nod,
- scoaterea unui element din coadă – funcţia se realizează prin ştergerea primului nod;
- ştergerea cozii.

Coada este o listă liniară, în care elementele listei se elimină din capul listei, şi elementele noi se includ prin coada listei.
Coadă de tip vagon este o listă liniară, în care includerea şi eliminarea elementelor din listă se efectuează din ambele
capete (vîrful şi sfîrşitul) ale listei.
Stiva şi coada se organizează atît static prin intermediul tabloului, cît şi dinamic – prin listă (simplu sau dublu lănţuită).
Vom cerceta cum se utilizează lista în formă de stivă pentru implementarea calculării expresiei aritmetice în formă
inversă poloneză. În astfel de mod de prezentare a expresiei, operaţiile se înregistrează în ordinea executării lor, iar operanzii
se află nemijlocit în faţa operaţiei. De exemplu, expresia (6+8)*5-6/2
în forma inversă poloneză are forma: 6 8 + 5 * 6 2 / -
Utilizînd noţiunea de stivă, expresia aritmetică în formă inversă poloneză se execută print-o singură trecere de examinare
a expresiei. Fiecare număr se introduce în stivă, iar operaţia se execută asupra următoarelor două elemente din vîrful stivei,
înlocuindu-le cu rezultatul operaţiei efectuate. Dinamica schimbărilor din stivă va fi următoarea:
S = < >; <6>; <6,8>; <14>; <14,5>; <70>; <70,6>; <70,6,2>;
<70,3>; <67>.
Mai jos este descrisă funcţia eval, care calculează valoarea expresiei indicate în tabloul m în formă de expresie inversă
poloneză, m[i]>0 indică numărul nenegativ, iar valoarea m[i]<0 - operaţia. În calitate de coduri pentru operaţiile de adunare,
scădere, înmulţire şi împărţire se aleg numerele: -1, -2, -3, -4. Pentru organizarea stivei se utilizează tabloul interior stack.
Parametrii funcţiei sînt tabloul de intrare m şi lungimea sa l.
float eval (float *m, int l)
{ int p,n,i;
float stack[50],c;
for(i=0; i < l ;i++)
if ((n=m[i])<0)
{ c=st[p--];
switch(n)
{
case -1: stack[p]+=c; break;
case -2: stack[p]-=c; break;
case -3: stack[p]*=c; break;
case -4: stack[p]/=c;
}
}
else stack[++p]=n;
return(stack[p]);
}
Problema rezolvata Sa se realizeze un program care raspunde la urmatoarele comenzi:

 a - Se citeste o linie de forma: identificator numar, unde numar este un numar întreg. Ca
rezultat, se retine în evidenta identificatorul împreuna cu numarul asociat lui.

 t - Se citeste o linie ce contine un identificator. Daca acesta apare în evidenta, se tipareste valoarea asociata
lui; în caz contrar se tipareste un mesaj de eroare.

 s - Se citeste o linie ce contine un identificator si îl sterge din evidenta.

 l - Se tiparesc identificatorii din evidenta în ordine alfabetica împreuna cu valorile asociate lor.

 +- Se citesc doua linii, fiecare continând un identificator. În cazul în care ambii se afla în evidenta se
tipareste suma lor. În caz ca unul sau ambii identificatori lipsesc din evidenta, se afiseaza un mesaj de eroare.

 -- Se citesc doua linii, fiecare continând un identificator. În cazul în care ambii se afla în evidenta se
tipareste diferenta lor. În caz ca unul sau ambii identificatori lipsesc din evidenta, se afiseaza un mesaj de eroare.

 *- Se citesc doua linii, fiecare continând un identificator. În cazul în care ambii se afla în evidenta se
tipareste produsul lor. În caz ca unul sau ambii identificatori lipsesc din evidenta se afiseaza un mesaj de eroare.

 /- Se citesc doua linii, fiecare continând un identificator. În cazul în care ambii se afla în evidenta se
tipareste rezultatul împartirii lor. În caz ca unul sau ambii identificatori lipsesc din evidenta se afiseaza un mesaj de
eroare.

 f - Se termina programul.

Observatie: Identificatorii sunt pastrati în tabela în mod ordonat.

Codul sursa
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <ctype.h>
#define Max 100 /* lungimea maxima a unei linii */

/* tipul pentru nodul listei */


typedef struct elem
{
char *id;
int valoare;
struct elem *urm;
} nod;

nod *radacina=NULL; /* pastreaza inceputul listei */


/*---------------------------------------------------------*/
/* */
/* Functia caut cauta sirul s in lista al carei inceput e indicat de parametrul lista. Daca sirul apare in lista*/
/* atunci functia returneaza pointerul la nodul respectiv, in caz contrar returneaza NULL */
/* */
/*---------------------------------------------------------*/
nod *caut(nod *lista, char *s)
{
nod *q1;

for (q1=lista; q1!=NULL && strcmp(q1->id, s)<0; q1=q1->urm);


/* caut 1 */

if (q1!=NULL && strcmp(q1->id, s)==0) /* caut 2 */


return q1; /* daca sirul s a fost gasit in lista */

return NULL;
}
/*---------------------------------------------------------*/
/* */
/* Functia listez parcurge lista si pentru fiecare nod afiseaza identificatorul memorat si valoarea atasata lui.*/
/*Deoarece lista este ordonata, afisarea identificatorilor* este in ordine alfabetica */
/* */
/*---------------------------------------------------------*/
void listez(void)
{
nod *q;

for (q=radacina; q!=NULL; q=q->urm)


printf("Identificator: %s Valoare: %d\n",q->id,q->valoare);
}

/*---------------------------------------------------------*/
/* Functia sterg elimina din lista indicata de pointerul lista, nodul ce are campul id egal cu argumentul s */
/* */
/*---------------------------------------------------------*/
nod *sterg(nod *lista, char *s)
{
nod *q1, *q2;

for (q1=q2=lista; q1!=NULL && strcmp(q1->id, s)<0;


q2=q1,q1=q1->urm); /* sterg 1 */
/* se parcurge lista cautandu-se nodul avand */
/* campul id egal cu sirul s */

if (q1!=NULL && strcmp(q1->id, s)==0) /* sterg 2 */


{
/* daca s-a gasit un astfel de nod */
if (q1!=q2) /* daca nodul nu este la inceputul listei */
/* sterg 3 */
q2->urm = q1->urm; /* elimina nodul din lista */
else /* nodul apare la inceputul listei */
lista = lista->urm; /* sterg 4 */

free(q1->id);
/* se elibereaza memoria ocupata de nodul eliminat */
free(q1);

return lista; /*returneaza pointerul catre inceputul listei modificate*/


}
else
{
printf("Eroare: identificatorul %s nu apare in lista\n", s);
return lista;
}
}
/*---------------------------------------------------------*/
/* */
/* Functia introduc insereaza un nod in lista ordonata, indicata de parametrul lista. Lista ramine ordonata si */
/* dupa inserare. Nodul nou are campul id egal cu sirul indicat de parametrul s, iar campul valoare egal cu */
/* parametrul v. Functia returneaza pointerul catre inceputul listei modificate */
/*---------------------------------------------------------*/
nod *introduc(nod *lista, char *s, int v)
{
nod *q1, *q2, *aux;
if ((aux=(nod *)malloc(sizeof(nod)))==NULL ||
(aux->id=(char *)malloc(strlen(s)+1))==NULL)
/* introduc 1 */
{
/* daca nu e memorie suficienta pentru a crea un nod nou, respectiv pentru a memora sirul s, se da un mesaj de eroare
dupa care executia e incheiata */

printf("Eroare: memorie insuficienta\n");


exit(1);
}
strcpy(aux->id, s); /* se salveaza s in nodul nou */
aux->valoare=v; /* se salveaza v in nodul nou */

/* nodul nou este inserat in lista ordonata astfel incat ea ramane ordonata si dupa inserare. Lista este parcursa
cautandu-se primul nod avand campul id mai mare sau egal cu s */

for (q2=q1=lista; q1!=NULL && strcmp(q1->id, s)<0;


q2=q1, q1=q1->urm); /* introduc 2 */

if (q1!=NULL && strcmp(q1->id, s)==0) /* introduc 3 */


/* daca in lista apare un nod avand campul id egal cu s, atunci se afiseaza un mesaj de eroare si se pastreaza
lista nemodificata*/
{
printf("Eroare: %s apare in tabela\n", s);
return lista;
}

if (q1!=q2) /* daca inserarea nu se face la inceputul listei*/


/* introduc 4 */
{
q2->urm=aux;
aux->urm=q1;
return lista ;
}

/* daca inserarea se face la inceputul listei */


/* introduc 5 */
aux->urm=lista;
return aux;
}

/*-------------------------------------------------------------*/
/* */
/*Functia citesc_linie citeste urmatoarea linie de la tastatura si recunoaste identificatorul si valoarea asociata lui. */
/*Identificatorul este returnat in parametrul s, iar valoarea in parametrul val */
/*-------------------------------------------------------------*/
void citesc_linie(char *s, int *val)
{
int i, j;
char temp[Max];

/* citeste urmatoarea linie de la tastatura */


gets(temp); /* citesc_linie 1 */

s[0]='\0'; /* initializeaza valorile argumentelor */


for (i=0; temp[i]!='\0'; )
/* atata timp cat nu a fost atins sfarsitul sirului */
/* citesc_linie 2 */
{
if (isalpha(temp[i])) /* daca incepe un identificator */
/* citesc_linie 3 */
{
j=0;

/* memoreaza identificatorul in s */
while (isalnum(temp[i]))
s[j++]=temp[i++]; /* citesc_linie 4 */

/* memoreaza sfarsitul de sir */


s[j]='\0'; /* citesc_linie 5 */
continue;
}

if (isdigit(temp[i])) /* daca incepe un numar */


/* citesc_linie 6 */
{
*val=0;
while (isdigit(temp[i])) /* citesc_linie 7 */
{
/* calculeaza valoarea numarului */
*val=*val*10+temp[i]-'0' ; /* citesc_linie 8 */
i++;
}
continue;
}

/* altfel se trece peste caracterul curent */


i++; /* citesc_linie 9 */
} /* while */
}
/*----------------------------------------------------------*/
/* */
/* Functia comanda_a realizeaza functionalitatea comenzii a */
/* */
/*----------------------------------------------------------*/
void comanda_a(void)
{
int val;
char s[Max];

citesc_linie(s, &val); /* citeste o linie de la tastatura */


if (strlen(s)!=0) /* daca linia e corecta */
radacina=introduc(radacina, s, val);
else printf("Eroare : linie incorecta\n");
}

/*---------------------------------------------------------*/
/* */
/* Functia comanda_t realizeaza functionalitatea comenzii t */
/* */
/*---------------------------------------------------------*/
void comanda_t(void)
{
int val;
char s[Max];
nod *p;

citesc_linie(s, &val); /* citeste o linie de la tastatura */


if (strlen(s)==0) /* daca linia e incorecta */
{printf("Eroare: linie incorecta\n"); return;}

if ((p=caut(radacina, s))!=NULL) /* cauta nodul in lista */


printf("Identificator:%s Valoare:%d\n", p->id, p->valoare);
else
printf("Eroare: Identificator nedefinit\n");
}
/*---------------------------------------------------------*/
/* */
/* Functia comanda_s realizeaza comanda s */
/* */
/*---------------------------------------------------------*/

void comanda_s(void)
{
char s[Max];
int val;

citesc_linie(s, &val); /* citeste o linie de la tastatura */


if (strlen(s)==0) /* daca linia citita e incorecta */
{printf("Eroare: linie incorecta\n"); return;}

radacina=sterg(radacina, s); /* sterge nodul din lista */


}

/*---------------------------------------------------------*/
/* */
/* Functia comanda_oper executa operatiile legate de */
/* comenzile +, -, *, / */
/* Se citesc cei doi operatori si se executa operatia */
/* dorita. Rezultatul este afisat. */
/* */
/*---------------------------------------------------------*/
void comanda_oper(char c)
{
char s1[Max], s2[Max];
int val;
nod *p1, *p2;

/* se citeste primul operand */


citesc_linie(s1, &val); /* comanda_oper 1 */

/* se citeste al doilea operand */


citesc_linie(s2, &val); /* comanda_oper 2 */

if (strlen(s1)!=0 && strlen(s2)!=0)


if(((p1=caut(radacina, s1))!=NULL) &&
((p2=caut(radacina, s2))!=NULL)) /* comanda_oper 3 */
/* se verifica daca operanzii apar in lista */
{
switch(c) /* functie de tipul comenzii */
{
case '+':
val=p1->valoare+p2->valoare;
break;
case '-':
val=p1->valoare-p2->valoare;
break;
case '*':
val=p1->valoare*p2->valoare;
break;
case '/':
if (p2->valoare!=0)
val=p1->valoare/p2->valoare;
else
printf("Eroare: Impartire la 0\n");
break;
}
printf("Rezultatul operatiei e %d\n", val);
}
else
printf("Operand nedefinit\n");
else
printf("Eroare: linie eronata\n");
}
/*-----------------------------------------------------------*/
/* */
/* Functia meniu afiseaza meniul programului,citeste comanda */
/* si apeleaza subrutina corespunzatoare */
/* */
/*-----------------------------------------------------------*/
void meniu(void)
{
char o;

while(1) /* meniu 1 */
{
clrscr();
/* se afiseaza meniul programului */
puts("a : adauga un identificator si valoarea asociata");
puts("t : tipareste valoarea asociata unui identificator");
puts("s : sterge un identificator");
puts("l : listeaza identificatorii si valorile asociate");
puts("+ : calculeaza suma pentru 2 identificatori");
puts("- : calculeaza diferenta pentru 2 identificatori");
puts("* : calculeaza produsul pentru 2 identificatori");
puts("/ : calculeaza impartirea pentru 2 identificatori");
puts("f : termina programul");
printf("\nOptiunea: ");
o=getche(); /* meniu 2 */
printf("\n\n");

switch (tolower(o)) /* meniu 3 */


{
case 'a':
comanda_a(); break;
case 't':
comanda_t(); break;
case 's':
comanda_s(); break;
case 'l':
listez(); break;
case '+': case '-': case '*': case '/':
comanda_oper(o);
break;
case 'f':
return;
default:
printf("Eroare : Comanda inexistenta\n");
}
printf("\nApasa orice tasta...");
getch();
}
}

/*----------------------------------------------------*/
/* Functia main apeleaza functia meniu */
/*----------------------------------------------------*/
void main(void)
{
meniu();
}
Comentarea programului Programul memoreaza identificatorii si valorile asociate lor într-o evidenta, pe care o
foloseste conform functionalitatii cerute. Evidenta este realizata printr-o lista simplu înlantuita ordonata. Fiecare nod al listei
contine un câmp pentru pastrarea identificatorului, care este indicat de char *id, un câmp pentru memorarea valorii
asociate identificatorului, câmpul int valoare, si un câmp pentru a crea înlantuirea cu nodul urmator din lista, câmpul
struct elem *urm. Lista este ordonata crescator functie de câmpurile id ale nodurilor.
Functia main apeleaza functia meniu. Aceasta afiseaza în mod repetat, datorita instructiunii while(1) de pe linia /*
meniu 1 */, optiunile pe care le ofera programul. În continuare, meniu citeste de la tastatura optiunea dorita (linia /*
meniu 2 */) si caracterul <Return>. În functie de optiunea aleasa (linia /* meniu 3 */) se selecteaza rutina ce
implementeaza functionalitatea dorita.
Rutina comanda_a citeste un identificator si valoarea asociata lui, dupa care îl introduce în evidenta. Citirea se face
apelând functia citesc_linie. În cazul în care lungimea identificatorului citit este 0, se afiseaza un mesaj de eroare. În caz
contrar, identificatorul si valoarea asociata lui sunt introduse în lista ordonata prin apelarea rutinei introducere.
comanda_t citeste un identificator folosind citesc_linie. Daca lungimea identificatorului este 0, atunci se afiseaza mesajul
ca linia este incorecta. Daca însa lungimea este diferita de 0, atunci identificatorul este cautat în lista (apelând functia caut).
Daca identificatorul apare în lista, atunci se afiseaza valoarea asociata lui, în caz contrar se tipareste un mesaj de eroare
adecvat.
Functia comanda_s citeste o linie ce contine un identificator (folosind citesc_linie). Daca linia citita este corecta (adica
lungimea identificatorului este nenula), atunci identificatorul este eliminat din evidenta prin apelarea lui sterg.
comanda_oper citeste doi identificatori (liniile /* comanda_oper 1 */ si /* comanda_oper 2 */) si
efectueaza o operatie aritmetica cu acesti operanzi. Felul operatiei dorite este transmis prin parametrul char c al rutinei
comanda_oper. Daca identificatorii sunt corecti (au lungimile diferite de 0), atunci ei sunt cautati în evidenta (linia /*
comanda_oper 3*/). Daca ambii apar, atunci, functie de c, se efectueaza operatia dorita, iar rezultatul este afisat.
Citirea unei linii de la tastatura se face prin rutina citesc_linie. Linia /* citesc_linie 1 */ citeste în variabila
locala temp o linie de la tastatura. Ciclul while de pe linia /* citesc_linie 2 */ parcurge tabloul temp. Daca
gaseste o litera (linia /* citesc_linie 3 */), atunci înseamna ca a descoperit începutul unui identificator. Ciclul
while de pe linia /* citesc_linie 4 */ preia întregul identificator si îl memoreaza în tabloul al carui început este
indicat de parametrul char *s. Linia /*citesc_linie 5 */ memoreaza caracterul ''\0'' ce indica sfârsitul
identificatorului. Daca la parcurgerea lui temp este întâlnita o cifra (linia /* citesc_linie 6 */), atunci a fost
descoperit începutul numarului care apare în linia de text. Acest numar este preluat prin ciclul while de pe linia /*
citesc_linie 7 */, calculându-se simultan si valoarea numerica asociata lui. Linia /* citesc_linie 8 */
face conversia, din sirul de caractere asociat numarului întreg, în valoarea lui. Calcularea valorii numerice se face folosind
variabila întreaga a carei adresa este transmisa prin parametrul int *val. Linia /* citesc_linie 9 */ "sare"
peste acele caractere care nu fac parte din identificator sau numar.
caut parcurge lista al carei început este transmis prin parametrul nod *lista, cautând nodul al carui identificator este
egal cu sirul indicat de parametrul char *s. Daca un astfel de nod este gasit, atunci caut returneaza pointerul catre el. În
caz contrar, returneaza NULL. Parcurgerea listei se face prin ciclul for de pe linia /* caut 1 */. Variabila q1 este
initializata astfel încât sa indice începutul listei. La sfârsitul fiecarei iteratii, q1 este mutat catre urmatorul nod. Conditia de
reluare a ciclului este ca lista sa nu fi fost parcursa în întregime (conditia q1 != NULL de pe linia /* caut 1 */) si
identificatorul sa nu fi fost gasit. Deoarece lista este ordonata crescator este suficient sa testam ca strcmp(q1->d,s)
<0 (linia /* caut 1 */). Linia /* caut 2 */ verifica daca identificatorul a fost gasit. Pentru aceasta este necesar
ca q1 != NULL (altfel s-ar fi parcurs toata lista) si sirul memorat în nodul curent sa fie egal cu cel indicat de s.
Rutina listez parcurge toate nodurile din lista si, pentru fiecare, afiseaza identificatorul retinut si valoarea atasata lui.
Rutina sterg elimina, din lista referita prin parametrul nod *lista, nodul al carui identificator este egal cu sirul
indicat de parametrul char *s. sterg returneaza pointerul catre începutul listei modificate. Dupa eliminare, lista ramâne
ordonata. Pentru eliminarea unui nod sunt folositi doi pointeri care parcurg lista. q1 indica nodul curent, în timp ce q2 pe cel
anterior lui. Acest lucru este necesar deoarece la eliminarea nodului indicat de q1, înlantuirea urm a nodului anterior (care
este indicat de q2) se modifica spre nodul urmator lui q1. Ciclul for de pe linia /* sterg 1 */ parcurge lista. Pointerii
q1 si q2 sunt initializati sa indice spre începutul listei din care se face stergerea. La fiecare reluare a ciclului, q2 ia vechea
valoare a lui q1, în timp ce q1 se muta spre nodul urmator. Conditia de reluare a ciclului for este ca lista sa nu fi fost parcursa
în întregime (q1 != NULL) si nodul sa nu fi fost gasit ( strcmp(q1->id, s) <0). Daca nodul a fost gasit (linia
/* sterg 2 */), atunci se verifica daca el este sau nu primul nod al listei. Daca nodul nu este chiar începutul listei
(linia /* sterg 3 */), atunci înlantuirea urm a nodului anterior celui indicat de q1 se modifica spre nodul urmator
celui indicat de q1. Daca însa nodul indicat de q1 este primul din lista, atunci variabila lista este schimbata spre al doilea nod.
Memoria ocupata de nodul eliminat este eliberata. Daca însa nu exista un nod care sa aiba identificatorul egal cu sirul indicat
de s, atunci sterg tipareste un mesaj de eroare.
introduc creeaza un nod nou care contine sirul indicat de parametrul char *s, iar câmpul lui de valoare este egal cu
parametrul int v. Nodul este inserat în lista indicata de parametrul nod *lista, astfel încât dupa introducere, lista
ramâne ordonata. introduc returneaza începutul listei modificate. Linia /* introduc 1 */ aloca memorie pentru noul
nod. Daca alocarea nu este posibila, se afiseaza un mesaj de eroare si executia programului este încheiata. În caz contrar,
câmpurile id si valoare sunt initializate corespunzator. Linia /* introduc 2 */ parcurge lista cautând pozitia în care
noul nod trebuie introdus. Pentru introducerea unui nod într-o lista ordonata sunt necesari doi pointeri; unul indica nodul în
fata caruia se face inserarea, iar al doilea pe cel anterior lui. Daca noul nod nu este inserat ca primul nod în lista (linia /*
introduc 4 */), atunci înlantuirea de la nodul anterior (indicat de q2) se schimba catre nodul nou, iar înlantuirea
noului nod se schimba spre nodul indicat de q1. Daca nodul devine primul în lista (linia /* introduc 5 */) atunci
înlantuirea lui urm este modificata spre nodul indicat de q1. În acest caz valoarea returnata de introduc (care este începutul
listei modificate) este tocmai pointerul spre nodul recent introdus. Daca în lista apare deja un nod al carui identificator este
egal cu cel care se doreste a fi introdus (linia /* introduc 3 */), atunci se afiseaza un mesaj de eroare si se pastreaza
lista nemodificata.

Llista liniara ramificata (arbore) (Figura 15.8)


 

Figura 15.8. Reprezentarea sub forma de arbore

In C o astfel de structura se poate defini prin:


typedef struct tip_lilira{
int cheie;
struct tip_lilira *plilira_stanga;
struct tip_lilira *plilira_dreapta;
} lilira;
1.6 Liste neliniare  Listele neliniare se definesc prin aceea ca elementele sunt la randul lor liste

 
Figura 15.9. Lista neliniara. Elementele listei sunt la randul lor liste

1.7. Exemplu listei dublu înlănţuite cu fişiere: Se consideră cunoscut că membrii unei familii au cîte o
sumă anumită de lei şi că fiecare alocă banii în mod diferit pentru a obţine un profit:
a) De depus banii la o bancă;
b) Se ocupă cu operaţii valutare;
c) A alocat banii în hîrtii de valoare.
Toate datele iniţiale să se scrie într-un fişier, apoi în dependenţă de activitatea fiecăruia să se analizeze
tranzacţiile în timp de o lună, jumătate de an si un an, evaluînd rezultatele şi de scris în fişiere aparte pentru
fiecare membru.
Listingul programului:
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<string.h>
struct tranz
{
char ou[20],in[20];
int sum;
time_t data;
};
struct persoana
{
char num[20];
char pren[20];
int sb,ov,hv,nt,nrper;
tranz t[20];
} t;
struct lista
{
persoana inf;
lista *urm,*prec;
} *p,*s;
void creare(int *n);
void afis_dir();
void afis_inv();
void exclud();
void includ();
void citp(persoana *p,int i);
void afsp(persoana *p,int i);
void sort();
void modif();
main()
{
int n,c;

for(;;)
{
printf("\n\
=========================================================\n\
|| MENIU ||\n\
|| 1:Citirea listei ||\n\
|| 2:Afisarea listei in ordine directa ||\n\
|| 3:Afisarea listei in ordine inversa ||\n\
|| 4:Excluderea unui element ||\n\
|| 5:Includerea unui element ||\n\
|| 6:Sortare ||\n\
|| 7:Modificare date persona ||\n\
|| ESC:Iesire din program ||\n\
=========================================================\n\ ");
c=getch();
switch(c)
{
case 49:creare(&n);break;
case 50:afis_dir();break;
case 51:afis_inv();break;
case 52:exclud();break;
case 53:includ();break;
case 54:sort();break;
case 55:modif();break;
case 27:exit(0);break;
default:printf("Optiune necunoscuta!");
}
}
return 0;
}
void creare(int *n)
{
struct lista *q,*r;
int i=0;
printf("\nDati numraul de persoane:\n");
scanf("%d",n);
p=new lista;
citp(&p->inf,i);
r=p;
q=r;
p->prec=NULL;
for(i=1;i<*n;i++)
{
r=new lista;
citp(&r->inf,i);
q->urm=r;
r->prec=q;
q=r;
}
r->urm=NULL;
s=r;
}
void afis_dir()
{
int k=0;
lista *r;
printf("\nPersoanele listei:\n");
r=p;
afsp(&r->inf,k++);
while(r->urm!=NULL)
{
r=r->urm;
afsp(&r->inf,k++);
}
}
void afis_inv()
{
int k=0;
lista *r;
printf("\nPersoanele listei:\n");
r=s;
afsp(&r->inf,k);
while(r->prec!=NULL)
{
r=r->prec;
afsp(&r->inf,k++);
}
}
void exclud()
{
int v,i=1,np;
struct lista *q,*r;
printf("\nDati numarul personalal persoanei care doriti sa excludeti:\n");
scanf("%d",&np);

r=p;
q=r;
while(r->urm!=NULL)
{
if(r->inf.nrper==np)
{
if(r==p)
{
p=r->urm;
r->urm->prec=NULL;
break;
}
else
{
q->urm=r->urm;
r->urm->prec=q;
}
break;
}

q=r;
r=r->urm;
}
if(r->urm==NULL)
{
q->urm=NULL;
s=q;
}

printf("Persoana a fost exclusa");


}
void includ()
{
int v,i,np;
struct lista *r=NULL,*q,*n;
printf("\nDati datele persoanei care doriti sa includeti\n");

n=new lista;
citp(&n->inf,0);

printf("\nDati numarul personal dupa care doriti sa includeti \n");


scanf("%d",&np);

r=p;
while(r->urm!=NULL)
{
if(r->inf.nrper==np)
{
n->urm=r->urm;
r->urm->prec=n;
n->prec=r;
r->urm=n;
break;
}

q=r;
r=r->urm;
}
if(r->urm==NULL)
{
n->urm=NULL;
n->prec=r;
r->urm=n;
s=n;
}
printf("Persoana a fost inclusa cu succes");
}
void sort()
{
int b=1;
struct persoana buf;
struct lista *r,*q;
r=p->urm;
q=p;
while(b)
{
b=0;
r=p->urm;
q=p;
while(r->urm!=NULL)
{
if(q->inf.nrper>r->inf.nrper)
{
buf=q->inf;
q->inf=r->inf;
r->inf=buf;
b=1;
}
q=r;
r=r->urm;
}
if(q->inf.nrper>r->inf.nrper)
{
buf=q->inf;
q->inf=r->inf;
r->inf=buf;
b=1;
}
}
}
void modif()
{
int k,i=0,b=0;
struct lista *r;
clrscr();
printf("\nDati numarul persoanl al persoanei care doriti sa modificati:\n");
scanf("%d",&k);
r=p;
while(r->urm!=NULL)
{
if(r->inf.nrper==k)
{
citp(&t,k);
r->inf=t;
b++;
break;
}
r=r->urm;
}
if(b)
printf("\n\n\nModificarile au fost salvate!\n");
else
if(r->inf.nrper==k)
{
citp(&t,k);
r->inf=t;
b++;
printf("\n\n\nModificarile au fost salvate!\n");
}
delay(1000);
}
void citp(persoana *p,int i)
{
char aux[1];
p->nt=0;
printf("\nDati datele persoanei %d",i+1);
printf("\nNume :\n");
gets(aux);
gets(p->num);
printf("Prenume :\n");
gets(p->pren);
printf("Dati numarul personal:\n");
scanf("%d",&p->nrper);
printf("Suma depusa in banca :\n");
scanf("%d",&p->sb);
printf("Suma investita in operatii valutare :\n");
scanf("%d",&p->ov);
printf("Suma alocata in hirtii de valoarea :\n");
scanf("%d",&p->hv);
}
void afsp(persoana *p,int i)
{
printf("\nPersoana %d\n",i+1);
printf("Nume : ");
puts(p->num);
printf("Prenume : ");
puts(p->pren);
printf("Numarul personal %d\n",p->nrper);
printf("Suma depusa in banca : %d\n",p->sb);
printf("Suma investita in operatii valutare :%d\n",p->ov);
printf("Suma alocata in hirtii de valoarea : %d\n",p->hv);
printf("Numaru de tranzactii efectuate: %d\n",p->nt);
}
3. Temele pentru verificarea cunoştinţelor şi lucrările de laborator
1. Analizaţi şi corectaţi programul de mai jos, care efectuează sortarea parţială a listei simplu lănţuite, în
consecutivitatea de valori s+t+1=l, K1'=K1; după sortare pointerul v indică la elementul K1. Apoi o transformaţi în listă
dublu lănţuită în organigramă şi program

listei simplu lănţuite


NOD *v;
float k1;
k1=prim->val;
r=prim;
while( r->urm!=NULL )
{
v=r->urm;
if (v->val; v=v->urm;
v->n=prim;
prim=v;
}
else r=v;
}
2. Alcătuiţi un program, care organizează o listă ciclică Wi (1<I) dintr-o consecutivitate de numere întregi B1, B2, ...,
Bn, din intervalul de la 1 pînă la 9999, sortate crescător.
Indicaţie: Se va considera lista Wi sortată crescător. Elementul Bi+1 se insertează în lista Wi astfel, ca lista indicată să
rămînă ordonată crescător. Pentru insertarea elementului nou în listă să se cerceteze trei cazuri:
- lista Wi este vidă,
- elementul se insertează la începutul listei,
- elementul se insertează la sfîrşitul listei.
3. Cercetaţi programul de mai jos şi apoi o transformaţil în listă dublu lănţuită
typedef struct str1
{ float val;
struct str1 *n;
} NOD;
void main()
{ NOD *arrange(void);
NOD *p;
p=arrange();
while(p!=NULL) {
printf("\n %f ",p->val);
p=p->n;
}
}
NOD *arrange() /* formarea listei sortate */
{
NOD *dl, *r, *p, *v; /* dl - începutul listei,
p,v – pointeri la două elemente vecine,*/
float in=1; /* r – fixează pointerul
la elementul current, care conţine valoarea in */
char *is;
dl=malloc(sizeof(NOD));
dl->val=0; /* primul element */
dl->n=r=malloc(sizeof(NOD));
r->val=10000; r->n=NULL; /* ultimul element */
while(1)
{ scanf(" %s",is);
if(* is=='q') break; in=atof(is); r=malloc(sizeof(NOD));
r->val=in; p=dl; v=p->n;
while(v->valn; }
r->n=v; p->n=r; }
return(dl);
}
4. Inversaţi consecutivitatea de simboluri introduse. De exemplu, s-a introdus consecutivitatea ABcEr-1,
consecutivitatea inversată va fi 1-rEcBA. Utilizaţi noţiunea de listă simplu lănţuită.
5. Cercetaţi operaţiile efectuate asupra stivei, descrise în programul de mai jos, elaborând organigramele detaliate pas
cu pas. Apoi o adăugaţi şi alte funcţii cu operaţiile tradiţionale:
typedef struct st /* declararea tipului STACK */
{ char ch;
struct st *ps; } STACK;
void main() { STACK *p,*q; char a; p=NULL;
do /* completarea stivei */
{ a=getch(); q=malloc(sizeof(STR1)); q->ps=p; p=q; q->ch=a;
} while(a!='.');
do /* tiparul stivei */
{ p=q->ps;free(q);q=p; printf("%c",p->ch); } while(p->ps!=NULL);
}
6. Scrieţi un program, în care numerele în baza 2 din lista dublu lănţuită dată se înlocuiesc cu cele în baza 10.
7. Scrieţi un program, care ar efectua următoarele operaţii. Se creează o listă dintr-un şir de numere reale, care se
termină cu zero, apoi din listă se şterg mai întîi elementele pozitive, apoi numerele impare. Sa se scrie o funcţie care
intoarce reuniunea a doua multimi de numere.
8. Scrieţi un program de unire a patru cozi în una nouă, în care vor fi stocate mai întîi numerele negative, zerourile,
apoi numerele pozitive.
9. Analizaţi, ce efectuează programul de mai jos, şi desenaţi organigrama. Apoi elaboraţi respectivele funcţii de
dezvoltarea programul şi pentru prelucrarea cozilor.
typedef struct nod
{
float val;
struct nod *n;
} NOD;
int index (NOD *x[100])
{
NOD *p;
int i,j=0;
float inp;
for (i=0; i<100; i++) x[i]=NULL;
scanf("%d",&inp);
while (inp!=0)
{
j++;
p=malloc(sizeof(NOD));
i=inp%100+1;
p->val=inp;
p->n=x[i];
x[i]=p;
scanf("%d",&inp);
}
return j;
}
Valoarea, returnată de funcţia index, va fi numărul de elemente cercetate din listă.
10. Analizaţi, ce efectuează programul de mai jos, şi desenaţi organigrama. Apoi elaboraţi respectivele funcţii de
dezvoltarea programul şi pentru prelucrarea cozilor.
#include <stdio.h>
#include <ctype.h>
#define INT 'i'
#define STR 's'
struct data { char tag; /* */
union { int i; char *s; } value; } a[10]; int counter = 0; /* */
void main(){ char word[128]; int i; char *malloc(unsigned); /* */
for(counter=0; counter < 10; counter++){ if( gets(word) == NULL ) break;
if( isdigit((unsigned char) *word)){ a[counter].value.i = atoi(word); a[counter].tag = INT; } else {
a[counter].value.s = malloc(strlen(word)+1); strcpy(a[counter].value.s, word); a[counter].tag = STR; } }
/* */ for(i=0; i < counter; i++) switch(a[i].tag){
case INT: printf("число %d\n", a[i].value.i); break;
case STR: printf("слово %s\n", a[i].value.s); free(a[i].value.s); break; } }
4.Probleme propuse spre rezolvare
1.Sa se implementeze un set de functii care sa realizeze urmatoarele operatii:
- adaug (t,id) - introduce identificatorul id în tabela de simboluri t;
- prezent (t,id) - returneaza 1 sau 0 dupa cum identificatorul id este sau nu prezent în tabela t;
- sterg (t,id) - elimina id din tabela t;

64
- reuniune (t1,t2,t) - t va contine identificatorii prezenti în t1 sau t2;
- intersectie (t1,t2,t) - t va contine identificatorii prezenti atât în t1 cât si in t2;
- diferenta (t1,t2,t) - t va contine identificatorii prezenti în t1 si absenti în t2;
- scriu(t) - tipareste în ordine alfabetica identificatorii din t.
Folosind functiile de mai sus sa se realizeze un program care citeste doua secvente consecutive de text, care se termina
fiecare cu caracterul `.`. Dupa citirea textelor se cere sa se tipareasca, în ordine alfabetica, identificatorii prezenti doar în
primul text, apoi cei doar în al doilea text. În continuare se vor tipari în ordine alfabetica identificatorii care sunt prezenti atât
în primul cât si în al doilea text. În final se afiseaza identificatorii care apar în cel putin unul din cele doua texte.

2. Se citeste o expresie în notatie poloneza, care contine ca operanzi constante reale, iar ca operatori +, -, * si /. Fiecare
expresie apare pe o linie separata. Dupa ce s-a citit expresia, ea este evaluata si rezultatul ei este tiparit. Programul citeste
expresii si le evalueaza pâna la citirea unui 0 singular pe o linie.
Observatii:
- Ca stiva se foloseste tipul abstract stiva cu valori reale, definit anterior.
- Se va defini functia getop (char *s), care citeste începând cu pozitia curenta a liniei de intrare si detecteaza
urmatorul operand sau operator. getop sare peste spatii si caracterele tab. Pentru un operand, functia returneaza constanta
NUMAR si, în parametrul s, adresa de început a sirului format din cifrele numarului. Pentru un operator, getop returneaza
caracterul citit, iar în parametrul s adresa de început a sirului format din acel caracter.
- Programul va fi împartit în trei fisiere: stiva.h - contine definitiile constantelor si a tipurilor de date folosite în program,
precum si declaratiile functiilor ce prelucreaza tipul abstract stiva. stiva.c - contine implementarea tipului abstract stiva.
main.c - contine programul principal precum si functia getop.
3. Sa se realizeze un program C ce tine evidenta personalului unei companii de dimensiuni mici (aproximativ 50 de
angajati). Informatia referitoare la angajati este pastrata într-un fisier si este folosita pentru initializarea bazei de date.
Fisierul contine linii de forma: nume varsta adresa numar_matricol functie
Câmpurile sunt separate printr-un spatiu si sunt siruri de caractere cu urmatoarele lungimi: nume - 19, vârsta - 2, adresa -
11, numar_matricol - 5 si functie - 5. Functiile care trebuie implementate sunt:
- IncarcBD(fis_bd) - încarca baza de date din fisierul fis_bd.
- SalvezBD(fis_bd) - salveaza baza de date din memorie în fisierul fis_bd.
- AfisezBD() - afiseaza baza de date din memorie.
- IntroducPersoana(n, v, a, nm, f) - introduce în baza de date o persoana si datele aferente ei.
- CautPersoana(n) - verifica prezenta unei persoane în baza de date.
- StergPersoana(n) - sterge din baza de date persoana cu numele n.
- ModificPersoana(n) - permite modificarea informatiilor legate de persoana cu numele n.
- RetVarsta(n) - returneaza vârsta persoanei cu numele n.
- RetAdresa(n) - returneaza adresa persoanei cu numele n.
- RetNrMatricol(n) - returneaza numarul matricol al persoanei cu numele n.
- RetFunctia(n) - returneaza functia persoanei cu numele n.
4. Scrieţi un program, care din lista L1, ce conţine numere întregi, să se extragă în lista L2 elementele cu numere pare cu
coordonatele lor din lista L1, iar în lista L3 elementele cu numere impare cu coordonatele lor din lista L1.
5. Scrieţi un program pentru a număra biţii de la dreapta spre stînga, pentru fiecare număr introdus într-o coadă şi le
salvează în una nouă. Sa se scrie o funcţie care sterge elementele din n in n.
6. Scrieţi un program, care roteşte fiecare element al listei dublu lănţuite n la dreapta cu b poziţii. Sa se scrie o funcţie care,
primind o lista, intoarce multimea tuturor perechilor din lista. Pentru [a,b,c,d] va produce [[a b],[a c],[a d],[b c],[b d],[c
d]].
7. Scrieţi un program de inversare a cei n biţi ai elementelor unei liste simplu lănţuită, care încep de la poziţia p, lăsîndu-i
pe ceilalţi neschimbaţi şi le salvează în una nouă. Sa se scrie o funcţie care transforma o lista intr-o multime
8. Scrieţi un program, care sortează şi converteşte literele mici în litere mari pentru cuvintele dintr-o stivă şi le salvează în
una nouă. Sa se intercaleze o literă in lista ordonata, astfel incat lista rezultata sa ramana ordonata
9. Scrieţi un program, care decide, dacă o valoare particulară x apare într-o listă dublu lănţuită v. Elementele lui v trebuie să
fie în ordine crescătoare. Se tipăreşte numărul elementului din listă (un număr între 0 si n-1), dacă x apare în v, şi –1,
dacă nu. Sa se scrie o funcţie care intoarce lista permutarilor unei liste date. Ex: pentru [1,2,3] furnizeaza L=[[1,2,3],
[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
10. Scrieţi un program, care converteşte elementul unei liste ciclice n în baza binară, într-un şir de caractere şi le salvează în
una nouă. Sa se scrie o funcţie care sterge din lista elementele din n in n.
11. Scrieţi un program, care converteşte întregii fără semn dintr-o listă simplu lănţuită n, într-o reprezentare binară şi le
salvează în una nouă. Sa se scrie o funcţie care intoarce lista submultimilor unei liste date. Ex: pentru [1,2,3] furnizeaza
[[],[1],[2],[3],[1,2],[1,3],[1,2,3]], nu neaparat in aceasta ordine.
12. Scrieţi un program, care converteşte fiecare element al listei dublu lănţuite într-un număr hexazecimal şi le salvează în
una nouă şi sa se inlocuiasca toate aparitiile unui element E cu elementele unei alte liste, L1. Exemplu:
inloc([1,2,1,3,1],1,[10,11],X) va produce X=[10,11,2,10,11,3,10,11].
13. Scrieţi un program de convertire a fiecărui element dintr-o stivă într-un şir de caractere şi invers, salvându-le în una nouă
şi sa se sorteze.
14. Scrieţi un program, care inversează fiecare element de tip şir de caractere dintr-o listă simplu lănţuită şi le salvează în
una nouă. Sa se sorteze lista cu pastrarea cuvintelor dubluri.
15. Scrieţi un program, care calculează cel mai mare divizor comun al elementelor dintr-o coadă şi le salvează într-o stivă
nouă. Sa se scrie o funcţie care adauga din n in n un element dat: ex: in [1,2,3,4,5] pt. n=2 si element=6 =>
L=[1,2,6,3,4,6,5]
16. Scrieţi un program, care compară două stive date şi formează încă o stivă cu cele ce coincid. Sa se scrie o funcţie care
intoarce o lista cu pozitiile pe care apare elementul maxim intr-o lista de numere intregi. Ex: pentru [1,-1,4,2,4] =>
L=[3,5]
17. Scrieţi un program de calculare a numărului de elemente dintr-o listă simplu lănţuită, care sînt mai mici ca valoarea
medie aritmetică a tuturor elementelor acestei liste. Sa se scrie o funcţie care testeaza egalitatea a doua multimi.
18. Scrieţi un program, care permite crearea unui arbore binar şi traversarea lui în inordine, preordine, postordine şi
elementele să fie salvate în fişiere.
19. Scrieţi un program, care dintr-o listă circulară de 100 de numere aleatoare să determine numărul maximal şi cel
minimal. Să se determine consecutivitatea de elemente, ce se află între numărul maximal şi cel minimal determinat. Sa
se scrie o funcţie care testeaza inegalitatea a doua multimi.
20. Scrieţi un program, care din trei liste simplu lănţuite să selecteze într-o listă nouă mai întîi numerele divizibile la 2, 4 şi
8, apoi numerele negative impare. Se cere de a-l diviza în 3 subliste
21. Scrieţi un program, care atribuie unei liste simplu lănţuite elementele altei liste în ordine inversă. Apoi de salvat în 2
fişiere, fiind ordonate descrescător, şi se unesc în unul, păstrîndu-se ordinea descrescătoare de sortare.
22. Scrieţi un program, care va tipări în ordine inversă subconsecutivitatea de numere dintre valoarea minimă şi maximă ale
unei liste simplu lănţuită şi se cere de a le diviza în 5 subliste.
23. Scrieţi un program, care formează o listă dublu lănţuită nouă din cea dată după următoarea legitate: elementele listei noi
se obţin din inversul cifrelor numărului din lista dată. Definiti o funcţie care sterge elementul minim din lista.
24. Se dă o consecutivitate de numere întregi, care se termină cu 0. Să se alcătuiască un program pentru introducerea acestei
consecutivităţi cu utilizarea metodei de stocare indexată consecutiv–lănţuită în aşa mod, ca numerele care coincid după
ultimele două cifre să fie într-o sublistă. Se va alege, în calitate de funcţie indexată, expresia de tipul: G(K)=K%100+1,
în calitate de listă de indici Х – tabloul de 100 de elemente. Pentru lista de indici se poate utiliza metoda stocării
indexate. Fie, de exemplu, lista B cu elementele: K1=(338, Z), K2=(145, A), K3=(136, H), K4=(214, I), K5=(146, C),
K6=(334, Y), K7=(333, P), K8=(127, G), K9=(310, O), K10=(322, X).
25. Pentru două liste date se cere de a le diviza în 8 subliste, adică X, în aşa mod, ca fiecare sublistă din B1, B2, ..., B7 să fie
elemente, care coincid în prima componentă cu primele două cifre. Lista Х, la rîndul său, va conţine indici la lista de
indici Y, astfel, încît în fiecare sublistă Y1, Y2, Y3 să se conţină elemente din X, la care în prima componentă coincid
primele două cifre. Dacă listele B1, B2, ..., B7 vor fi păstrate lănţuit, iar listele de indici X, Y indexat, astfel de păstrare a
listei B se va numi stocare indexată consecutiv– lănţuită. Să se alcătuiască un program de implementare a acestei
metode.
26. Scrieţi un program, care creează o listă circulară, a căror valori ale elementelor sînt cuprinse între 10 şi 1000. Să se
determine frecvenţa cu care a fost generat fiecare element al listei create.
27. Scrieţi un program, care determină cîte numere ale unei cozi de 100 de numere aleatoare sînt mai mari ca “vecinii” săi,
salvându-le într-un fişier.
28. Alcătuiţi un program, care ar efectua următoarele operaţii asupra listei dublu lănţuite:
- de determinare a primului element în listã;
- de cãutare a elementului dupã criteriul dat;
- de insertare a unui element nou, înainte sau dupã o componentã indicatã a listei;de eliminare a unui element din
listã. de sortare a componentelor listei.
29. Să se definească şi să se implementeze funcţiile pentru structura de date
typedef stuct { int lungime;
struct TIP_NOD *inceput, *curent, *sfarşit;
} LISTA;
având modelul din fig.3.1.

Fig.3.1.Modelul listei pentru problema 3.1


30. -Scrieti un program care determina numarul maximal si cel minimal intr-o lista circulara de 100 de numere aleatoare. Sa
se determine consecutivitatea de elemente ce se afla intre numerele maximal si minimal determinat.
31. Scrieţi un program cu funcţii, care compară două stive date şi calculează numărul de elemente din listă simplu lănţuită,
care sunt mai mici ca valoarea medie aritmetică a tuturor elementelor acestei liste..
Scrieţi un program, care atribuie unei liste simplu lănţuite elementele altei liste în ordine inversă.
Întrebări de control:
1. Definiţi noţiunea – tip abstract de date.
2. Cum se defineşte o structură?
3. Prin ce se deosebeşte structura de alte tipuri de date?
4. Cum se defineşte o variabilă de tip structură?
5. Cînd se utilizează punctul, când – săgeata în selectarea câmpurilor structurii?
6. Care sunt deosebirile între structura din limbajul C şi C++?
7. O structură poate oare să conţină altă structură?
8. O structură poate oare să conţină pointer spre ea însăşi?
9. Poate oare să fie creată dinamic o variabilă de tip structură?
10. Explicaţi prin organigramă cum are loc apelul prin referinţă și parcurgerea nodurilor.

Bibliografie
1."Limbajul de programare C". Brian W.Kernighan. Dennis M.Ritchie.
2."C. Tehnici de programare". Florin Munteanu, Gheroghe Musca, Florin Moraru.
3."Hrestomatia po programirovaniu na C v UNIX". Andrei Bogatiriev.
4."Programmirovanie na iaziche C". G.P.Cotlinschaia, O.I.Galinoschii.
4.1."Arta programarii calculatoarelor". Donald Cnut.
4.2. Tudor Sorin, Tehnici de programare, 1997
4.3. Razvan Andone, Ilie Gârbacea, Algoritmi fundamentali, O perspectiva C++, Ed. Libris, Cluj-Napoca, 1995
5.1.Tudor Sorin, TurboPascal: Algoritmi şi limbage de programare. Bucur.:L&S INFORMAT, 1996.
5.2.Cristea Valentin. Tehnici de programare. Ed.: Bucur., Teora,1993. /681.3; T29/
5.3.Ioan A. Leţia. Structura datelor şi Tehnici de programare. Ed.: Cluj-Napoca, 1986.
157p.Gorinştein A.M. Practica reşenia ingenernîx zadaci na EVM.-M.:1998.
245. Tudor Bălănescu. Corectudinea agoritmilor.Bucur.:Ed. Tehn.1995
246.MS. Trahtenbrot B.A. Algoritmî i vîcislitelnîe avtomatî. M.: S.radio.1974
5.13.Wirt N. Algoritmi i structuri dannîh .-M.: Mir.1989.
5.14.C.Hiuz C.Pfliger L.Rouz. Metodî programirovania. M.: MIR 1981
5.18.Pătrăşcoiu O. Elemente de grafuri şi combinatorică. Metode, algoritmi şi programe. Ed.:Buc., ALL,1994.
6. David R.Tribble, Notes About Programming Style, http://www.flash.net/~dtribble/src/sys/ style.htm, 1998
7. Dahl, O. J., Dijkstra E.W., Hoare C.A.R., Structured programming, Academic Press, 1972.
8. Informatica. Îndrumar metodic pentru lucrările de laborator. Marin Şt. Chişinău 2003. UTM
9. Liviu Negrescu. ”Limbajul de programare C şi C++” V.1-4. Buc. 1999
10. Knuth, D. E. - "Arta programarii calculatoarelor, vol. 1: Algoritmi fundamentali", Ed. Teora, 1999.
11. Knuth, D. E. - "Arta programarii calculatoarelor, vol. 2: Algoritmi seminumerici", Ed. Teora, 2000.
12. Knuth, D. E. - "Arta programarii calculatoarelor, vol. 3: Sortare si cautare", Ed. Teora, 2001.
13. Bacivarov, A.; Nastac, I. - "Limbajul C. Indrumar de laborator", Tipografia UPB, Bucuresti, 1997.
14. Bates, J; Tompkins, T. - "Utilizare C++", Ed. Teora 2001.
15. Andonie, R.; Gabarcea, I. - "Algoritmi fundamentali. O perspectiva C++", Ed. Libris, 1995.
16. Help din Turbo C ++IDE ,versiunea 3.0. 1999
17. CORMEN, T. - LEISERSON, CH. - RIVEST, R. : Introducere in algoritmi, Editura Computer Libris. Agora, Cluj-
Napoca, 2000.
18. Conspectele la PC 2009 şi SDA-2010

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