Sunteți pe pagina 1din 8

Lucrarea nr.

9
Structura de date lista

1. Scopul lucrării – îl reprezintă prezentarea structurii de date tip listă înlănţuită precum şi a
operaţiilor care se realizează asupra acesteia.

2.Aspecte teoretice
2.1. Implementarea listelor cu ajutorul tipului pointer
În această implementare, o listă liniară este o structură dinamică şi deci, ea poate fi definită în
termeni recursivi utilizând următoarele structuri de date:

struct nod {
int cheie;
struct nod *urm;
...info;
};

O astfel de structură ar putea fi reprezentată schematic în felul următor:

inceput
cheie 1 2 3 Ν
urm … Νull
info … … … …

Figura 1: Exemplu de listă implementată cu ajutorul tipului pointer


Se observă existenţa unei variabile pointer P care indică începutul listei (primul nod).

2.1.1. Crearea listelor înlănţuite. Inserţia unui nod într-o listă înlănţuită

nod *q=new nod;


q->urm=inceput;
inceput=q;

Trebuie menţionat că această secvenţă de program nu asignează şi valorile câmpurilor cheie


şi info pentru noul nod (cel inserat).
Inserţia unui nod la începutul listei se prezintă grafic astfel:

… …
inceput inceput

q->urm=inceput q->urm=inceput
q
inceput=q

Figura 2: Inserţia unui nod la începutul unei liste


Inserţia unui nod la sfârşitul unei liste se poate realiza prin secvenţa următoare:

nod *r =new nod;


r->urm=NULL;
sfârsit->urm=r;
sfârsit=r;

unde sfârșit este un pointer care indică întotdeauna sfârşitul listei (ultimul nod al acesteia).
Referitor la această secvenţă trebuie observat că aceasta nu poate insera un nod într-o listă
vidă, deoarece sfârșit->urm nu există în acest caz. Deci, dacă se doreşte crearea unei liste în
ordine naturală, primul nod trebuie inserat printr-un alt procedeu, de exemplu printr-o inserţie
la început de listă. Schematic, inserţia la sfârşitul unei liste se prezintă în felul următor:

sfirsit Null
sfirsit=r
sfirsit->urm=r

r
Null

Figura 3: Inserţia unei nod la sfârşitul unei liste


Pe lângă cele două situaţii prezentate anterior (inserţia unui nod la începutul unei liste şi
respectiv inserţia unui nod la sfârşitul unei liste), mai rămâne de arătat modalitatea de a insera
noduri într-o poziţie oarecare a unei liste. Aici putem avea două cazuri şi anume când se
doreşte ca inserţia noului nod să aibă loc după un anumit nod, indicat de un pointer p, sau
înaintea acestuia.
Astfel, fie p pointerul care indică un nod al listei iar q o variabilă pointer ajutătoare, care va
indica nodul care se inserează. Inserţia acestuia după nodul indicat de pointerul p, p->, se
realizează prin următoarea secvenţă:
Grafic, inserţia se prezintă în felul următor:

q 25 q->urm=p->urm

p->urm=q

10 20 30 …

Figura 4: Inserţia unui nod după nodul indicat de pointerul p


nod *q =new nod;
q->urm=p->urm;
p->urm=q;

Dacă se doreşte inserţia noului nod înaintea nodului indicat de p, *p, apare o complicaţie
generată de imposibilitatea practică de a afla simplu adresa predecesorului lui *p. Această
problemă însă se poate rezolva simplu printr-un artificiu şi anume: se inserează un nou nod
după nodul *p; în acest nod, se asignează toate câmpurile, mai puţin cel de înlănţuire (urm),
cu valorile nodului *p; în acest moment, nodul *p va exista în dublu exemplar în listă, pe
poziţia iniţială şi în poziţia imediat următoare; după care se asignează cu noile valori
câmpurile cheie şi info corespunzătoare vechiului nod *p. Acest lucru se poate ilustra prin
următoarea diagramă:

q 20

*q=*p

10 20 30 …

Figura 5a: Crearea unui nod şi copierea vechiului nod peste acesta
20

p->urm=q

10 15 30 …

Figura 5b: Înlănţuirea în listă a noului nod

Secvenţa de program corespunzătoare este următoarea:

nod *q =new nod;


*q=*p;
p->urm=q;

2.1.2. Suprimarea nodurilor dintr-o listă înlănţuită


Să presupunem că avem un pointer p care indică un nod al unei liste liniare înlănţuite, şi se
cere să se suprime succesorul nodului indicat de p, şi anume nodul p->urm. Aceasta se poate
face prin următoarea secvenţă:

