Sunteți pe pagina 1din 43

LISTE

Pentru rezolvarea unei probleme, în multe cazuri se pot folosi mai mulţi algoritmi, din care trebuie
ales cel mai eficient:
- la nivel conceptual, alegerea algoritmului se face în funcţie de complexitatea algoritmilor
descoperiţi pentru rezolvarea problemei, fiind preferat algoritmul care are complexitate mai mică –
algoritmul care consumă cel mai puţin resursele calculatorului;
- la nivel logic, alegerea se face în funcţie de modul în care pot fi implementaţi algoritmii în limbajul
de programare ales, fiind preferat algoritmul care are o implementare mai uşoară
– algoritmul care consumă cel mai puţin resursele programatorului.
Nu întotdeauna cei doi algoritmi coincid, programatorul urmând să decidă criteriul folosit pentru
alegerea algoritmului. Un rol important în implementarea eficientă a unui algoritm îl joacă modul în
care au fost organizate datele în colecţia de date.
Pe lângă criteriile pentru clasificarea datelor studiate, mai există şi următoarele criterii:

Structurile statice au un mare dezavantaj, deoarece alocarea zonei de memorie se face la compilarea
programului (în funcţie de modul în care a fost declarată structura), iar în timpul execuţiei
programului pot să apară următoarele cazuri:
- spaţiul alocat structurii este insuficient;
-spaţiul alocat structurii este mult mai mare decât este necesar.
Acest dezavantaj este eliminat prin folosirea structurilor dinamice, la care dimensiunea zonei de
memorie alocate nu mai este fixă, deoarece alocarea memoriei se face în timpul execuţiei
programului, în funcţie de numărul de componente ale structurii la acel moment
Exemplificam modul în care identificaţi problemele în care puteţi folosi structura de date de tip listă
pentru a le rezolva.
Enunţul problemelor pentru care trebuie aleasă o structură de date şi conceput

1
algoritmul pentru prelucrarea lor:

1. Într-o bibliotecă, există o colecţie de cărţi organizate în ordinea alfabetică a autorilor. Un cititor
poate împrumuta o carte (se extrage o carte din colecţie) sau poate înapoia o carte(se inserează o carte
în colecţie). Toate aceste operaţii trebuie executate astfel încât să se păstreze organizarea în ordine
alfabetică a autorilor.

2. La o benzinărie s-a format o coadă de aşteptare. Maşinile sunt servite în ordinea venirii: prima
maşină sosită este servită, iar ultima maşină venită se aşază la sfârşitul cozii. Toate aceste operaţii
trebuie executate astfel încât să se păstreze ordinea în care au sosit maşinile şi s-au aşezat la coadă.

3. Într-o stivă de cărţi, volumele sunt aşezate în ordinea alfabetică a titlurilor. Trebuie extrasă o carte
din stivă fără a deranja modul în care sunt ordonate cărţile în stivă.

La nivel conceptual, toate aceste colecţii de date reprezintă un şir de date de acelaşi tip, care trebuie
prelucrate prin inserarea şi extragerea de elemente, păstrându-se o anumită ordine de aranjare a
elementelor. Dacă la nivel logic s-ar alege soluţia de a grupa aceste elemente într-o structură de date
de tip vector, algoritmii de prelucrare – care sunt algoritmi de actualizare a vectorilor – vor necesita
multe deplasări de elemente care consumă timp de prelucrare.
Caracteristicile operaţiilor de prelucrare a vectorilor sunt:
- Vectorul este o structură de date care are lungimea fizică fixă, iar implementarea lui la nivel fizic
este un proces prin care se face conversia locaţiei conceptuale a unui element, în locaţia reală, din
memoria internă.
-Orice operaţie de adăugare sau extragere a unui element din colecţie modifică lungimea logică a
vectorului.
-Pentru a insera un element în colecţie, trebuie deplasate toate elementele spre
dreapta, începând cu poziţia inserării.
-Pentru a extrage un element din colecţie, trebuie deplasate toate elementele spre stânga, de la sfârşit,
până în poziţia din care se extrage.

În cazul structurilor care trebuie să-şi păstreze în timpul exploatării ordonarea după un anumit
criteriu, mecanismul vectorilor este greoi. În aceste cazuri, se poate alege ca soluţie de implementare
a structurii de date lista – care nu este o structură fizică de organizare a datelor, ci o structură logică,
ce degrevează programatorul de ordonarea după indice a structurii, impusă de vectori. Listele sunt
structuri de date care, spre deosebire de vectori, au marele avantaj că permit şi implementare prin
alocarea dinamică a memoriei.

Lista este o structură de date logică, liniară, cu date omogene, în care fiecare
element are un succesor şi un predecesor, exceptând primul element, care nu are
decât succesor, şi ultimul element, care nu are decât predecesor.
Lista este o colectie de elemente intre care este specificata cel putin o relatie de ordine.
Daca relatia de ordine "<" este totala se poate construi o secventializare a
elementelor listei astfel incit daca a si b sunt doua elemente consecutive in secventa atunci a<b si nu
exista nici un alt element c in lista asfel incit a<b<c.
Aparent, vectorul şi lista par să fie aceeaşi structură de date: sunt structuri omogene şi secvenţiale
(liniare) în care fiecare element are un succesor şi un predecesor, cu excepţia primului şi a ultimului
element, care au numai succesor, respectiv numai predecesor. Între cele două structuri există însă
următoarele deosebiri:
Criteriul Tabloul de memorie Lista
Dispunerea Sunt structuri contigue. Pot fi structuri dispersate.
elementelor Predecesorul, elementul şi Predecesorul,
în memorie succesorul se găsesc în locaţii elementul şi succesorul nu
contigue prin mecanismul de sunt în locaţii de
implementare a structurii de memorie contigue.

2
date la Programatorul trebuie să
nivel fizic. definească mecanismul prin
care elementele
structurii vor fi legate unele
de altele pentru
a putea fi regăsite în
memorie.
Implementarea Sunt structuri implicite. Fiind Sunt structuri explicite. Fiind
în limbaj o o structură
structură fizică, este logică, trebuie aleasă o
implementată în metodă de
limbajul de programare. Nu implementare folosind
necesită structurile fizice
informaţii suplimentare existente. Necesită informaţii
pentru localizarea suplimentare
elementelor structurii în pentru localizarea
memoria elementelor structurii în
internă, deoarece memoria internă, deoarece
mecanismul mecanismul
prin care este implementată prin care este implementată
fizic fizic nu asigură
asigură identificarea identificarea elementelor.
elementelor
Alocarea Sunt structuri statice. Sunt structuri statice sau
memoriei dinamice, în
funcţie de implementarea
aleasă.

Se defineşte lungimea listei (n) ca fiind numărul de elemente ale listei. Lista vidă este lista care are
lungimea 0 (nu are nici un element). Elementele listei se mai numesc şi noduri.

Implementarea listelor în limbajul C++

În funcţie de modul în care se alocă memoria internă, se pot folosi metodele:

În funcţie de modul în care sunt aranjate elementele în listă, se pot folosi metodele:

3
Nodurile listei sunt stocate într-un bloc contiguu de locaţii de memorie cu adrese
consecutive. De exemplu, dacă avem o listă formată din cuvinte de maximum 4
caractere, acestea vor fi scrise într-un bloc contiguu de locaţii de 5 octeţi.

Această implementare este cea mai simplă – şi se face folosind un vector de şiruri de caractere:
typedef char nod[5];
nod lista[100];
S-a declarat o listă care poate avea maxim 100 de noduri. Informaţia dintr-un nod este de tip şir de
caractere cu lungimea de 5 caractere. Acest tip de listă se mai numeşte şi listă contiguă.
În implementarea prin alocare secvenţială, algoritmii pentru operaţiile de prelucrare a listei sunt cei
folosiţi pentru operaţiile de prelucrare a vectorilor şi necesită foarte multe operaţii de deplasare a
elementelor în vector.

Implementarea prin alocare înlănţuită

Nodurile listei nu mai sunt stocate succesiv în memorie.


Exemplu – O listă este formată din 5 cuvinte (noduri), fiecare cuvânt având maxim 4 caractere.
Nodurile listei se exploatează în ordine alfabetică:
Lista = {"alfa", "beta", "gama", "teta", "zeta"}
Această implementare se poate face atât static, cât şi dinamic, între cele două implementări existând
următoarele diferenţe:
- în implementarea statică, nodurilor listei li se alocă un bloc contiguu de locaţii de memorie (zona de
memorie alocată vectorului);

în implementarea dinamică, nodurile listei ocupă locaţii dispersate din memorie


(a căror adresă poate fi păstrată cu ajutorul pointerilor).

4
Deoarece în ambele cazuri de implementare nodurile nu sunt aranjate succesiv, ci aleatoriu, în
memorie, trebuie implementat un mecanism prin care să se precizeze ordinea reală a acestor noduri
(ordinea în care se înlănţuiesc în listă). Aceasta înseamnă că:
-trebuie cunoscut locul din care începe lista (lanţul de noduri), adică poziţia primului nod din listă
(nodul prim) – în exemplu, nodul "alfa";
-trebuie cunoscut locul în care se termină lista, adică poziţia ultimului nod din listă (nodul ultim) – în
exemplu, nodul "zeta";
-pentru fiecare nod din listă, cu excepţia ultimului nod, trebuie cunoscut nodul care este succesorul
lui – de exemplu, pentru nodul "gama" trebuie cunoscut că
succesorul său este nodul "teta".
Metoda folosită pentru implementare este ca un nod al listei să conţină două tipuri de informaţii:
informaţia propriu-zisă şi informaţia de legătură (informaţia prin care se identifică nodul care
urmează în structură, adică
adresa acestui nod). Informaţia pentru legătură va fi:
- în cazul implementării statice – indicele din vector al succesorului;
- în cazul implementării dinamice – adresa succesorului exprimată printr-un pointer.
În ambele cazuri, informaţia de legătură memorată în ultimul nod este constanta NULL sau 0 (care
semnifică faptul că ultimul nod nu se leagă de nimic).

Implementarea statică pentru alocarea înlănţuită se face folosind un vector de


înregistrări:

typedef unsigned adresa;


struct nod
{char sir[5]; //informaţia propriu-zisă

5
adresa urm;}; //informaţia pentru legătură
nod lista[100];

În variabila urm se va memora adresa succesorului (indicele din vector al succesorului).


