Documente Academic
Documente Profesional
Documente Cultură
B5intro+cap1 (A Doua Carte)
B5intro+cap1 (A Doua Carte)
6
CAPITOLUL 1
STRUCTURI DE DATE şi COLECłII DE DATE
• identificarea datelor;
• clasificarea şi descrierea proprietăŃilor, a caracteristicilor datelor;
• gruparea datelor în colecŃii de date destinate prelucrării
automate;
• reprezentarea externă pe suporturi tehnice;
• identificarea, definirea şi descrierea procedurilor de prelucrare
automată.
7
structură de date presupune şi că între aceste informaŃii s-au definit o
serie de relaŃii cum ar fi:
• de echivalenŃă;
• de ordine parŃială sau de ordine totală
8
Structurile de date se implementează utilizând tipuri de date,
toate facilitate de către un limbaj de programare. Pentru multe metode
formale de proiectare şi limbaje de programare, factorul organizatoric
cheie sunt structurile de date şi nu algoritmii. Majoritatea limbajelor
dispun de un mod care permite reutilizarea structurilor de date şi pentru
alte aplicaŃii, prin ascunderea detaliilor de implementare, sigure şi
verificate, în spatele unor interfeŃe controlate. De exemplu, în limbajul
C++ se utilizează în acest scop tipuri obiectuale implementate prin
structuri sau prin clase.
Exemple de structuri de date (logice) uzuale: tablouri, stive, cozi,
liste, grafuri, arbori, tabele de dispersie.
Din cauză că structurile de date au o importanŃă atât de mare,
multe dintre ele sunt incluse în bibliotecile standard ale multor limbaje
de programare şi medii de dezvoltare, cum ar fi
9
• structuri de date externe: au un caracter relativ permanent,
deoarece sunt memorate pe suport extern. Aceste structuri pot
cuprinde:
o fişiere de date
o baze de date
o bănci de date
10
1.2 ColecŃii de date
11
corespunzător priorităŃii lui în O(log(n)). Inserarea elementelor în
coada prioritară se face în ordinea priorităŃii, astfel încât să fie extras
întotdeauna elementul cel mai prioritar. Serviciul de urgenŃă al
spitalelor foloseşte ca model coada prioritară. Cozile prioritare sunt
folosite la planificarea joburilor într-un sistem de operare (jobul cel mai
prioritar va fi primul executat).
• pot fi copiate.
• numărul maxim de elemente pe care îl pot conŃine
(„capacitatea”);
• numărul curent (actual) de elemente conŃinute (cardinalul);
• modificabile sau nemodificabile: suportă/nu suportă operaŃii de
modificare (de exemplu adăugare unui element, eliminarea unui
element);
• sunt sau nu sunt imutabile, adică permit/nu permit modificarea
elementelor colecŃiei;
• permit sau nu permit acces aleatoriu: accesul aleatoriu se referă
la faptul ca timpul de acces este acelaşi pentru toate elementele
colecŃiei.
12
Mai pot fi definite operaŃii precum:
• test dacă colecŃia este vidă: de regulă denumit isEmpty;
• modificarea unui element din colecŃie;
• copierea unei colecŃii;
13
ideală care descrie sistemul de fişiere cu directoare şi
subdirectoare sau diagramele de organizare ale firmelor).
• ColecŃii grupuri – sunt colecŃii neliniare in care elementele nu
sunt ordonate în nici un fel. Exemple:
o mulŃimea reprezintă un grup. OperaŃiile specifice sunt:
reuniunea, intersecŃia, diferenŃa, testul de apartenenŃă,
relaŃia de incluziune.
o graful este o structură de date care modelează relaŃiile
între obiecte prin două mulŃimi: o mulŃime de vârfuri şi o
mulŃime de muchii care conectează aceste vârfuri.
d) Unicitatea elementelor din colecŃie:
• ColecŃii cu elemente distincte: mulŃimea
• ColecŃii cu elemente multiple: lista, vectorul
14
• cu acces la orice element din colecŃie pe baza valorii
elementului
• cu acces limitat la primul şi/sau la ultimul element din colecŃie
j) Utilizarea colecŃiei:
• ColecŃii pentru memorarea temporară a unor date (de tip buffer),
care au un conŃinut foarte volatil: stive, cozi, mulŃimi;
• ColecŃii de căutare, cu un conŃinut mai stabil şi cu operaŃii
frecvente de căutare: liste, dicŃionare, arbori. Uneori se
consideră că o structură de date generală este o colecŃie de
înregistrări (structuri). Pentru o structură de date abstracte
folosită în căutare se evidenŃiază un câmp discriminant, folosit la
identificarea unică a fiecărei înregistrări denumit cheie (key) sau
cheie de căutare (search key). Cheia poate fi şi o combinaŃie a
două câmpuri din înregistrare (de ex. concatenarea a două
şiruri, cum ar fi numele şi prenumele).
15
1.3 Tipuri de date fundamentale în C/C++
16
int unsigned short x; etc.
Tabelul următor prezintă lungimea zonei de memorie ocupată de
fiecare tip de dată în medii de dezvoltare a aplicaŃiilor (IDE) scrise pe
32 biŃi implementate pe calculatoare compatibile IMB-PC şi intervalul
de valori aferent:
Dimensiune
Tip Domeniu
în bi i
-
int 32
2147483648..2147483647
char 8 -128...127
double 64 +/-1.7×10308
float 32 +/-3.4×1038
unsigned
8 0...255
char
unsigned
16 0..4294967295
int
short int 16 -32768..32767
short
unsigned 16 0...65535
int
-
long int 32
2147483648..2147483647
unsigned
32 0..4294967295
long
long long
64 -263...263-1
int
long long
unsigned 64 0...264-1
int
long
80 1.1×104932
double
bool 8 true, false
Tabelul 1.1 Tipuri de date în C
17
În fişierele header <climits> sunt definite constante simbolice
(cum ar fi: INT_MAX, INT_MIN, LONG_MAX, LLONG_MAX etc) care au ca
valoare limitele inferioară şi superioară ale intervalului de valori pentru
unele dintre tipurile de date întregi de mai sus. Numele unora dintre
aceste constante poate să difere în funcŃie de mediul de programare
utilizat. Astfel, pentru valoarea maximă a tipului long long int în
unele implementări este definită constanta simbolică I64_MAX.
Fără a detalia foarte mult modul de reprezentare a datelor reale
(de tip float sau double), ne reamintim faptul că, pentru acestea,
este importantă şi precizia de reprezentare. Deoarece calculatorul
poate reprezenta doar o submulŃime finită de valori reale, în anumite
cazuri, pot apare erori importante. Numerele reale pot fi scrise sub
forma:
N = mantisă×bazăexponent
unde:
- mantisa este un număr frac ionar normalizat (în faŃa virgulei se
afla 0, iar prima cifra de după virgula este diferita de zero);
- exponentul este un număr întreg.
- baza = 2 (deoarece forma interna de reprezentare a numerelor
este binară.
În memorie sunt reprezentate mantisa şi exponentul. Numărul de cifre
de după virgulă determină precizia de reprezentare a tipului real. Astfel,
într-un IDE în care se utilizează 23 cifre (binare) pentru reprezentarea
mantisei, două valori reale care în baza 10 diferă la a 7-a cifră vor avea
aceeaşi reprezentare şi deci vor fi considerate egale. Aceasta este
situaŃia uzuală pentru reprezentarea datelor de tip float. Pentru
datele de tip double, precizia este 14 cifre zecimale, iar pentru cele de
tip long double, precizia este de 20 cifre zecimale. Fişierul header
<cfloat> conŃine definiŃii pentru diverse constante simbolice
corespunzătoare valorilor limită care caracterizează cele trei tipuri reale
din tabelul 1.1
18
1.3.2 Pointeri
A) GeneralităŃi
B) OperaŃii cu pointeri
i afişarea
O expresie de tip pointer poate fi afişată aşa cum afişăm valori
aparŃinând unor tipuri întregi sau reale; presupunând că am declarat o
variabilă pointer p instrucŃiunea următoare are ca efect afişarea
19
adresei conŃinută de variabila p. În implementările uzuale la data scrierii
cărŃii, valoarea este afişată în baza 16 pe şase cifre.
cout << *p;
ii atribuiri
Un pointer poate fi asignat altui pointer doar dacă cei doi
pointează spre acelaşi tip. În caz contrar, trebuie aplicată o operaŃie de
conversie explicită (type cast) pentru ca tipul valorii pointerului din
dreapta semnului = să fie adus la tipul pointerului din stânga. ExcepŃie
de la această regulă face pointerul void* care este un tip generic şi
poate reprezenta orice tip de pointer fără a mai fi nevoie de conversie
de tip (type cast).
Exemplu:
int *p1, *p2; (1.2)
double *q;
p1 = p2; //atribuire corectă
q = p2; //atribuire incorectă
q = (double *)p2; //atribuire corectă
Un pointer poate primi şi una dintre valorile 0 sau NULL. Un pointer cu
valoarea 0 sau NULL nu pointează către nicio zonă de memorie.
Constanta NULL este declarată în fişierul header <iostream> şi în alte
câteva biblioteci standard. Atribuirea valorii NULL unui pointer este
echivalentă cu atribuirea valorii 0 deoarece 0 este convertit automat
către o adresă de tipul pointerului.
iv operatorul *
În contextul pointerilor operatorul * este denumit operator de
indirectare sau de dereferenŃiere. El ne oferă accesul la zona de
memorie a cărei adresă o conŃine pointerul căruia îi este aplicat.
În contextul instrucŃiunilor din exemplul anterior, instrucŃiunea
cout << *yPtr << endl;
20
tipăreşte valoarea zonei de memorie conŃinută de pointerul yPtr
(secvenŃa va afişa valoarea 5).
Un pointer dereferenŃiat poate fi folosit în partea stângă a unei
instrucŃiuni de asignare (in terminologia limbajului C/C++, o construcŃie
sintatctică care poate să apară în stânga a unei atribuiri se numeşte
lvalue):
De exemplu:
*yPtr = 17;
Prin această operaŃie (în contextul instrucŃiunilor din exemplul (1.3)),
valoarea 17 este asignată variabilei y.
DereferenŃierea nu se poate aplica pointerilor spre void.
A) new tip;
Această expresie are ca efect apariŃia unei variabile dinamice de
tipul precizat. Mai mult, valoarea expresiei este adresa unde s-a făcut
alocarea, iar tipul acestei adrese este pointer spre tipul din expresie. În
cazul în care nu se poate face această alocare, valoarea expresiei este
NULL. În mod normal, această adresă este reŃinută într-o variabilă de
tip pointer, astfel încât să putem accesa variabila dinamică care a
apărut (Variabillele dinamice NU au nume, deci le vom accesa prin
pointeri cu condiŃia să le ştim adresele!).
Exemple:
int *p1,*p2;
double *q;
p1 = new int; //apare o variabilă dinamică de tip int iar
adresa ei este reŃinută de p1. Accesarea acestei variabile dinamice se
va face cu sintaxa *p1!
21
q = new double; //apare o variabilă dinamică de tip double
iar adresa ei este reŃinută de q.
B) new tip(expresie);
Efect similar cu forma A) cu deosebirea că variabila dinamică
pentru care s-a făcut alocarea este iniŃializată cu valoarea expresiei.
C) new tip[nr_elem];
Această expresie are ca efect apariŃia a nr_elem variabile
dinamice, toate având tipul precizat. Aceste elemente vor ocupa poziŃii
consecutive în memoria dinamică. Mai mult, valoarea expresiei este
adresa unde se află primul dintre elementele alocate. În cazul în care
nu se poate face această alocare, valoarea expresiei este NULL. În
mod normal, această adresă este reŃinută într-o variabilă de tip pointer,
iar elementele alocate vor putea fi accesate asemănător cu elementele
unui tablou, deoarece variabilei pointer i se pot aplica parantezele
pătrate (vezi şi paragraful C de la acest subcapitol referitor la legătura
dintre pointeri şi tablouri)!
Exemplu:
int *p;
p = new int[100]; // s-au alocat 100 elemente de tip int
aflate în poziŃii consecutive în memorie, astfel încât vom putea accesa
aceste elemente cu sintaxe de genul p[0], p[1],..., p[i],... etc.
22
E) delete [] p; //unde p este pointer către un tablou
Efectul acestei instrucŃiuni este că zona de memorie ocupată de
tabloul spre începutul căreia pointează p este dezalocată.
vi operatorul ->
Acest operator se poate aplica numai pointerilor care reŃin
adrese ale unor variabile care au câmpuri (struct, class, union). El
oferă accesul la câmpurile respectivelor variabile. Să presupunem că
avem următoarele declarări:
struct STUDENT
{
char nume[20], prenume[30];
double notaexamen, notalaborator;
} *p;
În acest context, următoarele instrucŃiuni reprezintă alocarea de
memorie pentru o variabilă dinamică de tip STUDENT şi citirea de la
tastatură a valorilor câmpurilor acesteia:
p = new STUDENT;
cin >> p->nume >> p->prenume;
cin >> p->notaexamen >> p->notalaborator;
23
Pointerul vPtr poate fi iniŃializat cu adresa primului element al tabloului
folosind secvenŃa următoare:
int *vPtr;
vPtr = &(v[0]);
Adresa celui de-al doilea element al tabloului este &(v[1]).
Spre deosebire de operaŃiile matematice în care adunarea 22FF50+2
are ca rezultat valoarea 22FF52, în aritmetica pointerilor adăugarea
unui întreg la o adresă de memorie are ca rezultat o nouă adresă de
memorie. Aceasta este egală cu adresa iniŃială la care se adaugă un
număr de locaŃii de memorie egal cu valoarea întregului înmulŃită cu
dimensiunea obiectului la care referă pointerul.
Exemplu:
vPtr += 2;
are ca rezultat valoarea 22FF50 + 2 × 4 = 22FF58 (presupunem
că valorile int sunt reprezentate pe 4 octeŃi). În urma acestei operaŃii,
vPtr va pointa către v[2].
Similar, pentru un pointer către double, aceeaşi operaŃie are ca rezultat
valoarea 22FF50 + 2 × 8 = 22FF60.
Incrementarea unui pointer are acelaşi efect cu adunarea valorii 1 la
acel pointer.
Rezultatul diferenŃei între doi pointeri are sens dacă cei doi pointeri
pointează către elementele aceluiaşi tablou. În acest caz, rezultatul
este egal cu numărul de elemente aflat între adresele pointate de cei
doi pointeri (rezultatul poate fi pozitiv sau negativ!)
C) pointeri şi tablouri
24
Valoarea 3 din aceste expresii se numeşte offset la pointer, iar o
expresie care accesează un element al unui tablou în acest mod se
numeşte notaŃie offset sau notaŃie pointer.
ObservaŃie: fără paranteze, expresia *vPtr + 3 ar fi avut ca efect
adunarea valorii 3 la expresia *vPtr, adică la v[0].
În schimb, numele unui tablou este un pointer constant şi nu poate fi
folosit în operaŃii care i-ar schimba conŃinutul.
Exemple
v += 3; (1.5)
v = vPtr;
sunt operaŃii invalide.
Pentru pointeri se pot folosi indici la fel ca şi pentru tablouri.
Exemplu: în instrucŃiunile de mai jos vom citi de la tastatură o valoare
naturală n, vom aloca în memorie spaŃiu pentru un tablou cu n
elemente şi în cazul în care alocarea s-a efectuat cu succes vom citi de
la tastatură valori pentru aceste n elemente.
int * ptr=NULL, n, i; (1.6)
cin >> n;
if (n>0)
{ ptr = new int[n];
if (ptr != NULL)
{ for (i=0; i<n; i++)
cin >> ptr[i];
}
else
cout << „spatiu de memorie insuficient”;
}
28
1.4 Structura de date de tip tablou
tip nume[număr_de_elemente];
int n; (1.9)
cin >> n;
int a[n];
29
memorie pentru tabloul a pe să nu fie efectuată din varii motive (cel
mai probabil dintr-o valoare prea mare citită pentru n).
Un exemplu concret:
double a[10001];
• citire de la tastatură
• citire dintr-un fişier
• atribuire de valori după o formulă
30
• atribuire de valori aleatoare
• atribuirea de valori luate dintr-un alt tablou
• inserare
• eliminare
o a unui element determinat prin poziŃia din tablou
o a unui element determinat prin valoarea sa
o a tuturor elementelor egale cu o valore dată
• căutare
• sortare
• efectuarea unor calcule:
o minim/maxim
o statistici: contorizare, însumare
o verificare (dacă elementele tabloului au o anumită
proprietate)
• interclasare
D. OperaŃii diverse
31
• tablourile sunt prelucrate începând cu elementul din poziŃia 0.
Pozi ia 4 reprezintă deci a cincea poziŃie din tablou.
eliminare(nrelem2,a,4);
cout << "tabloul de double dupa eliminarea valorii
din pozitia 4\n";
afişare_e(nrelem2,a);
return 0;
}
Rezultatele afişate de program în fereastra consolă:
citire de la tastatura pentru un tablou
dati nr elem pentru un tablou de tip int... 5
dati elementele tabloului...
12 23 34 45 56
generare aleatoare
dati nr elem pentru un tablou de tip double...7
37.2727 60.9091 121.818 90.9091 153.636 112.727
70.9091
34
1.4.3 Utilizarea funcŃiilor din biblioteca <algorithm>
random_shuffle(adr1, adr2);
Rearanjează aleator valorile elementelor din zona de memorie dintre
adresele adr1 şi adr2.
35
remove(adr1, adr2, val);
Elimină toate elementele din zona de memorie dintre adresele adr1 şi
adr2 care sunt egale cu val. Returnează un pointer care reprezintă
noua valoare a adr2, ceea ce ne permite să determinăm câte
elemente au rămas în tablou.
sort(adr1, adr2);
Sortează crescător valorile elementelor din zona de memorie cuprinsă
între adresele adr1 şi adr2. Complexitate O(n×log n).
merge(adr1,adr2,adr3,adr4,adr5);
Interclasează valorile elementelor din zonele de memorie cuprinse între
adresele adr1 şi adr2, cu cele dintre adresele adr3 şi adr4;
rezultatul interclasării va fi depus în memorie începând de la adresa
adr5. FuncŃia returnează un pointer care reprezintă valoarea adresei
unde se termină zona de memorie unde a fost depus rezultatul
interclasării ceea ce ne permite să determinăm câte elemente conŃine
tabloul rezultat prin interclasare.
reverse(adr1, adr2);
Inversează ordinea valorilor elementelor din zona de memorie cuprinsă
între adresele adr1 şi adr2.
36
upper_bound(adr1, adr2, val);
Căutare binară. Apelabilă în acelaşi condiŃii ca funcŃiile anterioare.
Dacă val apare în tablou returnează adresa de după ultima apariŃie a
acesteia. Dacă nu apare, returnează adr2.
Programul următor ilustrează utilizarea acestor funcŃii.
int main()
{ int n,n1;
37
cout << "dati nr elem din tabl cu care lucrati:";
cin >> n;
int a[n+5], a1[n+5];
double b[n+5];
cout << "umplere cu o valoare data\n";
fill(a,a+n,30);
afişare_e(n,a);
eliminare
dati valoarea de eliminat: 1
13 2 10 3 -45 12 8 4 5 7 9 6 11 14
cautare secventiala
dati valoarea de cautat: 7
apare in pozitia 9
contorizare
dati valoarea de numarat: 8
nr. aparitii pentru 8 = 1
interclasare
dati numarul de elemente ale celor doua tablouri 10
15
40
primul tablou
0 1 1 5 9 19 21 22 24 24
al doilea tablou
2 10 14 15 16 17 19 19 23 29 34 38 40 42 44
cautare binara
dati valoarea de cautat: 19
rezultatul cautarii binare: 1
prima aparitie e in pozitia 11
ultima aparitie e in pozitia 13
inversarea ordinii elementelor
44 42 40 38 34 29 24 24 23 22 21 19 19 19 17 16 15
14 10 9 5 2 1 1 0
De exemplu:
42
Prin aceasta am declarat o variabilă v1 care conŃine un tablou de valori
int.
v1 = v2;
43
prin aceasta se face atât alocarea de memorie pentru tabloul
din v1 cât şi atribuirile corespunzătoare.
În schimb atribuirea
a = v2;
este eronată.
variabila_vector.nume_funcŃie(parametri)
begin()
Returnează un pointer la primul element din tabloul inclus in vector.
end()
Returnează un pointer la sfârşitul tabloului (adresa de după ultimul
element din tablou)
size()
Returnează numărul de elemente din tablou (valoare de tip unsigned)
max_size()
Returnează numărul maxim de elemente pe care le-ar putea avea
tabloul.
capacity()
Returnează numărul de elemente alocate pentru tablou în acel
moment.
empty()
Testează dacă vectorul este vid
44
push_back(val)
Adaugă un element care va conŃine valoarea val la sfârşitul tabloului.
Face alocarea de memorie (după mecanismul prezentat la 7.4.1) şi
incrementează numărul de elemente conŃinut de vector (furnizat de
funcŃia size()).
pop_back()
Elimină ultimul element din tablou.
erase(adr1, adr2)
Elimină elementele aflate între adresele adr1 şi adr2 din vectorul
curent
erase(adr)
Elimină elementul aflat la adresa adr din vectorul curent.
int main()
{vector <int> v1,v2,v3(13,7);
vector <double> a;
cout<<"nr elemente din v1 = "<<v1.size()<<endl;
//dimensiune 0
cout<<"nr elemente din v3 = "<<v3.size()<<endl;
//dimensiune 13
cout << "ilustrarea modului de alocare a memoriei";
cout << "prin operatia push_back\n";
for (int i=1;i<=20;i++)
{ v2.push_back(i);
cout<<v2.size()<<'-'<<v2.capacity()<<'\t';
}
generare_aleat(v1,12,19);
cout << "vector aleator de intregi"<<endl;
afişare_e(v1);
sort(v1.begin(), v1.end());
cout << "vectorul anterior sortat"<<endl;
afişare_e(v1);
v1.pop_back();
cout << "vectorul dupa pop_back"<<endl;
afişare_e(v1);
int poz, val;
cout<<"dati pozitia de unde vreti sa eliminati";
cout << "un element...";
cin >> poz;
if (poz<0 || poz>v1.size()-1)
cout << "las-o pe alta data\n";
else
v1.erase(v1.begin()+poz);
cout << "vector dupa eliminare"<<endl;
afişare_e(v1);
cout << "dati pozitia de unde vreti sa inserati";
cout << "un element si valoarea acestuia...";
46
cin >> poz >> val;
if (poz<0 || poz>v1.size())
cout << "las-o pe alta data\n";
else
v1.insert(v1.begin()+poz,1,val);
cout << "vector dupa inserare"<<endl;
afişare_e(v1);
generare_aleat(a,10,11);
cout << "vector aleator de numere reale"<<endl;
afişare_e(a);
return 0;
}
47
vector aleator de numere reale
dimensiune vector = 10
4.54545 7.27273 5.45455 6.36364 2.72727 6.36364
8.18182 1.81818 6.36364 6.36364
#include <string>
string numevar;
Exemple:
string s1;
Prin aceasta am declarat o variabilă s1care conŃine şir de caractere
vid.
string c[100];
48
Aici am declarat o variabilă c care este un tablou de 100 de string -
uri – practic o matrice în care cele 100 linii pot conŃine şiruri de
caractere de lungimi diferite.
string nume(nre,val);
string s2(10,’#’);
variabila s2 va conŃine un şir de 10 caractere #.
cout << s;
Dacă citirea sau scrierea se face dintr-un sau într-un fişier text, în
loc de cin sau cout se va utiliza numele variabilei asociată fişierului.
variabila_string.nume_funcŃie(parametri)
50
În prezentarea care urmează, string-ul care apare în faŃa numelui
funcŃiei (şi pe care nu îl vom mai scrie) îl vom denumi string curent
(eventual şir curent).
size()
Returnează numărul de elemente din stringul curent (valoare de tip
unsigned).
length()
Returnează numărul de elemente din stringul curent (valoare de tip
unsigned).
clear()
Şterge conŃinutul stringuluui curent care devine string vid.
empty()
Testează dacă şirul este vid. Dacă da returnează 1, dacă nu,
returnează 0.
erase(poziŃie, nrc)
Şterge din poziŃia dată un număr de caractere dat.
erase(poziŃie)
Şterge din poziŃia dată toate caracterele până la sfârşitul şirului.
substr(poziŃie, nrc)
Returnează un subşir al şirului conŃinând un număr de caractere
corespunzătoare cu numărul de caractere dat care începe de la poziŃia
dată.
substr(poziŃie)
Returnează un subşir al şirului începând din poziŃia dată până la
sfârşitul şirului.
find(şir)
Returnează poziŃia primei apariŃiii a şirului de caractere “şir” în
stringul curent.
Dacă nu apare ne returnează o valoare mai mare decât dimensiunea
şirului curent.
find(caracter)
Returnează poziŃia primei apariŃii a şirului de caractere “şir” în
stringul curent.
51
Dacă nu apare ne returnează o valoare mai mare decat dimensiunea
şirului curent.
find(şir, poziŃie) şi
find(caracter, poziŃie)
Similare cu cele anterioare cu deosebirea că încep căutarea din poziŃia
dată ca al doilea parametru.
rfind()
FuncŃie similară cu find() dar care caută începând de la dreapta
(sfârşitul) stringului.
find_first_of(şir)
Returnează poziŃia primei apariŃii în stringul curent a unui caracter
din şirul “şir”.
find_first_not_of(şir)
Returnează poziŃia primei apariŃii în stringul curent a unui caracter
care nu apare în şirul dat ca parametru al funcŃiei (notat „şir”).
insert(poziŃie,şir)
Inserează în poziŃia dată şirul dintre paranteze.
insert(pozitie,nr,caracter)
Inserează în poziŃia dată caracterul dat de nr ori.
52
vector putem să nu precizăm un număr maxim de elemente pe care
aceasta l-ar putea reŃine.
int main()
{ vector <string> cuvinte;
string aux;
ofstream fout("cuvinte.out");
ifstream fin("cuvinte.in");
if (fin == NULL)
{ cout << "fisier de intrare inexistent!";
return 1;
}
sort(cuvinte.begin(),cuvinte.end());
53
putem întâlni şi vom stabili ce paşi trebuie să efectuăm pentru fiecare
dintre aceste categorii. De regulă vom utiliza un atom curent şi în
funcŃie de tipul caracterului pe care suntem la un moment dat, vom
modifica acest atom curent în mod corespunzător.
În cazul problemei noastre atomul curent va fi un cuvânt curent iar în
text putem întâlni două categorii de caractere:
• literă; în acest caz suntem „în interiorul” unui cuvânt din textul
citit. Litera respectivă trebuie adăugată la cuvântul curent (prin
concatenare);
• spaŃiu: în acest caz s-a terminat un cuvânt iar acest cuvânt l-am
reŃinut în cuvântul curent. Vom adăuga cuvântul curent la o
structură (un vector) în care vom reŃine toate cuvintele din şitul
citit, apoi vom „reseta” cuvântul curent deoarece urmează să
reŃinem în el următorul cuvânt din şirul citit.
Pentru a nu „rata” ultimul cuvânt din text, vom adăuga un spaŃiu la
sfârşitul şirului citit.
int main()
{ vector <string> cuvinte;
string text,cuvcurent;
getline(cin,text);
text += ' '; //adaugam un spatiu
for (int i=0;i<text.size();i++)
{ if (text[i] != ' ')
cuvcurent += text[i];
if (text[i] == ' ')
{ cuvinte.push_back(cuvcurent);
cuvcurent.erase(0);
}
}
cout<<"fraza are "<< cuvinte.size() << " cuvinte\n";
for (int i=0;i<cuvinte.size();i++)
cout << cuvinte[i] << endl;
return 0;
}
54
text, citirea fiecărui număr în parte este o operaŃiune lentă. Este mult
mai rapid să citim din fişier un bloc de caractere de dimensiune mare
(ideal ar fi să stabilim dimensiunea acestui bloc în funcŃie de diverse
caracteristici ale hard-diskului cum ar fi capacitatea unei piste, a unui
sector, a memoriilor de tip cache ş.a.) şi apoi să parsăm blocul
respectiv.
Problema pe care o vom rezolva are următorul enunŃ: citim un şir de
caractere alcătuit din numere naturale separate prin câte un spaŃiu. Să
calculăm şi să afişăm suma numerelor din şirul citit.
De exemplu, dacă citim şirul
12 4 8 1 6
programul va afişa rezultatul 31.
Ideea de rezolvare a acestei probleme este similară cu cea
descrisă la exemplul anterior. DiferenŃa este că atomul curent va fi un
număr şi nu un şir de caractere. Adăugarea caracterului curent la
numărul curent nu se va face printr-o simplă concatenare ci prin
determinarea valorii numerice corespunzătoare caracterului citit
(deductibilă din codul ASCII al caracterului: de exemplu; '0' are codul
48, '1' are codul 49, '2' are codul 50 etc.) urmată de aplicarea formulei:
număr_curent = număr_curent *10 + cifra;
55
din şirul de intrare (deci va fi format numai din cifre). Apoi să convetim
fiecare string în valoarea numerică pe care o reprezintă cu funcŃii
predefinite pentru conversie – în cazul exemplului nostru cu funcŃia
stoi, aceasta are ca parametru un string şi returnează valoarea
numerică corespunzătoare. Însă pentru a putea utiliza această funcŃie
trebuie să lucrăm într-un mediu de dezvoltare a aplicaŃiilor C++ apărut
după 2011.
56
1.5 Structura de date de tip listă înlănŃuită
1.5.1 GeneralităŃi
O posibilitate ar fi următoarea:
59
}
cout << endl;
}
1 2
first first
val
0 0
p p
3 4
first
val val
0 0
p p first
Fig. 1.1 Efectul instrucŃiunilor 1, 2, 3, 4 din funcŃia adginceput
60
În continuare o funcŃie care elimină primul element dintr-o listă.
61
5 6
adr adr
val p val p
0 0
first first
7
adr
val p
0
first
Fig. 1.2 Eliminarea elementului pointat de adr dintr-o listă – funcŃia elimadr
62
În continuare vom analiza operaŃiunea de inserare într-o listă a unui
element după un element a cărui adresa o cunoaştem. Efectul
instrucŃiunilor 8,...,11 este ilustrat în figura 1.3.
template <class T> void
insertadr(tipelem<T> *adr, tipelem<T>*first, T val)
{ if (first == NULL)
{ cout<<" lista e vida\n";
return;
}
if (adr == NULL)
{ cout << "adresa dupa care inseram e NULL\n";
return;
}
/*8*/ tipelem<T> * q = new tipelem<T>;
/*9*/ q->info = val;
/*10*/ q->urmt = adr->urmt;
/*11*/ adr->urmt = q;
}
8 9
adr adr
q q
val
val
0 0
first first
10 11
adr adr
q q
val val
0 0
first first
Fig. 1.3 Efectul instrucŃiunilor 8,...,11 din funcŃia insertadr
63
//program 1.8: functia main() pentru liste simple
tipelem <double> *primul1 = NULL;
int main()
{cout << "lista initiala\n";
for (int i=1;i<=10;i++)
inslainceput(primul1,i/2.0);
afişare(primul1);
cout << "lista dupa eliminarea primului element\n";
elimprimul(primul1);
afişare(primul1);
cout << "lista dupa eliminarea celui de-al treilea
element\n";
elimadr(primul1->urmt->urmt, primul1);
afişare(primul1);
cout << "lista dupa inserarea valorii 177 dupa al
doilea element\n";
insertadr(primul1->urmt, primul1, 177.0);
afişare(primul1);
}
lista initiala
5 4.5 4 3.5 3 2.5 2 1.5 1 0.5
lista dupa eliminarea primului element
4.5 4 3.5 3 2.5 2 1.5 1 0.5
lista dupa eliminarea celui de-al treilea
element
4.5 4 3 2.5 2 1.5 1 0.5
lista dupa inserarea valorii 177 dupa al doilea
element
4.5 4 177 3 2.5 2 1.5 1 0.5
65
//program 1.9: liste duble
template <class T> struct tipelem
{ T info;
tipelem <T> *urmt, *prec;
};
#define FGENERICA template <class T>
FGENERICA void afiseazasd(tipelem<T> * first)
//afişare stanga->drepta
{ tipelem<T> *p;
for (p=first;p!=NULL;p=p->urmt)
cout << p->info << ' ';
cout << endl;
}
FGENERICA void afiseazads(tipelem<T> * last)
//afişare drepta->stanga
{ tipelem<T> *p;
for (p=last; p!=NULL; p=p->prec)
cout << p->info << ' ';
cout << endl;
}
FGENERICA void adglainceput(tipelem<T> *&first,
tipelem<T> *&last, T val)
{
/*12*/ tipelem<T> *p = new tipelem<T> ;
/*13*/ p->info = val;
/*14*/ p->urmt = first;
/*15*/ p->prec = 0; //0 este echivalent cu NULL
if (first != NULL)
{
/*16!*/ first->prec = p;
/*17*/ first = p;
}
else
{
first = last = p;
}
}
/*Remarcăm în funcŃia adginceput tratarea separată a cazului în care
lista este vidă. Aceasta deoarece dacă lista este vidă, first are
valoarea NULL iar operaŃiunea de dereferenŃiere făcută prin
instrucŃiunea numerotată cu 16 în cazul în care este aplicată unui
pointer spre NULL este greşită şi produce o eroare de rulare! Efectul
instrucŃiunilor 12,...,17 este ilustrat în figura 1.4:
66
12 13
first first
0 0
... ...
val
p p
14 15
first first
0 0
... ...
val 0 val
p p
16 17
first
... ...
0 val 0 val
p first p
Fig. 1.4 Efectul instrucŃiunilor 12,...,17 din funcŃia adginceput pentru o listă dublă
}
FGENERICA void insertadr(tipelem<T> *&first,
tipelem<T> *&last, tipelem<T> *adr, T val)
{ if (adr==NULL)
{ cout << "adresa data este NULL...";return;
}
68
if (first==NULL)
{cout << "lista vida, nu avem dupa cine insera";
return;
}
tipelem<T> *p = adr->urmt;
tipelem<T> *elemnou = new tipelem<T>;
elemnou->info = val;
elemnou->urmt = p;
elemnou->prec = adr;
adr->urmt = p->prec = elemnou;
}
70
Parcurgerea unei liste de tip list se face cu un tip de pointeri
specializaŃi pentru astfel de operaŃii denumiŃi iteratori. În cazul
variabilelor de tip list, ei pot fi direcŃi (pentru parcurgere de la „stânga
la dreapta”) sau inverşi/reversibili (pentru parcurgere de la „dreapta la
stânga”). Pentru a trece de la un element la altul aceşti pointeri pot fi
incrementaŃi sau decrementaŃi.
Variabilele de tip listă vor avea numele din declaraŃia anterioară iar
elementele acestor liste duble vor avea informaŃia propriu-zisă de tipul
precizat între parantezele unghiulare. Listele conŃinute de aceste
variabile vor fi vide.
De exemplu:
list <int> L1;
reprezintă declararea unei variabile L1 care conŃine o listă cu valori
int
71
este declararea unei variabile denumită v2 care conŃine o listă cu 10
elemente cu informaŃia propriu-zisă de tip int. În toate cele 10
elemente această informaŃie este egală cu 100.
begin()
Returnează un pointer la primul element din listă.
end()
Returnează un pointer la sfârşitul listei (adresa de după ultimul element
din listă).
rbegin()
Returnează un pointer la ultimul element din listă.
rend()
Returnează un pointer la marcajul de sfârşit de listă aflat la „capătul din
stânga” al listei (util pentru parcurgerea „dreapta-stânga”).
size()
Returnează numărul de elemente din listă (valoare de tip unsigned)
max_size()
72
Returnează numărul maxim de elemente pe care le-ar putea avea lista.
empty()
Testează dacă lista este vidă.
push_back(val)
Adaugă un element care va conŃine valoarea val la sfârşitul listei.
pop_back()
Elimină ultimul element din listă.
push_front(val)
Inserează un element care va conŃine valoarea val la începutul listei.
pop_front()
Elimină primul element din listă.
insert(adr, val)
Inserează în listă înaintea elementului de la adresa adr un element
având valoarea val.
erase(adr1, adr2)
Elimină elementele aflate între adresele adr1 şi adr2 din lista
curentă.
erase(adr)
Elimină elementul aflat la adresa adr din lista curentă.
clear()
Elimină toate elemente din listă.
remove(val)
Elimină toate elementele din listă care conŃin valoarea val.
remove_if(nume_funcŃie_predicat_unară)
Elimină toate elementele din listă pentru care funcŃia „predicat”
returnează valoarea true. O funcŃie predicat unară se declară având
un singur parametru de tipul informaŃiei propriu-zise reŃinută de
elementele din listă şi returnează valoarea true dacă parametrul
satisface condiŃia dorită şi false în caz contrar. De exemplu, dacă
dorim să eliminăm toate valorile pare dintr-o listă, putem declara funcŃia
predicat astfel:
bool par(int x)
{ if (x%2 == 0) return true;
else return false;
}
iar apelul funcŃiei remove_if se va putea face astfel:
L1.remove_if(par);
sort()
Sortează crescător elemente din listă în cazul în care pentru tipul
acestora este definit operatorul <.
sort(nume_funcŃie_comparare)
Sortează crescător elemente din listă în cazul în care pentru tipul
acestora NU este definit operatorul <. În acest caz, prin funcŃia de
comparare stabilim cum vrem să se facă sortarea. Această funcŃie
trebuie să aibă doi parametri de tipul informaŃiei propriu-zisă conŃinută
de elementele listei şi să returneze valoarea true (sau 1) dacă primul
parametru e „mai mic” decât al doilea.
reverse()
Inversează ordinea elementelor din listă.
unique()
Elimină dublurile dintr-o listă sortată în cazul în care pentru tipul
acestora este definit operatorul ==.
74
unique(nume_funcŃie_predicat_binară)
Elimină dublurile dintr-o listă sortată în cazul în care pentru tipul
acestora NU este definit operatorul ==. FuncŃia predicat binară se
declară având doi parametri de tipul elementelor din listă şi returnează
valoarea true dacă cei doi parametri „sunt egali” şi false în caz
contrar.
merge(list L2)
Interclasează elementele listei curente cu elementele din lista L2. Cele
două liste trebuie să conŃină elemente de acelaşi tip iar respectivele
elemente trebuie sa fie sortate. Rezultatul interclasării se va afla în lista
curentă. Este necesar ca pentru elemente din cele două liste să fie
definit operatorul <.
front()
Returnează valoarea din primul element din listă.
back()
Returnează valoarea din ultimul element din listă.
return 0;
}
79
1.6. Structura de date de tip stivă
1.6.1. GeneralităŃi
80
• vârful stivei – o dată (un câmp) de tip int care va reprezenta
poziŃia din tablou unde urmează să fie inserat un nou element;
• şapte funcŃii care implementează operaŃiile enumerate mai sus.
În exemplul care urmează, numărul maxim de elemente din stivă este
stabilit la 10000 printr-o declaraŃie de tip #define.
bool egoala()
{ if (varf == 0) //return varf==0;
return true;
else
return false;
}
void pop()
{ if (egoala())
{ cout << "stiva e goala!\n";
return ;
}
varf--;
}
tipg top()
81
{ if (!egoala())
return st[varf-1];
else
cout << "stiva e goala!\n";
}
int nrelem()
{ return varf; }
void golire()
{ varf = 0; }
int capacitatemax()
{ return nmax; }
};
int main()
{ stiva <int> st1;
stiva <string> st2;
st1.initializare();
cout<< "sitva 1 este goala (1=da):";
cout << st1.egoala() <<"\n";
cout << "numarul maxim de elemente din stiva =";
cout << st1.capacitatemax() << "\n";
for (int i=1;i<=10;i++)
st1.push(10*i);
cout << "stiva contine ";
cout <<st1.nrelem()<<" elemente "<<"\n";
cout<< "in varful stive este valoarea ";
cout <<st1.top()<<"\n";
st1.pop();
cout << "stiva contine " <<st1.nrelem();
cout <<" elemente "<<"\n";
cout << "in varful stive este valoarea ";
cout <<st1.top()<<"\n";
cout << "golirea si afişarea stivei:" << endl;
while (!st1.egoala())
{ cout << st1.top() << ' ';
st1.pop();
}
cout << '\n';
string cuvant;
st2.initializare();
cout << "Tastati patru cuvinte...";
for (int i=1;i<=4;i++)
82
{
cin >> cuvant;
st2.push(cuvant);
}
cout<<"cuvintele in ordinea inversa a citirii\n";
while (!st2.egoala())
{ cout << st2.top() << ' ';
st2.pop();
}
return 0;
}
Vom implementa stiva sub forma unei structuri în care vom cuprinde:
• o structură reprezentând tipul elementelor listei;
• un pointer care va reŃine adresa primului element din listă;
• funcŃiile enumerate mai înainte.
OperaŃiunile de inserare şi eliminare sunt de fapt cele de inserare a
unui element în faŃa primului element dintr-o listă şi respectiv
eliminarea primului element dintr-o listă.
83
template <class tipinfo> struct stiva
{ struct element
{ tipinfo info;
element * urmt;
};
element *varf;
void initializare()
{ varf = NULL; }
bool egoala()
{ if (varf==NULL) return true;
else return false;
}
void pop()
{ if (egoala())
{ cout << "stiva goala!\n";
return;
}
element *p;
p = varf;
varf = varf->urmt;
delete p;
}
tipinfo top()
{ if (!egoala()) return varf->info;
else
cout << "stiva e goala! \n";;
}
84
int nrelem()
{ int ct=0;
element * p;
for (p=varf; p!=NULL; p=p->urmt)
{ ct++; }
return ct;
}
void clear()
{ while (!isempty())
pop();
}
};
int main()
{stiva <int> st1;
stiva <string> st2;
st1.initializare();
cout << "sitva este goala (1=da):";
cout << <<st1.egoala()<<endl;
for (int i=1;i<=10;i++)
st1.push(10*i);
cout<< "stiva contine "<<st1.nrelem();
cout << <<" elemente "<<endl;
cout << "in varful stive este " <<st1.top()<< endl;
st1.pop();
cout<<"stiva contine "<<st1.nrelem();
cout <<" elemente "<<endl;
cout << "in varful stive este " <<st1.top()<< endl;
cout << "golirea si afişarea stivei:" << endl;
while (!st1.egoala())
{ cout << st1.top() << ' ';
st1.pop();
}
cout << '\n';
string nume;
st2.initializare();
cout << "Tastati patru cuvinte...";
for (int i=1;i<=4;i++)
{ cin >> nume;
st2.push(nume);
}
cout << "numele in ordinea inversa a citirii\n";
while (!st2.egoala())
{ cout << st2.top() << '\n';
st2.pop();
85
}
return 0;
}
stack<tip_elm,tip_containr> var_stiva(var_containr);
87
D. FuncŃii conŃinute de o variabilă de tip stack
size()
Returnează numărul de elemente din stivă (valoare de tip unsigned)
empty()
Testează dacă stiva este vidă. Returnează 1 sau 0.
push(val)
Inserează un element care va conŃine valoarea val în vârful stivei.
pop()
Elimină elementul din vârful stivei.
top()
Returnează valoarea elementului din vârful stivei.
88
Expresie C Forma poloneză prefixată
a+b +ab
a-b -ab
a*b *ab
a/b /ab
a%b %ab
89
4. Pentru o expresie compusă de forma E=E1 op E2, unde E1 şi
E2 sunt expresii oarecare, avem relaŃia
fppost (E1 op E2)= fppost(E1) fppost(E2) op
90
1) Parcurgem în ordine toate caracterele expresiei.
Fie expr[i] caracterul curent
a. dacă expr[i] este operand, aceasta se
adaugă la forma poloneză;
b. dacă expr[i] este ( aceasta se pune pe
stivă
c. dacă expr[i] este ) atunci se scoate câte
un caracter de pe stivă şi se adaugă la
forma poloneză până când în varful stivei
apare ( . Aceasta se elimină din stivă
(dar evident nu se adaugă în forma
poloneză).
d.dacă expr[i] este operator atunci
d1.acesta se pune pe stivă dacă
i. stiva este goală;
ii. în vârful stivei este o (
iii. în vârful stivei este un
operator cu prioritate mai
mică;
d2.în caz contrar (deci când în vârful
stivei este un operator cu prioritate
mai mare sau egală) acesta este scos de
pe stivă şi adăugat la forma poloneză,
după care se reia pasul d.
2) După ce am parcurs expresia iniŃială,
eventualele caractere rămase pe stivă sunt
succesiv scoase de pe stivă şi adăugate în
forma poloneză.
91
Expresie Stiva Poloneză ObservaŃii
a+b*c
a+b*c a
a+b*c +←←vfS a
a+b*c +←←vfS ab
a+b*c *←←vfS ab Pasul d1, cazul iii
+
a+b*c *←←vfS abc
+
+←←vfS abc* Golirea stivei – pasul 2) din
algoritm
abc*+ Continuă golirea stivei
92
//program 1.13 determinarea formei poloneze
//postfixată
#include <iostream>
#include <stack>
#include <string>
using namespace std;
stack <char> st;
string expr, polo;
int main()
{ expr = "a+b/(c-d*(x+y-z)+t)";
expr = '('+expr +')'; //incadrare intre paranteze
for (int i=0;i<expr.size(); i++)
{ if (expr[i]>='a'&&expr[i]<='z')
polo += expr[i];
if (expr[i] == '(' )
st.push('(');
if (expr[i] == ')' )
{
while (st.top() != '(' )
{ polo += st.top();
st.pop();
}
st.pop();
}
if (expr[i] == '+'||expr[i] == '-')
if (st.empty()||st.top() == '(' )
st.push(expr[i]);
else
{ polo += st.top();
st.pop();
i--;
}
if (expr[i] == '*'||expr[i] == '/')
if(st.empty()||st.top()=='('
||st.top()=='+'||st.top()=='-' )
st.push(expr[i]);
else
{ polo += st.top();
st.pop();
i--;
}
}
cout<<"forma poloneza corespunzatoare expresiei\n";
cout << expr << " este "<< polo << endl;
return 0;}
93
Rezultatul programul afişat în fereastra consolă este:
94
1.7 Structura de date de tip coadă
1.7.1 GeneralităŃi
Este important ca aceste operaŃii (sau cel puŃin cele din primul grup) să
fie de complexitate O(1).
95
1.7.2. Implementarea unei cozi folosind structura de
date de tip tablou unidimensional
int nrelem()
{ return ultim - prim; }
void goleste()
{ prim = ultim = 0; }
int capacitatemax()
{ return nmax; }
};
int main()
{ coada<int> q1;
coada <string> q2;
q1.initializare();
cout << "coada 1 este goala (1=da):";
cout << q1.egoala() <<"\n";
cout << "numarul maxim de elemente din coada =";
cout << q1.capacitatemax() << "\n";
for (int i=1;i<=10;i++)
q1.push(10+i);
cout << "coada contine " <<q1.nrelem();
cout <<" elemente "<<"\n";
cout << "in prima pozitie a cozii este ";
cout << q1.fata()<<"\n";
cout << "in ultima pozitie a cozii este ";
cout <<q1.spate()<<"\n";
q1.pop();
cout << " coada contine " <<q1.nrelem();
cout << " elemente "<<"\n";
cout << "in prima pozitie a cozii este ";
cout << q1.fata()<<"\n";
cout << "in ultima pozitie a cozii este ";
cout << q1.spate()<<"\n";
cout << "golirea si afişarea cozii:" << endl;
while (!q1.egoala())
{ cout << q1.fata() << ' ';
q1.pop();
97
}
cout << '\n';
string cuvant;
q2.initializare();
cout << "Tastati 4 cuvinte...";
for (int i=1;i<=4;i++)
{ cin >> cuvant;
q2.push(cuvant);
}
cout << "numele in ordinea citirii\n";
while (!q2.egoala())
{ cout << q2.fata() << ' ';
q2.pop();
}
return 0;
}
101
B. Utilizarea variabilelor de tip queue
queue<tip_elm,tip_containr>var_coada(var_containr);
102
C. OperaŃii implementate pentru variabile de tip queue
size()
Returnează numărul de elemente din stivă (valoare de tip unsigned)
empty()
Testează dacă stiva este vidă. Returnează 1 dacă e goală sau 0 dacă
nu e goală.
push(val)
Inserează în coadă un element care va conŃine valoarea val.
pop()
Elimină un element din coadă (conform principiului FIFO).
front()
Returnează valoarea elementului din faŃa cozii.
back()
Returnează valoarea ultimului element din coadă.
103
1.7.5. AplicaŃii ale structurii de tip coadă
104
numărul egal cu prima cifră (cifra sutelor) a
respectivului element.
6. Reconstruim tabloul tab în acelaşi mod ca la
paşii 2 sau 4. După refacerea tabloului prin
extragerea tuturor valorilor din cele zece
cozi, tabloul va fi sortat crescător.
Dacă numerele din şir au mai mult de trei cifre se repetă paşii de
mai sus luând în considerare cifrele de la rangurile următoare (cifrele
miilor, zecilor de mii etc.).
Dacă şirul de valori are n elemente, atunci complexitatea
algoritmului va fi O(n), dar numărul de paşi este direct proporŃional
cu numărul maxim de cifre pe care îl au valorile din şir şi cu timpul
necesar accesării unei cifre dintr-un număr (în baza 10 acest acces se
face prin împărŃiri).
Există diverse variante mai rapide ale acestui algoritm. De
exemplu, putem considera numere din şir ca fiind date într-o bază
superioară, de exemplu 10000. Aceasta va presupune utilizarea a
10000 de cozi, dar numărul de distribuiri a numerelor în cozi urmată de
refacerea şirului cu valorile extrase din cozi se va reduce de patru ori.
Faptul că se utilizează de 10000 de ori mai multe cozi nu afectează
mărimea zonei de memorie ocupată dacă se utilizează cozi
implementate pe baza unei structuri de date dinamice.
B. Algoritmul Lee
107
1. Citim datele de intrare (preferabil dintr-un
fişier text)
2. Marcăm toate elementele din M cu o valoare
arbitrară VA
3. Bordăm matricea A cu 1 (poziŃii ocupate)
4. Inserăm poziŃia de plecare în Q
5. M[poziŃia de plecare] = 1
6. Repetă
a. extragem în PC poziŃia curentă din Q
b. pentru fiecare poziŃie PV vecină cu PC
i. dacă A[PV]==0 şi M[PV]== VA atunci
1. i1. M[PV] = M[PC]+1
2. i2. Inserăm PV în Q
3. sfârşit_dacă
ii. sfârşit_pentru
7. până când Q este vidă sau PV este poziŃia
finală
Determinarea drumului de lungime minimă se face conform
algoritmului prezentat la începutul subcapitolului.
Observăm că nu este neapărată nevoie de două matrice – am
putea folosi o singură matrice în care să atribuim elementelor care
corespund poziŃiilor cu obstacole o valoare convenabilă, de exemplu
valoarea negativă -1, iar elementelor corespunzătoare poziŃiilor libere o
valoare arbitrară convenabilă evident diferită de -1.
Există şi alte variante ale algoritmului – de exemplu poziŃiile pe
unde trece pionul conŃin anumite valori şi trebuie găsită o rută de cost
minim sau maxim.
Complexitatea algoritmului este O(n) unde prin n am notat numărul
de poziŃii din labirint (egal cu numărul de elemente din matricea care
descrie labirintul).
108
1.8. Cozi cu două capete (cozi duble)
109
Variabila va avea numele din declaraŃie anterioară iar elementele
tabloului conŃinut de coada dublă vor avea tipul dintre parantezele
unghiulare. Cozile duble conŃinute de aceste variabile vor fi vide (nu
are alocată memorie pentru niciun element).
De exemplu:
deque <int> dq1;
//o variabilă dq1 care conŃine o coadă dublă de
//valori int
110
1.8.3. OperaŃii implementate pentru variabile de tip
deque
begin()
Returnează un pointer la primul element din deque.
end()
Returnează un pointer la sfârşitul cozii duble (adresa de după ultimul
element din deque)
rbegin()
Returnează un pointer la ultimul element din deque.
rend()
Returnează un pointer la marcajul de sfârşit de listă aflat la „capătul din
stânga” al cozii duble (util pentru parcurgerea „dreapta-stânga”).
size()
Returnează numărul de elemente din deque (valoare de tip
unsigned)
111
max_size()
Returnează numărul maxim de elemente pe care le-ar putea avea
coada dublă.
empty()
Testează dacă coada dublă este vidă
push_back(val)
Adaugă un element care va conŃine valoarea val la sfârşitul tabloului.
pop_back()
Elimină ultimul element din tablou.
push_front (val)
Adaugă un element care va conŃine valoarea val la începutul cozii
duble.
pop_front()
Elimină primul element din coada dublă.
front()
Returnează valoarea din primul element din coada dublă.
back()
Returnează valoarea ultimului element al cozii duble.
112
1.9 Probleme propuse
113
Problema 3: Ce afişează instrucŃiunile notate cu 1 şi 2 din programul
alăturat?
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{ int a1[20], n=10, x=20;
for (int i=0;i<n;i++)
a1[i] = i;
fill(a1, a1+4, x);
/*1*/for (int i=0;i<n;i++) cout << a1[i] << ' ';
cout << endl;
sort(a1+2, a1+n-2);
/*2*/for (int i=0;i<n;i++) cout << a1[i] << ' '; }
115
doilea şir de caractere, care să conŃină prenumele, urmat de exact un
spaŃiu şi apoi numele din şirul citit iniŃial.
Exemplu: dacă se citeşte şirul:
Popescu Vasile
se va construi şi apoi se va afişa pe ecran şirul
Vasile Popescu
118
while (!q2.empty())
{ cout << q2.front()<<' ';
q2.pop();
}
}
119
numere din şir care începe din poziŃia i, (deci suma numerelor xi, xi+1,
..., xi+k-1) este maximă. Programul va afişa pe ecran valoarea sumei
maxime şi poziŃia din şir unde începe respectiva secvenŃă.
Exemplu: dacă fişierul numere.in conŃine valorile
8 3
2 9 4 7 5 2 9 9
programul va afişa valorile
20 2 (suma maximă a trei numere aflate în poziŃii alăturate din şir se
obŃine pentru numerele 9, 4, 7). ConcepeŃi pentru rezolvarea acestei
probleme un algoritm cu complexitatea O(n).
120