r=p->urm;
p->urm=r->urm;

În secvenţa de program mai sus se poate evita utilizarea variabilei pointer r prin înlocuirea
fragmentului de mai sus cu instrucţiunea:

p->urm=p->urm->urm;

Utilizarea variabilei r are însă avantajul că, prin intermediul ei, programatorul poate avea
acces ulterior la nodul suprimat, acces care altfel se pierde.
Dacă programatorul nu mai are nevoie ulterior de nodul suprimat, se recomandă utilizarea
procedurii standard dispose(r) pentru a elibera zona de memorie ocupată de *r.
Daca însă se doreşte suprimarea chiar a nodului indicat de pointerul p dintr-o listă, şi anume
chiar nodul *p, observăm că şi aici apare aceeaşi dificultate, generată de imposibilitatea de a
afla simplu adresa predecesorului lui *p. Soluţia se bazează pe aceeaşi tehnică: se aduce
succesorul în locul lui *p şi apoi se suprimă vechiul succesor. Secvenţa de program care
realizează acest lucru este următoarea:

r=p->urm;
*p=*r;
free(r);

Se remarcă faptul că ambele tehnici de suprimare se pot aplica numai daca p nu este ultimul
nod al listei, adică în cazul în care r->urm!=NULL.

2.1.3. Traversarea unei liste înlănţuite


Se înţelege prin traversarea unei liste parcurgerea secvenţială a acesteia în scopul executării
unei anumite operaţii asupra tuturor nodurilor listei.
Astfel, fie început pointerul care indică începutul listei şi q o variabilă pointer auxiliară.
Traversarea se va face astfel:

q=inceput;
while (q!=NULL)
{
prelucrare(*q);
q=q->urm;
}

O operaţie frecvent utilizată o reprezintă căutarea unui nod dat într-o listă: secvenţa prin care
se caută nodul cu cheia x dată într-o listă înlănţuită unde început este pointerul de început de
listă este următoarea:

#define 0 FALSE
#define 1 TRUE

b=FALSE;
q=început;
while ((q!=NULL) && (b==FALSE))
if (q->cheie==x) b=TRUE;
else q=q->urm;

Dacă după terminarea secvenţei avem b=true atunci *q este nodul căutat; în caz contrar,
nodul nu s-a găsit şi avem q=NULL.

2.2. Problemă rezolvată


Să se realizeze un program cu ajutorul caruia se va putea prelucra o listă simplu înlanţuită, în
care fiecare nod va conține informaţii specifice unui student : nume, prenume, grupa.
Programul va fi realizat folosind un meniu, având următoarele opţiuni : Adăugare student,
Ştergere student, Afişare student (informaţiile referitoare la studentul cu un anumit nume),
Afişare lista studenţi, Ieşire program.

#define use_CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_DEPRECATE
#include "stdafx.h"
#include <iostream>
#include <conio.h>
#include <stdio.h>
#include <string.h>
#pragma warning (disable : 4996)
using namespace std;
struct student_struct
{
char nume[20];
char prenume[20];
char grupa[6];
student_struct *urm;
};

student_struct *prim, *p, *t;


int o;

void adauga_student()
{
char nume[20], prenume[20], grupa[6];
cout<<"\n\nAdaugare student";
cout<<"\n\nDati numele studentului : ";
cin>>nume;
cout<<"\nDati prenumele studentului : ";
cin>>prenume;
cout<<"\nDati grupa studentului : ";
cin>>grupa;
if (prim == NULL)
//daca nu avem o lista, se crează primul nod
{
prim = new student_struct;
strcpy(prim->nume, nume);
strcpy(prim->prenume, prenume);
strcpy(prim->grupa, grupa);
prim->urm = NULL;
}
else //daca avem deja o lista, adaugam la capat
{
p = prim;
while (p->urm != NULL) p = p->urm;
t = new student_struct;
strcpy(t->nume, nume);
strcpy(t->prenume, prenume);
strcpy(t->grupa, grupa);
t->urm = NULL;
p->urm = t;
}
}

void afis_lista()
{
cout<<"\n\nAfisare lista studenti\n\n";
if (prim == NULL)
cout<<"Nu avem nici un student in lista";
else
{
p = prim;
while (p != NULL)
{
cout<<"\nNume:"<< p->nume<<"\nPrenume:"<<p->prenume<<"\nGrupa:"<<
p->grupa;
p = p->urm;
}
}
_getch();
}