În general, putem spune că, pentru implementarea statică a listei şi pentru prelucrarea cu algoritmii
specifici unei liste, trebuie declarate următoarele tipuri de date şi variabile:

const unsigned NMAX=100;


typedef unsigned adresa;
struct nod
{<tip 1> <info 11>, <info 12>, ..., <info 1n>;
<tip 2> <info 21>, <info 22>, ..., <info 2n>;
.............................................
<tip m> <info m1>, <info m2>, ..., <info mn>;
adresa urm;};
nod lista[NMAX+1];

Câmpurile <info ij> sunt câmpurile cu informaţii, iar câmpul urm este câmpul care conţine
informaţia de legătură (adresa succesorului). Se defineşte constanta NMAX pentru a putea folosi în
expresii lungimea fizică a vectorului. Deoarece indicele 0 se foloseşte pentru adresa NULL, în
vectorul care implementează lista, elementul cu indicele 0 nu se foloseşte.
În liste, algoritmii de inserare şi eliminare a unor noduri din structură se simplifică foarte mult:
- Inserarea unui nod constă în alocarea zonei de memorie în care se scrie nodul şi legarea lui la
structură (stabilirea legăturii cu predecesorul şi cu succesorul lui).
-Eliminarea unui nod constă în ruperea nodului din structură, prin legarea predecesorului său cu
succesorul lui, şi eliberarea zonei de memorie pe care a ocupat-o.
Implementarea structurilor de date cu ajutorul listelor are următoarele avantaje:
- alocarea dinamică a memoriei – care o gestionează mult mai eficient;
- algoritmii de prelucrare – care sunt mult mai eficienţi (se execută mult mai puţine operaţii pentru
inserarea şi ştergerea unui element).

Metoda de Avantajul alocării dinamice a Avantajul algoritmilor


implementare memoriei de prelucrare

statică secvenţială Nu Nu
statică înlănţuită nu da
dinamică înlănţuită da da

Se observă că implementarea statică secvenţială nu aduce niciunul dintre avantajele listelor, fiind o
implementare în care lista este de fapt un vector.
Algoritmii pentru prelucrarea unei liste înlănţuite sunt aceiaşi, atât în cazul implementării statice, cât
şi în cazul implementării dinamice. Deoarece informaţiile pe care le aveţi nu vă permit încă
abordarea alocării dinamice a memoriei, pentru prezentarea algoritmilor se va folosi implementarea
statică.
Aşadar, lista este o structură logică de date, parcursă liniar, care are două extremităţi (început şi
sfârşit), în care fiecărui element i se asociază o informaţie suplimentară referitoare la locul
elementului următor, din punct de vedere logic

6
Clasificarea listelor

Tema Reprezentaţi grafic o listă simplu înlănţuită prin predecesori şi o listă


circulară dublu înlănţuită.

7
Algoritmi pentru prelucrarea listelor generale

Operaţiile care se pot executa asupra listelor sunt:


-iniţializarea listei – se creează lista vidă;
-rearea listei – se adaugă repetat elemente la listă, pornind de la lista vidă;
-inserarea unui element în listă – la început, la sfârşit, în interior;
-eliminarea unui element din listă – la început, la sfârşit, în interior;
-parcurgerea listei – se vizitează elementele listei pentru a obţine informaţii;
-căutarea în listă a unui element care îndeplineşte anumite condiţii;
-concatenarea a două liste;
- divizarea în două liste.
Pentru a simplifica algoritmii pentru prelucrarea listei, vom considera că un nod al listei conţine
numai un câmp cu informaţie şi câmpul pentru realizarea legăturii.

const unsigned NMAX=100;


typedef unsigned adresa;
struct nod
{int info; adresa urm;};
nod lista[NMAX+1];
adresa prim,ultim,p;
unsigned nr_el,liber[NMAX];
int n;

Veţi mai folosi următoarele date şi structuri de date:


-Variabilele prim şi ultim reprezintă adresele (indicele poziţiei din vector) în care sunt memorate
primul şi respectiv ultimul nod; ele vă ajută să identificaţi extremităţile listei.
-Variabila p este adresa (indicele) unui nod curent din listă (este folosită pentru parcurgerea listei).
-Variabila n conţine valoarea care se atribuie câmpului cu informaţii.
-Variabila nr_el este lungimea listei (numărul de noduri ale listei).
-Vectorul liber este o hartă a nodurilor libere: liber[p] are valoarea 1 dacă nodul cu adresa (indicele) p
este liber şi valoarea 0 dacă nodul cu adresa p este ocupat. Prin acest vector, se implementează
mecanismul de alocare şi de eliberare a zonei de memorie necesare unui nod. Atunci când se
inserează un nod, mai întâi trebuie să i se aloce memorie internă. Căutând în vectorul liber, se va
putea identifica prima locaţie liberă din vectorul lista. Atunci când se şterge un element, se eliberează
memoria alocată în vectorul lista, prin actualizarea informaţiei din vectorul liber.
Există următoarele cazuri speciale de liste:
- Lista vidă: atât nodul prim cât şi nodul ultim nu există, şi indicii folosiţi pentru
adresarea lor au valoarea NULL:
prim = ultim = NULL;
- Lista cu un singur nod: nodul prim este şi nodul ultim şi nu mai este legat de nici un alt element:

lista[prim].info = val;
lista[prim].urm = NULL;
ultim = prim;

Există următoarele stări ale listei, care trebuie cunoscute în algoritmii de prelucrare:
-Lista vidă. Această stare trebuie cunoscută, atunci când se elimină noduri din listă, deoarece în lista
vidă nu există noduri care să fie eliminate. Pentru testarea unei liste dacă este vidă, se poate
implementa funcţia operand este vida(), care va furniza valoarea 1 („adevărat“), dacă lista este vidă,
şi valoarea 0 („fals“) dacă lista nu este vidă.

int este_vida(adresa prim)


{return prim==NULL;}

8
-Lista plină. Această stare poate să apară din cauza implementării statice a listei. Ea trebuie
cunoscută atunci când se adaugă noduri la listă, deoarece se poate depăşi lungimea fizică a
vectorului. În lista plină nu mai există locaţii libere în vector, în care să se adauge noduri. Pentru
testarea unei liste dacă este vidă, se poate implementa funcţia operand este_plina(), care va furniza
valoarea 1 („adevărat“), dacă lista este plină, şi valoarea 0 („fals“) dacă lista nu este plină.

int este_plina()
{return nr_el == NMAX;}

Iniţializarea listei
Prin acest algoritm se creează lista vidă, în care atât nodul prim cât şi nodul ultim nu există, şi vor
avea valoarea NULL. În vectorul liber, toate elementele au valoarea 1 (toate elementele din vectorul
listei sunt libere).
Implementarea algoritmului. Se foloseşte funcţia procedurală init() ai cărei parametri prim şi ultim
de tip adresa se transmit prin referinţă, deoarece sunt parametri de ieşire.

void init(adresa &prim, adresa &ultim)


{ultim=prim=NULL; nr_el=0;
for (adresa p=1;p<=NMAX; p++) liber[p]=1;}

Alocarea memoriei
Pentru a putea adăuga un nod la listă – trebuie mai întâi să i se aloce o zonă de
memorie. Prin acest algoritm se găseşte prima poziţie liberă în vectorul lista.
Implementarea algoritmului. Se foloseşte funcţia operand aloc_mem() care furnizează adresa
(indicele) primei poziţii libere în vector – variabila locală p de tip adresă.

adresa aloc_mem()
{adresa p;
for (adresa p=1; !liber[p]; p++);
liber[p]=0; nr_el++;
return p;}

Crearea listei
Deoarece în algoritmii de prelucrare trebuie să se cunoască adresa primului nod, este importantă
adăugarea primului nod la lista vidă. Paşii algoritmului de creare a unei liste sunt:
PAS1. Se adaugă primul nod la listă (nodul prim).
PAS2. Cât timp mai există informaţie execută: se adaugă un nod la listă.

Adăugarea primului nod la listă


Prin acest algoritm se adaugă primul nod în listă. Spaţiul alocat acestui nod este prima poziţie din
vector (poziţia cu indicele 0). În vectorul liber, poziţia 0 se va înregistra ca ocupată prin atribuirea
valorii 0. Nodul prim şi nodul ultim au valoarea 1. Nodul prim, fiind unicul nod, nu mai este legat de
alt nod.

În lista cu un singur nod, adresa de legătură a nodului prim are valoarea NULL şi atât nodul prim
cât şi nodul ultim au aceeaşi adresă. Paşii executaţi în acest algoritm sunt:
PAS1. Se cere alocarea de memorie pentru nodul prim.
PAS2. Se scrie informaţia în nodul prim.
PAS3. Adresei de legătură a nodului prim i se atribuie valoarea NULL.
PAS4. Nodului ultim i se atribuie adresa nodului prim.

9
Implementarea algoritmului. Se foloseşte funcţia procedurală adauga primul nod() ai cărei parametri
se transmit astfel: prim şi ultim de tip adresa prin referinţă, deoarece sunt parametri de ieşire, iar n,
care conţine informaţia utilă, prin valoare, deoarece este parametru de intrare.

void adaug_primul_nod(adresa &prim, adresa &ultim, int n)


{prim=aloc_mem(); lista[prim].info=n; lista[prim].urm=NULL; ultim=prim;}

Adăugarea unui nod la listă


Pentru adăugarea unui nod la listă (prin scriere la adresa p care i s-a alocat), în funcţie de cerinţele
problemei, se poate folosi unul dintre algoritmii următori:
1. adăugarea în faţa primului nod;
2. adăugarea după ultimul nod;
3. adăugarea într-o poziţie în interiorul listei.
În toate cele trei variante adăugarea se poate face numai dacă lista nu este plină.

Adăugare în faţa primului nod


Paşii executaţi în acest algoritm sunt:
PAS1. Se cere alocarea de memorie pentru nodul p.
PAS2. Se scrie informaţia în nodul p.
PAS3. Dacă lista este vidă, nodul p adăugat va fi şi nodul ultim.
PAS4. Nodul p se leagă de nodul prim.
PAS5. Nodul p inserat devine nodul prim.

Implementarea algoritmului. Se foloseşte funcţia procedurală adauga_inainte_ prim() ai cărei


parametri se transmit astfel: prim şi ultim de tip adresa prin referinţă, deoarece sunt parametri de
intrare-ieşire şi n – care conţine informaţia utilă – prin valoare, deoarece este parametru de intrare.

