Documente Academic
Documente Profesional
Documente Cultură
---- Liste simplu înlănţuite. Implementarea listelor simplu înlănţuite cu alocare dinamică ---
În programare listele sunt secvenţe de înregistrări (adesea denumite noduri) între care există o serie de
relaţii de referire reprezentate prin adrese de legătură. Listele simplu înlănţuite sunt particularizate prin
faptul că orice înregistrare face referire doar la o singură altă înregistrare din listă: următoarea. Listele
simplu înlănţuite sunt printre cele mai simple şi în acelaşi timp printre cele mai utilizate structuri de
date. Cu ajutorul acestora se pot reprezenta structuri precum stivele, cozile, tablourile asociative, tabele
hash, etc.
Principial, arhitectura unei liste simplu înlănţuite este redată mai jos în Fig. 1. În acest caz discutăm
despre noduri cu o construcţie minimală ce au în componenţă o variabilă etichetă şi un element de
legătură către următorul nod din listă însă pot exista cazuri când înregistrările au mai multe elemente
componente cu diferite roluri pornind de la acela de a stoca o informaţie utilă dar ajungând şi până la
acela de a eticheta anumite proprietăţi ale înregistrării curente. Se pot extrage două concluzii privind
figura: 1) lista este încheiată cu un identificator terminal (cel mai adesea NULL), 2) nodurile listei nu pot
fi reprezentate cu ajutorul unui singur tip de date fundamental existent.
Pentru reprezentarea nodurilor de informaţie pentru listele simplu înlănţuite, în limbajul C / C++
se utilizează un struct care trebuie să conţină în componenţa sa cel puţin un pointer reprezentând
legătura către următorul nod precum şi o serie de alte intrări reprezentând informaţia utilă, pointeri
către alte structuri unde se stochează informaţia utilă sau etichete de identificare / marcare asociate cu
nodul curent. Spre exemplu structura de mai jos reprezintă tipul pentru noduri dintr-o presupusă listă
care conţine o singura informaţie utilă: un index de identificare:
struct My_Nod
{
int index_nod; // reprezinta informatia utila
My_Nod * next; // va stoca legatura catre elemente urmatoare
};
Un exemplu mai complicat în care se presupune definirea tipurilor de date pentru o listă simplu
înlănţuită care conţine înregistrări legate de informaţiile private şi publice ale unor persoane ar putea fi
scris în felul următor: se defineşte un struct adiţional care reprezintă şablonul pentru informaţiile
publice respectiv unul pentru informaţiile private iar struct-ul pentru nod va conţine un pointer către
două astfel de struct 'specializate'. Se procedează în acest fel deoarece în anumite cazuri se poate face
economie de memorie ocupată: un pointer în struct-ul unui nod ocupă mai puţină memorie decât dacă
acesta ar conţine variabile pentru toate înregistrările de interes care la momentul respectiv se consideră
că nu ar fi disponibile. Codul este cel de mai jos:
//structura care contine informatia privata - este posibil ca aceste date sa nu fie completate de utilizator
struct Private_Info
{
bool i_am; // boy / girl
Vom parcurge mai jos fiecare operaţie principală de manipulare: inserare, extragere, căutare,
ştergere, pentru un exemplu în care discutăm despre o listă trivială conţinând în struct-ul nodului o
variabilă de tip întreg reprezentând un index dat de utilizator. În cazul în care se doreşte utilizarea unei
liste cu noduri cu mai multe înregistrări trebuie specificată o funcţie suplimentară de încărcare a datelor
într-un struct nod. Aceasta, în funcţie de cum a fost construită, poate fi apelată fie în cadrul funcţiei
'AddNod', fie în exterior, caz în care 'AddNod' va primi ca parametru un pointer către nodul creat pentru
a-l putea lega la listă.
//sablon noduri
struct My_Nod
{
int index;
My_Nod * next;
};
//caz particular 2 - nodul este primul element din lista sau unicul element din lista
if(pTmp->index == _index)
{
*pLista = (*pLista)->next; //modific varful listei
return pTmp; //pTmp indica deja spre varful listei asa ca este intors
}
// pas 1: parcurg lista in cautarea nodului cu indexul '_index';
// Totusi sunt constrans sa ma pozitionez pe nodul anterior nodului cautat pentru a putea
// realiza extragerea din lista.
while(pTmp->next)
{
//daca gasesc nodul, intrerup parcurgerea
if(pTmp->next->index == _index)
break;
//pas 3: modific legaturile in lista astfel incat sa 'sar' peste nodul extras.
// Aici intervine constrangerea din parcurgere. // Totodata 'desprind' nodul extras din lista.
pTmp -> next = pTmp ->next->next;
pExtras->next = NULL;
Căutarea nu diferă mult de codul de mai sus, fiind simplificată în sensul că se păstrează legăturile în listă
(nodul nu trebuie extras).
Ştergerea unui nod se poate face utilizând codul de mai jos. Funcţia primeşte drept parametrii vârful
listei şi nodul de şters prin intermediul cheii sale.
//caz particular 2 - nodul de sters este primul element din lista sau unicul elem din lista
if((*pLista)->index == _index)
{
*pLista = (*pLista)->next; // deplasez varful listei
delete tmp; // tmp a fost apriori setat cu primul elem din lista
return 1;
}
//caz general - pas 1: caut nodul de sters astfel incat sa ma pozitionez inaintea acestuia
while(tmp->next)
{
if(tmp->next->index == _index)
break;
tmp = tmp->next;
}
//pas 2: sterg daca sunt pe un nod valid (nu ultimul din lista)
if(tmp->next)
{
pSters = tmp->next; // selectez nodul
tmp->next = tmp->next->next; // se sunteaza nodul de sters
delete pSters; // stergerea efectiva
return 1;
}
return 0;
}
Exerciţii:
1) Să se scrie toate funcţiile necesare care implementează o listă simplu înlănţuită conţinând drept
informaţie un câmp de tipul int.
OBS: Funcţiile trebuie să îndeplinească următoarele cerinţe:
- să introducă un element în listă după ultimul nod. Elementul introdus este un nod de tipul
listei.
- să caute un nod în listă după informaţia stocată şi să întoarcă un pointer la nodul găsit
- să şteargă un nod din listă care are informaţia egală cu parametrul primit.
- să şteargă toate componentele listei
2) Utilizând pachetul de funcţii scrise la problema 1), să se scrie un program care citeşte de la tastatură o
serie de valori şi le inserează succesiv în listă.
OBS: Se va implementa o funcţie care este apelată înainte de adăugarea unui nou nod. Aceasta citeşte
informaţia de la tastatură şi crează un nod după care întoarce un pointer către acesta. Nodul va fi
introdus în listă folosind funcţia de adăugare. Să se scrie în plus o funcţie care inserează un nou nod în
listă după un nod identificat prin informaţia stocată şi să se verifice funcţionarea acesteia. Funcţia are
următorul prototip:
3) Utilizând funcţiile de mai sus să se implementeze o listă simplu înlănţuită conţinând informaţii unice şi
să se scrie o funcţie void MarkData(My_Nod * _inLista) care inserează eticheta '0' după fiecare nod cu o
valoare impară de informaţie din intervalul *0, 9+.
4) Utilizând funcţiile de mai sus să se scrie un program care citeşte de la tastatură un număr de K noduri,
le introduce în listă după care şterge fiecare duplicat din listă. Exemplu: dacă se vor citi de la tastatură
valorile 1, 1, 2, 3, 1, 4, 7, 9, 7, 3 , 2, rezultatul trebuie să fie: 1, 2, 3, 4, 7, 9.