void afis_student()
{
char nume[20];
cout<<"\n\nAfisare student";
cout<<"\n\nDati numele studentului cautat = ";
cin>>nume;
int gasit = 0;
p = prim;
while (p != NULL)
{
if (strcmp(p->nume, nume) == 0)
{
gasit = 1;
cout << "\nNume:" << p->nume << "\nPrenume:" << p->prenume <<
"\nGrupa:\n" << p->grupa;
}
p = p->urm;
}
if (!gasit)
cout<<"\nNu am gasit studentul:"<< nume;
_getch();
}

void sterge_student()
{
cout<<"\n\nStergere student";
char nume[20];
cout<<"\n\nDati numele studentului ce se sterge :\n ";
cin>>nume;
p = prim; int gasit = 0;
while (p != NULL)
{
if (strcmp(p->nume, nume) == 0)
{
gasit = 1;
cout << "\nNume:" << p->nume << "\nPrenume:" << p->prenume <<
"\nGrupa:\n" << p->grupa;
break;
}
p = p->urm;
}
if (!gasit)
cout<<"Nu am gasit studentul:"<< nume;
else
{
if (p == prim) //daca e tocmai primul din lista
prim = prim->urm;
else
if (p->urm == NULL) //daca e ultimul
{
t = prim;
while (t->urm != p) t = t->urm;
delete p;
t->urm = NULL;
}
else //daca e undeva prin lista
{
t = prim;
while (t->urm != p) t = t->urm;
t->urm = p->urm;
delete p;
}
}
_getch();
}
int main()
{
do
{
cout<<"Meniu\n";
cout<<"1.Adaugare student\n";
cout<<"2.Stergere student\n";
cout<<"3.Afisare student\n";
cout<<"4.Afisare lista studenti\n";
cout<<"5.Iesire\n\n";
cout<<"Alegeti optiunea : "; cin>>o;
switch (o)
{
case 1:adauga_student(); break;
case 2:sterge_student(); break;
case 3:afis_student(); break;
case 4:afis_lista(); break;
}
} while (o != 5);
}

3. Probleme propuse

1. Se consideră o listă simplu înlănțuită. Realizați funcția de adăugare în listă a elementelor


date de la tastatură și afișarea acestora.
2. Se consideră o listă simplu înlănţuită în care fiecare nod va conţine un cuvânt. Să se
sorteze aceste cuvinte în ordine alfabetică folosind algoritmul de sortare Quicksort. Evaluaţi
performanţele unui astfel de algoritm de sortare, pentru diferite valori ale numărului de
cuvinte (10, 1000, 10000) şi realizaţi o comparaţie faţa de utilizarea unui tablou în locul unei
liste. Comentaţi rezultatul.
3. Se dă o listă înlănţuită L care conţine ca şi elemente şiruri de caractere (cuvinte), şi o altă
listă P, care conţine numere întregi în ordine crescătoare. Să se scrie funcţia prin care se şterg
din lista L toate elementele care se află pe poziţiile specificate prin numerele din lista P, ca o
funcţie cu parametri de tipul sterge(L,P). Funcţia va trata prin mesaje sugestive situaţiile de
eroare.
4. Se consideră două fraze oarecare, formate din cuvinte separate prin caracterul spaţiu,
virgulă sau punct-virgulă, care se citesc de la tastatură. Frazele se termină cu caracterul punct.
Să se scrie un program care, utilizând o structură de listă simplu înlănţuită pentru memorarea
frazelor, determină câte cuvinte comune au cele două fraze.
5. Să se scrie o funcţie care realizează operaţia de cut-and-paste a unei porţiuni dintr-o listă
înlănţuită L1 în altă listă înlănţuită L2, conţinând şiruri de caractere, astfel încât numărul de
mutări de elemente să fie cât mai mic posibil. Funcţia va conţine ca şi parametri : poziţia de
început respectiv de sfârşit din prima lista L1 pentru care se face cut şi poziţia din a doua lista
L2 după care se face paste. Să se scrie un program care utilizează funcţia astfel creată, şi care
va afişa cele două liste în formă iniţială şi apoi, după realizarea operaţiei cut-and-paste.
6. Problema Josephus : Se aşează un număr de N copii, numerotaţi de la 1 la N, într-un cerc.
Pornind de la copilul cu numărul 1, se trece o minge de la un copil la altul, şi fiecare al M-lea
copil la care ajunge mingea este eliminat din cerc. Jocul continuă cu persoana care urmează
după copilul eliminat. Câştigătorul este ultimul copil rămas cu mingea. Să se scrie programul
care rezolvă acest joc, pentru valori date ale lui M respectiv N.