void adaug_inainte_prim(adresa &prim, adresa &ultim, int n)


{adresa p; p=aloc_mem();
lista[p].info=n; lista[p].urm=prim; //nodul p se leagă de nodul prim
if (este vidă(prim)) ultim=p;
prim=p;} //nodul p devine nodul prim

Adăugare după ultimul nod


Paşii executaţi în acest algoritm sunt:
PAS1. Se cere alocarea de memorie pentru nodul p.
PAS2. Se scrie informaţia în nodul p.
PAS3. Nodul p este nod terminal (nu se leagă de nimic – adresa de legătură este NULL).
PAS4. Nodul ultim se leagă de nodul p adăugat.
PAS5. Dacă lista este vidă, nodul p inserat va fi şi nodul prim.
PAS6. Nodul p adăugat devine nodul ultim.

10
Implementarea algoritmului. Se foloseşte funcţia procedurală
adauga_dupa_ ultim() ai cărei parametri se transmit astfel: prim şi ultim de tip adresa prin referinţă,
deoarece sunt parametri de intrare-ieşire şi n – care conţine informaţia utilă – prin valoare, deoarece
este parametru de intrare.

void adaug_dupa_ultim(adresa &prim, adresa &ultim, int n)


{adresa p; p=aloc mem();
lista[p].info=n; lista[p].urm=NULL; //nodul p nu se leagă de nimic
lista[ultim].urm=p; //nodul ultim se leagă de nodul p
if (este_vidă) prim=p;
ultim=p;} //nodul p devine nodul ultim

Adăugarea în interiorul listei


se poate face în două moduri:
a. după nodul din poziţia q;
b. înainte de nodul din poziţia q.

Adăugarea în interiorul listei după nodul din poziţia q


Paşii algoritmului sunt:
PAS1. Se cere alocarea de memorie pentru nodul p.
PAS2. Se scrie informaţia în nodul p.
PAS3. Nodul p se leagă de succesorul nodului q.
PAS4. Nodul q se leagă de nodul p care se adaugă.
PAS5. Dacă nodul q a fost ultimul nod, nodul p adăugat devine nodul ultim

Implementarea algoritmului. Se foloseşte funcţia procedurală adauga_dupa() ai cărei parametri se


transmit astfel: q (adresa nodului după care se face adăugarea) prin valoare, deoarece este parametru
de intrare, ultim (adresa ultimului nod), prin referinţă, deoarece este parametru de intrare-ieşire, şi n
– care conţine informaţia utilă – prin valoare, deoarece este parametru de intrare.

void adauga_dupa(adresa q, adresa &ultim, int n)


{adresa p; p=aloc_mem();
lista[p].info=n; lista[p].urm=lista[q].urm; lista[q].urm=p;
if (lista[p].urm=NULL) ultim=p;}

Adăugarea în interiorul listei înainte de nodul din poziţia q


Pentru a insera nodul p înaintea nodului q, trebuie să legăm predecesorul nodului q de nodul p. Dar,
predecesorul unui nod nu este cunoscut. Inserarea unui nod în listă înseamnă, de fapt, inserarea în

11
listă a informaţiei pe care o conţine, între alte două informaţii, şi anume: informaţia din predecesorul
nodului q trebuie să fie anterioară ei, iar informaţia din nodul q trebuie să o urmeze. Astfel, în listă
nu se va insera nodul p înainte de nodul q, ci după el, interschimbând apoi informaţiile între cele
două noduri. Paşii executaţi în acest algoritm sunt:
PAS1. Se cere alocarea de memorie pentru nodul p.
PAS2. Se copiază informaţia din nodul q în nodul p.
PAS3. Se scrie în nodul q informaţia care trebuie adăugată la listă.
PAS4. Nodul p se leagă de succesorul nodului q.
PAS5. Nodul q se leagă de nodul p adăugat.
PAS6. Dacă nodul q a fost ultimul nod, nodul p adăugat devine nodul ultim.

Implementarea algoritmului. Se foloseşte funcţia procedurală adauga_in_fata() ai cărei parametri se


transmit astfel: q (adresa nodului înaintea căruia se face adăugarea), prin valoare, deoarece este
parametru de intrare, ultim (adresa ultimului nod), prin referinţă, deoarece este parametru de
intrare-ieşire, şi n – care conţine informaţia utilă – prin valoare, deoarece este parametru de intrare.

void adauga_in_fata(adresa q, adresa &ultim, int n)


{adresa p; p=aloc mem();
lista[p].info=lista[q].info; lista[q].info=n;
lista[p].urm=lista[q].urm; lista[q].urm=p;
if (lista[p].urm)==NULL) ultim=p;}

Parcurgerea listei
Prin acest algoritm se vizitează fiecare nod al listei, pornind de la primul nod, până la ultimul nod,
în ordinea de înlănţuire a nodurilor – furnizată de câmpul urm din nodul vizitat. Lista se parcurge
pentru a prelucra informaţia stocată în noduri.
Implementarea algoritmului. Se foloseşte funcţia procedurală parcurge() al cărei parametru prim de
tip adresa se transmite prin valoare, deoarece este parametru de intrare.

void parcuge(adresa prim)


{for (adresa p=prim; p!=NULL; p=lista[p].urm)
//se prelucreează lista[p].info;}

Căutarea unui nod în listă


Într-o listă se poate căuta:
1. Nodul care îndeplineşte o anumită condiţie, pe care o notăm cu conditie şi care
este exprimată printr-o expresie logică ce conţine cel puţin un câmp cu informaţie din nod; valoarea
ei este dependentă de informaţia stocată în nod. Algoritmul este: se parcurge lista începând de la
primul nod, în ordinea de înlănţuire a nodurilor, până segăseşte nodul care îndeplineşte condiţia sau
până s-a ajuns la sfârşitul listei.

12
2. Nodul care se găseşte într-o anumită poziţie în listă, pe care o notăm cu poz.
Algoritmul este: se parcurge lista începând de la primul nod, în ordinea de înlănţuire a nodurilor,
până când s-au parcurs poz noduri sau până s-a ajuns la sfârşitul listei.
Implementarea algoritmului. Se foloseşte o funcţie operand cu tipul adresa care va returna adresa
nodului găsit. În cazul în care nodul căutat nu există în listă, funcţia va returna valoarea NULL. În
ambele variante:
-parametrul funcţiei este prim de tip adresa şi se transmite prin valoare, deoarece este parametru de
intrare;
-variabila locală p de tip adresa se foloseşte pentru parcurgerea listei – este iniţializată cu adresa
primului nod;
-adresa nodului găsit se memorează în variabila p care va fi returnată de funcţie.
Varianta 1

adresa caut(adresa prim)


{for (adresa p=prim; p!=NULL && !conditie; p=lista[p].urm);
return p;}

Varianta 2 – Variabila locală nr de tip int se foloseşte pentru a număra poziţiile


parcurse – este iniţializată cu valoarea 1.

adresa caut(adresa prim, int poz)


{adresa p=prim; int nr=1;
if (poz>nr_el) return NULL;
else for (;nr<poz; p=lista[p].urm,nr++);
return p;}

Căutarea succesorului şi a predecesorului unui nod


Într-o listă se pot căuta predecesorul şi succesorul unui nod care are adresa p. Valoarea lui p poate să
fie furnizată de un algoritm de căutare în listă. Presupunem că lista are cel puţin trei noduri şi că
există nodul căutat. Algoritmul este: se iniţializează predecesorul (pred) cu adresa primului nod din
listă şi succesorul (succ) cu adresa celui de al treilea nod dinlistă şi se parcurge lista, în ordinea de
înlănţuire a nodurilor, de la al doilea nod, până la nodul căutat, folosind variabila q de tip adresa,
actualizând de fiecare dată valorile adreselor succesorului şi predecesorului.
Implementarea algoritmului. Se foloseşte o funcţie procedurală pred_succ() ai cărei parametri se
transmit astfel: prim (adresa primului nod) şi p (adresa nodului căruia i se caută succesorul şi
predecesorul), prin valoare, deoarece sunt parametri de intrare, iar pred (adresa predecesorului) şi
succ (adresa succesorului) prin referinţă, deoarece sunt parametri de ieşire.

void pred_succ(adresa prim, adresa p, adresa &pred, adresa &succ)


{adresa q=lista[prim].urm;
pred=prim; succ=lista[lista[prim].urm].urm;
while (q!=p) {pred=q; q=lista[q].urm; succ=lista[q].urm;}

Observaţie. Prin găsirea predecesorului unui nod, se poate simplifica algoritmul pentru ştergerea
ultimului nod.

Eliminarea unui nod din listă


Eliminarea unui nod se face numai dacă lista nu este vidă. După eliminarea din listă a nodului de la
adresa p, se va elibera zona de memorie ocupată de nod.
Se foloseşte funcţia procedurală elibereaza() al cărei parametru p de tip adresa se transmite prin
valoare (este parametru de intrare).

void elibereaza(adresa p)
{liber[p]=1; nr_el--;}

13
Pentru eliminarea unui nod din listă, în funcţie de cerinţele problemei, se poate folosi unul dintre
algoritmii următori:
1. eliminarea primului nod;
2. eliminarea ultimului nod;
3. eliminarea unui nod din interiorul listei.

Eliminarea primului nod


Paşii executaţi în acest algoritm sunt:
PAS1. Se salvează adresa nodului prim în variabila de tip adresă q.
PAS2. Succesorul nodului prim devine nodul prim.
PAS3. Se eliberează zona de memorie de la adresa memorată în variabila q

Implementarea algoritmului. Se foloseşte funcţia procedurală elimina_prim() al cărei parametru prim


de tip adresa se transmite prin referinţă, deoarece este parametru de intrare-ieşire.

void elimina_prim(adresa &prim)


{adresa q=prim; prim=lista[prim].urm; elibereaza(q);}

Eliminarea ultimului nod


Paşii executaţi în acest algoritm sunt:
PAS1. Se salvează adresa nodului ultim în variabila q de tip adresa.
PAS2. Se caută predecesorul ultimului nod, prin parcurgerea listei începând de la primul nod, până
la predecesorul nodului terminal (nodul care nu se leagă de nimic – adresa de legătură are valoarea
NULL).
PAS3. Predecesorul nodului ultim devine nod terminal (adresa de legătură este NULL).
PAS4. Predecesorul nodului ultim devine nodul ultim.
PAS5. Se eliberează zona de memorie de la adresa memorată în variabila q..

Implementarea algoritmului. Se foloseşte funcţia procedurală elimina_ultim() al cărei parametru


ultim de tip nod se transmite prin referinţă deoarece este parametru de intrare-ieşire.

void elimina ultim(adresa prim, adresa &ultim)


{adresa p,q=ultim;
for(p=prim;lista[lista[p].urm].urm!=NULL;p=lista[p].urm);
//lista[p].urm este adresa ultimului nod
//şi nodul p este predecesorul ultimului nod
lista[p].urm=NULL; ultim=p; elibereaza(q);}

14
Eliminarea unui nod din interiorul listei
Pentru a elimina nodul p aflat în interiorul listei, trebuie să legăm predecesorul nodului p de
succesorul lui. Dar, predecesorul unui nod nu este cunoscut, ci numai succesorul lui.
Eliminarea unui nod din listă înseamnă de fapt eliminarea din listă a informaţiei pe care o conţine.
Astfel, din listă nu se va elimina nodul p, ci succesorul său (care este cunoscut), după ce informaţia
din el a fost copiată în nodul p. Paşii executaţi în acest algoritm sunt:
PAS1. Se salvează adresa succesorului nodului p în variabila q de tip adresa.
PAS2. Se copiază în nodul p toată informaţia din succesorul lui (informaţia propriu-zisă şi informaţia
de legătură).
PAS3. Se cere eliberarea zonei de memorie de la adresa memorată în variabila q.
PAS4. Dacă succesorul nodului p era nodul ultim, atunci nodul p devine nodul ultim.
Implementarea algoritmului. Se foloseşte funcţia procedurală elimina() ai cărei parametri sunt de tip
nod: p (adresa nodului care se elimină), care se transmite prin valoare, deoarece este parametru de
intrare, şi ultim, care se transmite prin referinţă, deoarece este parametru de intrare-ieşire.

void elimina(adresa p,adresa &ultim)


{adresa q=lista[p].urm; //nodul q este succesorul nodului p
lista[p].info=lista[q].info; lista[p].urm=lista[q].urm;
elibereaza(q);
if (lista[p].urm)==NULL) ultim=p;}

Prelucrarea listelor

În cazul listelor ordonate, dacă informaţia din nodul listei este o dată elementară, cheia de ordonare
va fi data elementară. Dacă informaţia din nodul listei este o înregistrare, cheia de ordonare va fi
unul dintre câmpurile înregistrării.
În general, rezolvarea problemelor în care organizaţi datele sub
formă de liste presupune folosirea algoritmilor prezentaţi,astfel:
1. Se creează lista prin folosirea următorilor algoritmi:
- Se creează lista vidă – algoritmul pentru crearea listei vide.
- Se adaugă câte un nod la listă. Poziţia în care se adaugă nodul depinde de cerinţele problemei. Dacă
lista nu este ordonată, adăugarea se poate face la sfârşitul sau la începutul listei – algoritmul pentru
adăugare la începutul sau la sfârşitul listei. Dacă lista este ordonată, se parcurge mai întâi lista pentru
a găsi poziţia de inserare – algoritmul pentru căutarea unui nod în listă – după care se inserează noul
nod în poziţia găsită – algoritmul pentru adăugare în interiorul listei.
2. Se întreţine lista prin folosirea următorilor algoritmi:
-Se adaugă noi noduri la listă. Ca şi la crearea listei, în funcţie de cerinţele problemei se va folosi unul
dintre algoritmii de adăugare.
-Se elimină noduri din listă. Mai întâi se parcurge lista pentru a găsi nodul care se elimină –
algoritmul pentru căutarea unui nod în listă – după care se elimină
nodul din poziţia găsită folosind unul dintre algoritmii pentru eliminare – în
funcţie de poziţia în care a fost găsit nodul.
-Se modifică informaţia dintr-un nod al listei. Mai întâi se parcurge lista pentru a găsi nodul în care
se va modifica informaţia – algoritmul pentru căutarea unui
nod în listă. Dacă lista nu este ordonată sau dacă informaţia care se modifică nu

15
face parte dintr-un câmp cheie dintr-o listă ordonată, se modifică valoarea câmpului respectiv. Dacă
lista este ordonată şi, prin modificarea valorii câmpului, se poate distruge această ordonare, se
modifică valoarea câmpului, se salvează nodul listei într-o variabilă intermediară, se elimină din
vechea poziţie – algoritmul pentru eliminarea unui nod din listă –, se parcurge lista pentru a găsi
noua poziţie – algoritmul pentru căutarea unui nod în listă – şi se inserează nodul în poziţia găsită –
algoritmul pentru adăugarea unui nod la listă.
3. Se obţin informaţii din listă. Se parcurge lista – algoritmul de parcurgere a listei şi se vizitează
fiecare nod al listei pentru a extrage din el informaţiile necesare.
În exempul următor se urmărește exemplificarea modului în care, pentru rezolvarea problemei,
folosiţi algoritmii de prelucrare a listelor simplu înlănţuite şi implementarea lor cu ajutorul
subprogramelor.
Exemplul 1:
Se citesc, într-o listă, cifrele unui număr care are o valoare foarte mare. Citirea se va face până când
numărul citit nu este o cifră. Să se afişeze inversul acestui număr.
Nodurile listei nu sunt ordonate conform unui criteriu. Cifrele numărului se vor citi de la tastatură şi
se vor scrie în listă prin adăugare în faţa primului nod. Pentru a afişa inversul numărului se va
parcurge lista de la primul nod până la ultimul, şi se va afişa informaţia din fiecare nod. Deoarece în
aceşti algoritmi nu este necesară informaţia despre adresa ultimului nod, din subprograme au fost
eliminate prelucrările care se referă la acesta.
Folosind tehnica top-down, problema poate fi împărţită în subprobleme, iar algoritmul de rezolvare
a unei subprobleme este implementat cu ajutorul unui subprogram.
P1 Se creează lista vidă – subprogramul init().
P2 Se creează lista cu cifre – subprogramul creare()– astfel: se citeşte primul număr şi cât timp
numărul citit este cifră şi lista nu este plină – subprogramul este_plina()
– execută: se adaugă un nod cu cifra respectivă înaintea primului nod din listă –
subprogramul adaug_inainte_prim() – şi se citeşte un număr.
P3 Dacă lista nu este vidă – subprogramul este_vida()–, atunci se parcurge lista de la primul nod
până la sfârşitul ei şi se afişează informaţia din fiecare nod – subprogramul afiseaza().
Structura modulelor este următoarea:

Specificaţiile subprogramelor sunt următoarele:


1. Modulul 1 (init):
_ Date de intrare: niciuna;
_ Date de ieşire: prim – adresa primului nod al listei;
_ Funcţia modulului: crearea listei vide.
2. Modulul 2 (este plina):
_ Date de intrare: niciuna;
_ Date de ieşire: valoarea de adevărat sau fals a stării de listă plină;
_ Funcţia modulului: obţinerea informaţiei despre starea de listă plină.
3. Modulul 3 (este vida):

16
_ Date de intrare: prim – adresa primului nod al listei;
_ Date de ieşire: valoarea de adevărat sau fals a stării de listă vidă;
_ Funcţia modulului: obţinerea informaţiei despre starea de listă vidă.
4. Modulul 4(aloc mem):
_ Date de intrare: niciuna;
_ Date de ieşire: p – adresa alocată pentru nod;
_ Funcţia modulului: alocarea unei zone de memorie (un element din vector) pentru un nod nou.
5. Modulul 5 (creare):
_ Date de intrare: prim – adresa primului nod al listei;
_ Date de ieşire: prim – adresa primului nod al listei;
_ Funcţia modulului: se creează lista prin citirea cifrelor de la tastatură.
6. Modulul 6 (adaug inainte prim):
_ Date de intrare: prim şi n – adresa primului nod al listei şi cifra din nodul care se adaugă;
_ Date de ieşire: prim – adresa primului nod al listei;
_ Funcţia modulului: se adaugă la listă un nod cu informaţie înaintea primului nod.
7. Modulul 7 (afişează):
_ Date de intrare: prim – adresa primului nod al listei;
_ Date de ieşire: niciuna;
_ Funcţia modulului: vizitarea nodurilor listei de la primul până la ultimul, pentru a
afişa informaţia din nod.
Programul obţinut este:

#include <iostream.h>
const unsigned NMAX=100;
typedef unsigned adresa;
struct nod {unsigned cifra;
adresa urm;};
nod lista[NMAX+1];
int nr_el,liber[NMAX];

int este_plina()
{return nr_el == NMAX;}

int este vida(adresa prim)


{return prim==NULL;}

void init(adresa &prim)


{prim=NULL; nr_el=0;
for (adresa p=1;p<=NMAX;p++) liber[p]=1;}

adresa aloc_mem()
{adresa p;
for (p=1; !liber[p]; p++);
liber[p]=0; nr_el++;
return p;}

void adaug_inainte_prim(adresa &prim, int n)


{adresa p=aloc_mem();
lista[p].cifra=n; lista[p].urm=prim; prim=p;}

void creare(adresa &prim)


{unsigned cif;
cout<<"Scrieti cifrele numarului:"<<endl;
cout<<"cifra= "; cin>>cif;
while (cif<=9 && !este_plina()) //se adauga noduri la lista
{adaug_inainte_prim(prim,cif);
cout<<"cifra= "; cin>>cif;}}

17
void afiseaza(adresa prim)
{for (adresa p=prim; p!=NULL; p=lista[p].urm) cout<<lista[p].cifra;}

void main()
{adresa prim; init(prim); creare(prim);
if (!este vida(prim)) {cout<<"Numarul invers este: "; afiseaza(prim);}
else cout<<"Nu exista numar";}

TEMA
1. Refaceţi programul folosind prototipurile funcţiilor.
2. Scrieţi următoarele programe în care numerele din nodurile listei se
citesc dintr-un fişier text în care sunt scrise pe acelaşi rând, separate
prin spaţiu:
a. Se creează o listă cu aceste numere şi se afişează în ordinea inversă citirii din fişier numai numerele
pare.
b. Se creează o listă în care ordinea de acces este inversă celei în care sunt citite numerele. Să se
elimine din listă cel mai mic număr şi cel mai mare număr.

Exemplul 2:
Se introduc de la tastatură mai multe numere naturale, până când se introduce numărul 0. Să se
afişeze mai întâi numerele pare şi apoi numerele impare.
Vom folosi pentru memorarea numerelor o listă înlănţuită, în care vom scrie mai întâi numerele pare
şi apoi numerele impare. Numerele pare vor fi scrise în listă prin adăugarea în faţa primului nod, iar
numerele impare prin adăugare după ultimul nod. Problema poate fi descompusă în subprobleme,
care vor fi rezolvate cu ajutorul subprogramelor.
P1 Se creează lista vidă – subprogramul init().
P2 Se creează lista cu numere – subprogramul creare()– astfel: se citeşte primul
număr şi se adaugă primul nod la istă – subprogramul adaug_prim() –, se citeşte
următorul număr şi cât timp numărul citit este diferit de zero şi lista nu este plină –subprogramul
este_plina() – execută: dacă numărul este par, atunci se adaugă înaintea primului nod –
subprogramul adaug_inainte_prim(); altfel, se adaugă după ultimul nod – subprogramul
adaug_dupa_ultim() – şi se citeşte un nou număr.
P3 Dacă lista nu este vidă – subprogramul este_vida()–, atunci se parcurge lista de la primul nod
până la sfârşitul ei şi se afişează informaţia din fiecare nod – subprogramul afiseaza()

Programul obţinut este:


#include <iostream.h>
const unsigned NMAX=100;
typedef unsigned adresa;

struct nod {unsigned nr;


adresa urm;};

nod lista[NMAX+1];
int nr_el,liber[NMAX];

int este_plina()
{return nr_el == NMAX;}

int este vida(adresa prim)


{return prim==NULL;}

void init()
{nr_el=0;
for (adresa p=1;p<=NMAX;p++) liber[p]=1;}

18
adresa aloc mem()
{adresa p;
for (p=1; !liber[p]; p++);
liber[p]=0; nr el++;
return p;}

void adaug prim(adresa &prim, adresa &ultim, int n)


{prim=ultim=aloc mem();
lista[prim].nr=n; lista[prim].urm=NULL;}

void adaug inainte prim(adresa &prim, int n)


{adresa p=aloc mem();
lista[p].nr=n; lista[p].urm=prim; prim=p;}

void adaug dupa_ultim(adresa &ultim, int n)


{adresa p=aloc mem();
lista[p].nr=n; lista[p].urm=NULL; lista[ultim].urm=p; ultim=p;}

void creare(adresa &prim)


{adresa ultim; unsigned n;
cout<<"Scrieti numerele:"<<endl; cout<<"numar= "; cin>>n;
if (n!=0)
{adaug_prim(prim,ultim,n);
cout<<"numar= "; cin>>n;
while (n!=0 && !este_plina())
{if (n%2==0) adaug inainte prim(prim,n);
else adaug dupa ultim(ultim,n);
cout<<"numar= "; cin>>n;}}}

void afisare(adresa p)
{while (p!=NULL) {cout<<lista[p].nr<<" "; p=lista[p].urm;}

void main()
{adresa prim; init(); creare(prim);
if (!este vida(prim)) {cout<<"Numerele sunt: ";
afisare(prim);}
else cout<<"Nu exista numere";}

TEMA
Refaceţi programul folosind prototipurile funcţiilor. Explicaţi modul în care a fost descompusă
problema în subprobleme şi precizaţi specificatiile fiecarui modul

Exemplul 3:
Se citesc de la tastatură mai multe numere, până se citeşte
valoarea 0. Numerele se scriu într-o listă, ordonate crescător. Să se afişeze numerele din nodurile
listei.
Numerele care trebuie memorate în nodurile listei se citesc de la tastatură, până se citeşte valoarea 0
(are semnificaţia că nu mai există informaţie de adăugat). Nodurile acestei liste fiind ordonate,
inserarea unui nou număr citit de la tastatură trebuie să se facă într-o poziţie care să păstreze ordinea
elementelor. Acest algoritm se numeşte algoritmul de sortare prin inserare. Se porneşte de la lista
care conţine primul nod cu informaţie. Paşii executaţi în acest algoritm sunt:
PAS1. Se adaugă primul nod cu informaţie la listă.
PAS2. Cât timp mai există informaţie de adăugat execută
PAS3. Se caută nodul înaintea căruia trebuie adăugat noul nod.
PAS4. Dacă s-a ajuns la sfârşitul listei, atunci se adaugă noul nod ca ultim nod din
listă; altfel, se adaugă noul nod în faţa nodului găsit. Se revine la Pasul 2.

19
Implementarea algoritmului.
#include <iostream.h>
const unsigned NMAX=100;
typedef unsigned adresa;
struct nod {int info;
adresa urm;};
nod lista[NMAX+1];
int nr_el,liber[NMAX];

int este_plina()
{return nr_el==NMAX;}

int este vida(adresa prim)


{return prim==NULL;}

void init()
{nr_el=0;
for (adresa p=1;p<=NMAX; p++) liber[p]=1;}
adresa aloc_mem()
{adresa p; for (p=1; !liber[p]; p++);
liber[p]=0; nr_el++;
return p;}

void adaug_prim(adresa &prim,int n)


{prim=aloc_mem();
lista[prim].info=n; lista[prim].urm=NULL;}

void adauga_ultim(adresa q,int n)


{adresa p=aloc_mem();
lista[p].info=n; lista[q].urm=p; lista[p].urm=NULL;}
adresa cauta(adresa prim,int n)
{adresa p=prim;
while(lista[p].info<n && lista[p].urm!=NULL) p=lista[p].urm;
return p;}

void adauga_in_fata(adresa q,int n)


{adresa p=aloc_mem();
lista[p].urm=lista[q].urm; lista[q].urm=p;
lista[p].info=lista[q].info; lista[q].info=n;}

void creare(adresa &prim)


{adresa q; int n; init(); cout<<"numar "; cin>>n;
adaug_prim(prim,n); //Se adaugă primul nod
cout<<"numar "; cin>>n;
while(n!=0 && !este_plina()
{q=cauta(prim,n);
//Se caută nodul q înaintea căruia trebuie adăugat nodul p
if (lista[q].info<n) // Dacă s-a ajuns la sfârşitul listei
adauga_ultim(q,n); //atunci nodul p se adaugă ca ultim nod
else adauga_in_fata(q,n); //altfel nodul p se adaugă în faţa nodului q
cout<<"numar "; cin>>n;}}

void afiseaza(adresa prim)


{for (adresa p=prim; p!=NULL; p=lista[p].urm)
cout<<lista[p].info<<" ";}

void main()
{adresa prim; creare(prim);
afiseaza(prim);

20
getch();
clrscr();}
if (!este_vida) afiseaza(prim);//se parcurge lista pentru afişare
else cout<<"Nu exista elemente";}

TEMA
Refaceţi programul de la problema 2 astfel încât numerele din listă să fie
afişate astfel: mai întâi numerele pare, ordonate crescător, şi apoi
numerele impare, ordonate descrescator

Exemplul 4
Calcularea rezistenţei echivalente. Să se calculeze rezistenţa echivalentă între punctele A şi B pentru
circuitul electric din figură.

Pentru calcularea rezistenţei echivalente se porneşte de la ultimele rezistenţe – Rn şi Rn-1 – care sunt
legate în serie. Se calculează rezistenţa lor echivalentă Re1, care va fi legată în paralel cu rezistenţa
Rn-2. Prin calcularea rezistenţei echivalente a celor două rezistenţe legate în paralel Re1 şi Rn-2, se va
obţine o nouă rezistenţă echivalentă Re2 care este legată în serie cu rezistenţa Rn-3. Calcularea
rezistenţei echivalente a circuitului electric este un proces repetitiv în care alternează calcularea unei
rezistenţe echivalente a două rezistenţe legate în serie cu calcularea unei rezistenţe echivalente a
două rezistenţe legate
în paralel. Pentru a şti care dintre variantele de calcul se alege, se foloseşte variabila s, care are
valoarea 1 dacă rezistenţele sunt legate în serie şi valoarea 0 dacă sunt legate în paralel. Valorile
pentru rezistenţe se citesc dintr-un fişier text în care sunt memorate pe acelaşi rând, separate prin
spaţiu. Se creează o listă simplu înlănţuită în care ordinea de acces este inversă faţă de cea în care
sunt citite numerele din fişier. Pentru calcularea rezistenţei echivalente, lista se parcurge de la primul
nod până la ultimul nod.

#include<fstream.h>
const unsigned NMAX=100;
typedef unsigned adresa;
struct nod {float info;
adresa urm;};
nod lista[NMAX+1];
int nr_el,liber[NMAX];
fstream f("lista_r.txt",ios::in);

int este_plina()
{return nr_el == NMAX;}

void init(adresa &prim)


{prim=NULL; nr_el=0;
for (adresa p=1;p<=NMAX; p++) liber[p]=1;}

adresa aloc_mem()
{adresa p;
for (p=1; !liber[p]; p++);
liber[p]=0; nr_el++;
return p;}

21
void adaug_inainte_prim(adresa &prim, float x)
{adresa p; p=aloc_mem();
lista[p].info=x; lista[p].urm=prim; prim=p;}

void creare(adresa &prim)


{float x; init(prim);
while (f>>x && !este_plina()) adaug_inainte_prim(prim,x);}
float R(adresa prim)
{int s=1; float r=lista[prim].info; adresa p=lista[prim].urm;
while (p!=NULL)
if (s) {r+=lista[p].info; s=0; p=lista[p].urm;}
else {r=(r*lista[p].info)/(r+lista[p].info);
s=1; p=lista[p].urm;}
return r;}

void main()
{adresa prim; creare(prim); f.close();
cout<<"Rezistenta echivalenta= "<<R(prim);}

Lista liniara simplu inlantuita

O lista liniara simplu inlantuita este caracterizata prin faptul ca relatia de ordine definita pe
multimea elementelor este unica si totala.
Ordinea elementelor pentru o astfel de lista este specificata explicit printr-un cimp de informatie care
este parte componenta a fiecarui element si indica elementul urmator, conform cu relatia de ordine
definita pe multimea elementelor listei.
Deci fiecare element de lista simplu inlantuita are urmatoarea structura:

Pe baza informatiei de inlantuire (pastrata in cimpul leg) trebuie sa poata fi identificat urmatorul
element din lista.

Lista liniara simplu inlantuita alocata static

Daca implementarea structurii de lista inlantuita se face prin tablouri, aceasta este o lista inlantuita
alocata static sau simplu o lista inlantuita statica.
Consideram urmatoarele declaratii:

struct Element {
char data[100];
int leg;
};
Element V[8];

Pentru elementele vectorului V exista o ordine naturala data de aranjarea in memorie a elemetelor
sale: V[0], V[1], ... V[7]. Vom reperezenta memoria ocupata de vectorul V astfel incit fiecare element
sa fie reprezentat vertical, in felul urmator:

22
Completind cimpul leg pentru fiecare element al vectorului putem obtine o lista liniara simplu
inlantuita. Valoarea cimpului leg va fi indexul in vector al urmatorului element din lista.
Vectorul V:

Pe baza inlantuirii stabilita de valorile din figura de mai sus se obtine ordinea:
V[3], V[6], V[7], V[0], V[1], V[2], V[4], V[5].
Obs. Ultimul element din lista are in cimpul leg valoarea -1. Este necesar sa cunoastem care este
primul element din inlantuire, pentru aceasta retinem intr-o variabila:

int cap; //indexul primului element.


cap=3

Indiferent de modul cum se materializeaza informatiile de legatura pentru a reprezenta o lista


inlantuita vom folosi urmatoarea reprezentare:

Sageata care porneste din cimpul leg arata faptul ca valoarea memorata aici indica elementul la care
duce sageata.

Parcurgerea listei
Consideram: cap - contine adresa primului element din lista.
O parcurgere inseamna prelucrarea pe rind a tuturor elementelor listei, in ordinea in care acestea
apar in lista. Vom avea o variabila pointer crt care va indica pe rind fiecare element al listei:
Parcurgerea in ordine a elemntelor listei se face in felul urmator:

int crt;
.................
crt = cap;
while (crt!=-1) {
// Prelucreaza V[crt]

crt = V[crt].leg;
}
Un caz special apare atunci cind dorim sa facem o parcurgere care sa se opreasca in fata unui element
care sa indeplineasca o conditie (ca in cazul cind inseram un element intr-o pozitie data printr-o
conditie, sau stergem un elemen care indeplineste o conditie).
Presupunem ca lista are cel putin un element.

23
p = cap;
while (V[p].leg!=-1 && !conditie(V[p].leg))
p = V[p]leg;

Bucla while se poate opri pe condifia "V[p].leg==-1", ceea ce insemna ca nici un element din lista nu
indeplineste conditia iar variabila p indica ultimul element din lista, sau pe conditia
"conditie(V[p]leg)", ceea ce insemna ca p va contine adresa elementului din fata primului element
care indeplineste conditia.

Inserarea unui element in lista


Consideram: cap - contine adresa primului element din lista;
p - contine adresa unui element izolat care dorim sa fie inserat in lista.
Inserarea in fata

Fiecare sageata nou creeata insemna o atribuire: se atribuie variabilei in care sageata nou creata isi
are originea, valoarea luata dintr-o variabila in care se afla originea unei sageti cu aceeasi destinatie.
In cazul nostru avem atribuirile (fiecare atribuire corespunde sagetii cu acelasi numar din figura):

(1) V[p].leg = cap;


(2) cap = p;
Sa detaliem: Prima atribuire
V[p].leg = cap;
leaga elementul de inserat de restul listei. In urma acestei atribuiri, cap si p.leg vor indica ambii
inceputul listei initiale
A doua atribuire:
cap = p;
pune in variabila cap adresa elementului inserat in fata listei.

Observatie:
Daca variabila cap este initial -1, (ceea ce inseamna inserarea intr-o lista vida)
atribuirile de mai sus functioneaza corect rezultind o lista cu un singur element.
V[p].leg = cap; // de fapt V[p].leg = -1;
cap = p;

Inserarea in interior sau la sfirsit


Varibila q va indica elementul dupa care se face inserarea.

24
(1) V[p].leg = V[q].leg;
(2) V[q].leg = p;
Observatii:
Atunci cind q indica ultimul element dintr-o lista, atribuirile de mai sus functioneaza corect si
adauga elementul indicat de p la sfirsitul listei.
Nu se poate face inserarea in fata unui element dat (prin q) fara a parcurge lista de la capat.
Stergerea unui element din lista
Consideram: cap - contine adresa primului element din lista.
Stergerea la inceputul listei
Prin operatia de stergere se intelege scoaterea unui element din inlantuire.

(1) p = cap;
(2) cap = V[cap].leg;

Stergerea in interior sau la sfirsit


Varibila q va indica elementul din fata celui care va fi sters.

25
(1) p = V[q].leg;
(2) V[q].leg =V[p].leg; // sau V[q].leg =V[V[q].leg].leg;

Observatii:
Atunci cind q indica penultimul element dintr-o lista, atribuirile de mai sus
functioneaza corect si sterg ultimul element din lista.
Nu se poate face stergerea elementului indicat de q fara parcurgerea listei de la capat.

Exemplul 5
a) Sa se creeze o lista liniara simplu inlantuita care sa memoreze urmatoarele informatii despre elevii
unei clase formata din n elevi:
- numele (sir de maxim 20 de caractere)
- prenumele (sir de maxim 20 de caractere)
- 3 note intr-un vector cu 3 componente reale
b) Sa se afiseze numele, prenumele si media fiecarui elev.
c) Sa se scrie o functie care calculeaza si returneaza media clasei.

#include <iostream>
#include<fstream>

ifstream f("elevi.in");
struct elev{char nume[20];
char prenume[20];
float note[3];
float media;
};
struct nod{elev e;
int leg;
};
nod V[100];
int nrelv=0;

void adaugf(int &cap,elev x)


{nrelv++;
V[nrelv].e=x;
V[nrelv].leg=cap;
cap=nrelv;
}

void citire(int &cap)


{int n;elev x;

26
f>>n;
for(int i=1;i<=n;i++)
{f>>x.nume>>x.prenume>>x.note[0]>>x.note[1]>>x.note[2];
x.media=(x.note[0]+x.note[1]+x.note[2])/3;
adaugf(cap,x);
}
}

void afis(int cap)


{int crt=cap;
while(crt!=-1){cout<<V[crt].e.nume<<" "<<V[crt].e.prenume<<"
"<<V[crt].e.media<<endl;
crt=V[crt].leg; }
}

float mediagen(int cap)


{float s=0;
int n=0;
while(cap!=-1){s+=V[cap].e.media;n++;cap=V[cap].leg;}
return s/n;
}

int main()
{ int cap=-1;
citire(cap);
cout<<cap;
afis(cap);
cout<<mediagen(cap);
return 0;
}

Exemplul 6
Sa se scrie o functie care primeste ca parametru adresa primului nod al unei LLSI cu cel putin 3
noduri si sterge primul si ultimul nod al listei.Lista este memorata intr-un vector V. Elementele listei
sunt date de inretgistrarea

struct elev
{ char nume[30];
int note[3];
int leg;}

void sterg (int &cap)


{ int crt;
cap=V[cap].leg ;// stergerea primului element
crt=cap;
while(V[V[crt].leg].leg!=-1)
crt=V[crt].leg;
V[crt].leg=-1; // stergerea ultimului element al listei
}

Liste liniare dublu inlantuite

O lista liniara dublu inlantuita este caracterizata prin faptul ca pe multimea elementelor sunt definite
doua relatii de ordine totala inverse una alteia (inainte si inapoi). Rezulta doua secventializari ale
listei Ordinea elementelor pentru o astfel de lista este specificata prin doua cimpuri de informatie
care sunt parte componenta a fiecarui element si indica elementul urmator si respectiv elementul
precedent, conform cu relatiile de ordine definite pe multimea elementelor listei.

27
Deci fiecare element de lista dublu inlantuita are urmatoarea structura:

Pe baza informatiilor de inlantuire (pastrate in cimpurile urm si prec) trebuie sa poata fi identificate
urmatorul element din lista respectiv elementul precedent.

Liste liniare dublu inlantuite alocate static


Daca implementarea structurii de lista inlantuita se face prin tablouri, aceasta este o lista inlantuita
alocata static sau simplu o lista inlantuita statica.
Consideram urmatoarele declaratii:

struct Element {
char data[50];
int urm,prec;
};
Element V[8];

Pentru elementele vectorului V exista o ordine naturala data de aranjarea in memorie a elemetelor
sale: V[0], V[1], ... V[7]. Vom reperezenta memoria ocupata de vectorul V astfel incit fiecare element
sa fie reprezentat vertical, in felul urmator:

Completand cimpurile urm si prec pentru fiecare element al vectorului se obtine o lista liniara dublu
inlantuita. Valoarea cimpului urm va fi indexul in vector al urmatorului element din lista iar valoarea
cimpului prec va fi indexul in vector al elementului precedent din lista.
Vectorul V:

28
Pe baza inlantuirii stabilita de valorile din figura de mai sus se obtin:
secventa inainte: V[3], V[6], V[7], V[0], V[1], V[2], V[4], V[5] si
secventa inapoi : V[5], V[4], V[2], V[1], V[0], V[7], V[6], V[3].

Observatie.
Ultimul element din lista are in cimpul de legatura valoarea -1.
Este necesar sa cunoastem care este primul element in cele doua inlantuiri. Pentru aceasta retinem in
doua variabile:
int cap1,cap2;
indexul primului element din fiecare inlantuire:
cap1=3.
cap2=5.
Parcurgerea in ordinea inainte a elemntelor listei se face in felul urmator:
int crt;
.................
crt = cap1;
while (crt!=-1) {
// Prelucreaza V[crt]
crt = V[crt].urm;
}

Parcurgerea in ordinea inapoi a elementelor listei se face in felul


urmator:
int crt;
.................
crt = cap2;
while (crt!=-1) {
//Prelucreaza V[crt] (
crt = V[crt].prec;
}

Operatii in liste liniare dublu inlantuite

O lista liniara dublu inlantuiata poate fi privita ca o lista liniara simplu inlantuiata daca se face
abstractie de una din inlantuiri. Astfel operatiile cu astfel de liste au implementari similare celor
corespunzatoare de la listele liniare simplu inlantuite. Operatiile de insertie sau stergere la sfarsitul
listei pot fi simplificate utilizand cealalta inlantuire pentru a accesa ultimul element (ultimul element
intr-o inlantuire este primul element in cealalta inlantuire). Operatiile de insertie sau stergere in
interiorul listei pot fi optimizate alegand inlantuirea cea mai potrivita situatiei respective.

Exemplul 7
a) Sa se creeze o LLDI care sa memoreze numere intregi citite dintr-un fisier text.
b) Sa se scrie o functie care primeste ca parametru adresa primului nod al listei si o afiseaza in
ambele sensuri.
c) Sa se scrie o functie care primeste ca parametru adresa p a unui nod si un numar natural x si
adauga dupa nodul indicat de p un nod care sa contina valoarea x.
d) Sa se scrie o functie care primeste ca parametru adresa p a unui nod si sterge nodul indicat de p.
e) Folosind functiile de punctele b), c) si d) sa se adauge dupa nodul al doilea un nod cu informatia 7,
sa se stearga al treilea nod si apoi primul nod si sa se afiseze lista in ambele sensuri dupa fiecare
dintre aceste operatii.

#include <iostream>
#include<fstream>

29
using namespace std;

ifstream f("date.in");

struct nodd{int info;


int prec,urm;
};
nodd V[100];
int nrelem=0;

void adaugs(int &prim,int x)


{int crt;
crt=prim;
nodd nou;
nou.info=x;
nrelem++;
if(crt!=-1)
{
while(V[crt].urm!=-1) crt=V[crt].urm;
nou.prec=crt;
nou.urm=-1;
V[crt].urm=nrelem;

}
else {prim=nrelem;
nou.urm=-1;
nou.prec=-1;
}

V[nrelem]=nou;

void creare(int &prim)


{int x;
while(f>>x) adaugs(prim,x);
}

void afis(int prim)


{int p=prim;
while(V[p].urm!=-1) {cout<<V[p].info<<" ";
p=V[p].urm;

}
cout<<V[p].info<<" ";
cout<<endl;
while(V[p].prec!=-1) {cout<<V[p].info<<" ";
p=V[p].prec;
}
cout<<V[p].info<<" ";
cout<<endl;
}

void inseraredupa(int p,int x)


{int q;
nrelem++;
nodd nou;
nou.info=x;
q=V[p].urm;

30
nou.prec=p;
V[p].urm=nrelem;
nou.urm=q;
if(q!=-1) V[q].prec=nrelem;
V[nrelem]=nou;
}

void sterg(int p, int &prim)


{int q,r;
q=V[p].prec;
r=V[p].urm;
if(r!=-1) V[r].prec=q;
if(q!=-1) V[q].urm=r;
if(prim==p)
prim=r;

int main()
{ int prim=-1;
creare(prim);
afis(prim);
inseraredupa(V[prim].urm,7);
afis(prim);
sterg(V[V[prim].urm].urm,prim);
afis(prim);
sterg(prim,prim);
afis(prim);
f.close();
}

Exemplul 8
Se considera o lista liniara dublu inlantuita. Sa se scrie o functie care primeste ca parametru adresa
primului nod al listei si muta ultimul nod in fata primului. Elementele listei sunt date de
inretgistrarea
struct nod
{ char nume[30];
int prec,urm;}

void mutaup(int &prim)


{int u=prim;
while(V[u].urm!=-1) u=V[u].urm;
V[V[u].prec].urm=-1;
V[u].prec=-1;
V[u].urm=prim;
V[prim].prec=u;
prim=u;
}

Liste circulare

O lista circulara simplu inlantuita este o lista liniara simplu inlantuita modificata astfel incat ultimul
element arata spre primul element din lista.
O lista circulara dublu inlantuita este o lista liniara dublu inlantuita modificata astfel incat ultimele
elemente pointeaza respectiv spre primele elemente din lista.
In continuare ne vor referi la liste circulare simplu inlantuite pe care le vom numi simplu: liste
circulare.

31
Deci fiecare element de lista circulara are urmatoarea structura:

Pe baza informatiei de inlantuire (pastrata in cimpul leg) trebuie sa poata fi identificat urmatorul
element din lista.

Lista circulara alocata static


Daca implementarea structurii de lista circulara se face prin tablouri se o lista circulara alocata static
sau simplu o lista circulara statica.
Consideram urmatoarele declaratii:

struct Element {
char data[100];
int leg;
};
Element V[8];

Pentru elementele vectorului V exista o ordine naturala data de aranjarea in memorie a elemetelor
sale: V[0], V[1], ... V[7]. Vom reperezenta memoria ocupata de vectorul V astfel incit fiecare element
sa fie reprezentat vertical, in felul urmator:

Completind cimpul leg pentru fiecare element al vectorului putem obtine o lista liniara simplu
inlantuita. Valoarea cimpului leg va fi indexul in vector al urmatorului elementdin lista.
Vectorul V:

Pe baza inlantuirii stabilita de valorile din figura de mai sus se obtine


secventa: V[3], V[6], V[7], V[0], V[1], V[2], V[4], V[5], V[3], V[4], ....

Observatie
Nu mai exista un ultim element in lista ca la listele liniare.
Este necesar sa cunoastem care este primul element din inlantuire, pentru aceasta retinem intr-o
variabila:
int cap;
indexul primului element
cap=3.
Parcurgerea in ordine a elementelor listei se face in felul urmator:

32
int crt;
.................
if (cap!=-1){
crt = cap;
// Prelucreaza V[crt]
while (V[crt].leg!=cap) {
crt = V[crt].leg;
// Prelucreaza V[crt]
}
}
Indiferent de modul cum se materializeaza informatiile de legatura pentru a reprezenta o lista
inlantuita vom folosi urmatoarea reprezentare:

Sageata care porneste din cimpul leg arata faptul valoarea memorata aici indica elementul la care
duce sageata.

Operatii in liste circulare

-Parcurgerea listei
Consideram: cap - contine adresa primului element din lista.
O parcurgere inseamna prelucrarea pe rind a tuturor elementelor listei, in ordinea in care acestea
apar in lista. Vom avea o variabila pointer p care va indica pe rind fiecare element al listei:

if (cap!=-1){
p = cap;
// Prelucreaza V[p].data
while (pV[p].leg>!=cap){
p = V[p].leg;
// Prelucreaza V[p].data
}
}

Un caz special apare atunci cind dorim sa facem o parcurgere care sa se opreasca in fata unui element
care sa indeplineasca o conditie (ca in cazul cind inseram un element intr-o pozitie data printr-o
conditie, sau stergem un element care indeplineste o conditie).
Presupunem ca lista are cel putin un element.

p = cap;
while (V[p].leg!=cap && !conditie(V[p].leg))
p = V[p].leg;

Bucla while se poate opri pe condifia "V[p].leg==cap", ceea ce insemna ca nici un element din lista nu
indeplineste conditia iar pointerul p indica ultimul element din lista, sau pe conditia
"conditie(V[p].leg)", ceea ce insemna ca l p va contine adresa elementului din fata primului element
care indeplineste conditia.

-Inserarea unui element in lista


Consideram: cap - contine adresa primului element din lista;
q - contine adresa unui element izolat care dorim sa fie inserat in lista.
Inserarea in fata

33
Fiecare sageata nou creata insemna o atribuire: se atribuie varibilei in care sageata nou creata isi are
originea, valoarea luata dintr-o variabila in care se afla originea unei sageti cu aceeasi destinatie.
In cazul nostru avem atribuirile (fiecare atribuire corespunde sagetii cu acelasi numar din figura):
(1) parcurge lista (p = adresa elemenului ce contine in cimpul leg
adresa continuta de variabila cap)
(2) V[p].leg = q;
(3) V[q].leg = cap;
(4) cap=q
Sa detaliem:
Prima operatie:
Parcugerea listei are rolul de a depista elementul care indica capul listei (contine in cimpul leg adresa
continuta de variabila cap). Adresa acestui element va fi continuta de variabila p.

A doua operatie:
V[p].leg=q
Modifica elementul care contine in cimpul leg adresa din cap a primului element din lista pentru a
contine adresa continuta de q a elementului inserat element care va fi de altfel si capul listei

A treia operatie:
V[q].leg = cap;
leaga elementul de inserat de restul listei. In urma acestei atribuiri, cap si V[q].leg vor indica ambii
inceputul listei initiale (vezi figura de mai jos).

34
A patra operatie:
cap = q;
pune in pointerul cap adresa elementului inserat in fata listei.

Observatie:
Daca lista in care se face insertia este vida atunci trebuie efectuate citeva modificari in secventa 1- 4
pentru ca rezultatul sa fie corect.
Exercitiu: Care sunt aceste modificari?
Inserarea in interior
Varibila q va indica elementul dupa care se face inserarea.

(1) V[p].leg = V[q].leg;


(2) V[q].leg = p;

Observatii:
1. Nu se poate face inserarea in fata unui element dat (prin q) fara a parcurge lista de la capat.
2. Nu exista nici o diferenta intre inserarea in interior in cazul listelor liniare simplu inlantuite si cel al
listelor circulare.

-Stergerea unui element din lista


Consideram: cap - contine adresa primului element din lista.

35
-Stergerea la inceputul listei
Prin operatia de stergere se intelege scoaterea unui element din inlantuire.

(1) parcurge lista (variabila p va contine adresa elementului care indica prin leg la elementul adresat
de cap)
(2) V[p].leg=V[cap].leg
(3) p = cap;
(4) cap = V[cap].leg;

-Stergerea in interior
Varibila q va indica elementul din fata celui care va fi sters.

(1) p = V[q].leg;
(2) V[q].leg = V[p].leg; // sau V[q].leg = V[V[q].leg].leg;

Observatii:
1. Atunci cind q indica penultimul element dintr-o lista, atribuirile de mai sus
functioneaza corect si sterg ultimul element din lista.
2. Nu se poate face stergerea in fata unui element dat (prin q) fara a parcurge lista de la capat.
3. Nu exista nici o diferenta intre stergera in interior in cazul listelor liniare simplu inlantuite si cel al
listelor circulare.

Exemplul 9
Sa se genereze o lista circulara care prelucreaza numere intregi:
a) sa se parcurga lista incepand de la primul nod si sa se afiseze informatia
b) sa se parcurga n elemente pornind de la primul din p in p elemente. De exemplu, daca lista
contine valorile: 1,2,3,4,5,6,7,8 si n=10 iar pasul este 3 atunci se va afisa: 1,4,7,2,5,8,3,6,1,4
c) sa se afiseze toate permutarile circulare ale listei de numere

36
d) sa se stearga elementul de pe pozitia k si sa se afiseze lista ramasa

#include <iostream>
#include <fstream>
using namespace std;
ifstream f("numere.in");

struct nod
{
int info,next;

};
nod V[100];
int prim=-1,nrelem=-1;

void insert(int x)
{
nrelem++;
nod k;
int crt;
k.info=x;
if(prim==-1)
{
prim=0;
k.next=prim;

}
else
{
crt=prim;
while(V[crt].next!=prim) crt=V[crt].next;
k.next=prim;
V[crt].next=nrelem;
}

V[nrelem]=k;

void afis()
{
int crt;
crt=prim;
while(V[crt].next!=prim) { cout<<V[crt].info<<" "; crt=V[crt].next;}
cout<<V[crt].info<<endl;
}

void afis(int n,int p)


{
int crt,i,j;
crt=prim;
for(i=1;i<=n;i++)
{
cout<<V[crt].info<<" ";
for(j=1;j<=p;j++) crt=V[crt].next;
}
cout<<endl;
}

37
void afiscirc()
{
int crt,cap;
cap=prim;
do {
crt=cap; while(V[crt].next!=cap) {cout<<V[crt].info<<" ";
crt=V[crt].next;}
cout<<V[crt].info<<endl;
cap=V[cap].next;
}while(cap!=prim);
}

void sterg(int k)
{ int crt=prim,i;
for(i=1;i<k-1;i++)
crt=V[crt].next;
if(V[crt].next==prim)
prim=V[prim].next;
V[crt].next=V[V[crt].next].next;

int main()
{
int x,n,p,k;

cout<<"numarul de numere afisate=";


cin>>n;
cout<<"pasul=";
cin>>p;
cout<<"pozitia stearsa=";
cin>>k;
while(f>>x)
insert(x);
f.close();
afis();
afis(n,p);
afiscirc();
sterg(k);
afis();
return 0;
}

Exemplul 10
Un numar de n copii se gasesc in cerc urmand ca unul dintre ei sa iasa din cerc. N si cele n nume se
citesc din fisierul copii.in. In fisierul cantec.in este scris un cantecel despartit in silabe. De la tastatura
se citeste numele unui copil de la care se incepe “numaratoarea”. Sa se determine numele copilului
care iese din cerc.
Exemplu:
copii.in
5
adi
dan
cristi
ana
alexandru

38
cantec.in
a la ba la por to ca la
Daca se incepe de la dan iese ana.

#include <iostream>
#include<string.h>
#include <fstream>

using namespace std;

ifstream f("copii.in");
ifstream g("cantec.in");

struct copil
{
char nume[30];
int next;};

copil V[100];
int prim=-1,nrelem=-1;

void adaug(char x[100])


{
copil nou;
int crt;
strcpy(nou.nume,x);
if(prim=-1)
prim=0;
nou.next=prim;
nrelem++;
if(prim!=-1)
{crt=prim;
while(V[crt].next!=prim) crt=V[crt].next;
V[crt].next=nrelem;

}
V[nrelem]=nou;}

void afis()
{
int crt=prim;
do{
cout<<V[crt].nume<<" ";
crt=V[crt].next;
}while(crt!=prim);}

void numaratoare(char nume[30],int k)


{
int crt=prim,i;
while(strcmp(V[crt].nume,nume)!=0)
crt=V[crt].next;

for(i=1;i<=k-1;i++)
crt=V[crt].next;
cout<<V[crt].nume<<" iesi afara";
}

int main()

39
{ char x[100],nume[30];
int k=0;
while(f>>x)
adaug(x);
afis();
f.close();
while(g>>x) k++;
cout<<k;
cout<<endl<<"numele primului elev=";
cin>>nume;
numaratoare(nume,k);
return 0;
}

Exemplul 11
Fie o problemã a unei firme de turism care se enunțã astfel: dintr-o listã de n localitãți, n≤20, așezate
în ordine alfabeticã, se vor alcãtui anumite voiaje. Costul zilnic de cazare în fiecare localitate este
cunoscut dintr-un fișier de date locuri.txt. Un turist dorește sã afle costul total al unui voiaj pe care îl
alcãtuiește din lista de localitãți prezentatã de firmã. Nu se iau în considerare cheltuielile de
transport.
De exemplu, fie lista localitãților însoțite de costul de cazare pe zi:

și se dorește afișarea unui itinerariu care pornește din București. Acesta poate fi cel din figura care
pleacã din București și se încheie la Horezu.

Costul total va fi de 109 lei noi.


Rezolvare. Pentru a rezolva aceastã problemã este nevoie, în primul rând, de organizarea datelor,
astfel ca localitãțile sã fie reprezentate și la nivelul programului. Se va alege o listã simplu-înlãnțuitã,
lista. Apoi, pe baza listei localitãților și a propunerilor fãcute de client, se alcãtuiește un voiaj; acesta
și costul total se afișeazã costul total.

#include<fstream.h>
#include<conio.h>
#include<string.h>

void main()
{typedef struct loc
{char nume[15];float pret;int urm;};
int prim,ultim,curent,ant,p,L[20],i,
v[20],plina=0;
loc lista[20];
prim=ultim=-1;
char den[15]; float cost, cost_total;
ifstream f(.locuri.txt.);
for(i=0;i<20;i++) L[i]=1;
clrscr();
while(!f.eof()&& !plina)//creare lista
{f>>den;if(f.eof())break;

40
f>>cost;
curent=-1;
for(i=0;i<20&&curent==-1;i++)
if(L[i]) {L[i]=0;curent=i;}
if(curent==-1)plina=1;
else
{strcpy(lista[curent].nume,den);
lista[curent].pret=cost;
lista[curent].urm=-1;
ant=-1;
p=prim;
while(p!=-1 &&
strcmp(den,lista[p].nume)>=0)
{ant=p;p=lista[p].urm;}
if(ant==-1)
{ lista[curent].urm=prim;
prim=curent;
if(ultim==-1)ultim=curent;
}
else
if(p==-1)
{ lista[ultim].urm=curent;
ultim=curent;
}
else
{ lista[ant].urm=curent;
lista[curent].urm=p;
}
}
}
if(plina) cout<<„\nLista e plina”<<endl;
f.close();
//Afisare lista
curent=prim;
while(curent!=-1)
{ cout<<lista[curent].nume<<„ „;
curent=lista[curent].urm;
}
//construire voiaj
cout<<endl;
cost_total=0;i=-1;
cout<<„Dati localitatea de pornire „;
cin>>den;
do
{curent=prim;
while(curent!=-1 &&
strcmp(lista[curent].nume,den)!=0)
curent=lista[curent].urm;
if(curent==-1)
cout<<„Nu este o localitate din
lista”<<endl;
else
{v[++i]=curent;
cost_total+=lista[curent].pret;
}
cout<<„Dati localitatea urmatoare „;
cin>>den;
}while(strcmp(den,”stop”)!=0);
cout<<„\n Ati ales voiajul „<<endl;

41
for(int k=0;k<=i;k++)
cout<<lista[v[k]].nume<<endl;
cout<<„Cu costul total „<<cost_total;
getch();
}

in program este folosit vectorul v în care sunt înregistraţi indicii localitãţilor alese de client din lista.
Tema Modificați programul aplicației de mai sus pentru a extinde dialogul cu clientul, astfel încât
acesta sã poatã propune mai mult de un voiaj; pentru fiecare nouã propunere se afișeazã lista
localitãților pe un rând-ecran, iar pe rândul urmãtor, costul acelui voiaj.

Exemplul 12
Un caz interesant de listã este lista circularã, adicã lanțul închis. Pentru acest tip de listã
extremitatea de început coincide cu cea de sfârșit. Simularea unei astfel de liste cu ajutorul
structurilor fixe va folosi tot un tablou unidimensional. În prelucrãrile informațiilor din acest tip de
listã se va avea grijã ca elementul cu indicele de sfârșit al listei, ultim, sã aibã ca succesor elementul
de indice inițial al listei, prim. În cazul eliminãrilor unor elemente dintr-o listã circularã apare
necesarã determinarea situației de listã cu un singur element, adicã momentul în care prim=ultim,
sau, altfel exprimat, lista[prim].urm=prim.
Ca aplicație, se va considera lista grup utilizatã în exemplele paragrafului. Numele persoanelor din
lista grup vor fi ale unor copii care sunt așezați în cerc în ordinea venirii. Copiii pregãtesc un joc prin
alegerea celui care sã porneascã jocul. Pentru aceasta, începând cu primul așezat în cerc, este eliminat
fiecare al n-lea copil, pînã ce mai rãmâne un singur copil care va începe jocul. Se cere organizarea
acestor prelucrãri într-un program care sã afișeze numele copilului care este ales în acest mod,
numele copiilor citindu-se dintr-un fișier, copii.txt, iar valoarea lui n de la tastaturã.

#include<fstream.h>
#include<conio.h>
#include<string.h>

void main()
{typedef struct persoana
{char nume[15];int urm;};
int prim,ultim,ant,curent,L[10],i,
plina=0,n;
persoana grup[10]; char den[15];
prim=ultim=-1;
for(i=0;i<10;i++) L[i]=1;
ifstream f(„copii.txt”);
while(!f.eof()&&!plina)//creare lista
{ f>>den;
if(f.eof()) break;
curent=-1;
for(i=0;i<10&&curent==-1;i++)
if(L[i]) {L[i]=0;curent=i;}
if(curent==-1)plina=1;
else
{strcpy(grup[curent].nume,den);
grup[curent].urm=-1;
if(prim==-1) prim=curent;
else grup[ultim].urm=curent;
ultim=curent;
}
}if(plina) cout<<„\nLista e plina”<<endl;
f.close();

42
//afisarea listei
cout<<„lista creata este”<<endl;
curent=prim;
while(curent!=-1)
{cout<<grup[curent].nume<<„ „;
curent=grup[curent].urm;
}
//inchiderea circulara
grup[ultim].urm=prim;
//eliminare cu pasul n
cout<<endl;
cout<<”Dati pasul de elmiminare”;
cin>>n;
curent=prim;
while(grup[curent].urm!=curent)
{for(i=1;i<n;i++)
{ant=curent;
curent=grup[curent].urm;
}
grup[ant].urm=grup[curent].urm;
L[curent]=1;
curent=grup[ant].urm;
}
cout<<endl;
cout<<”A ramas copilul”;
cout<<grup[curent].nume;
}

43

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