Documente Academic
Documente Profesional
Documente Cultură
date
Cuprins
INTRODUCERE ................................................................................ 6
CAPITOLUL 1. STRUCTURI DE DATE şi COLECŢII DE DATE ….7
1.1 Generalităţi şi definiţii……………………………………………7
1.1.1 Noţiunea de dată ....................................................... ..……7
1.1.2 Definiţia noţiunii structură de date ..................................... 7
1.1.3 Clasificări ale structurilor de date ........................................ 9
1.2 Colecţii de date………………………………….……….…......11
1.2.1 Exemple de colecţii: .......................................................... 11
1.2.2 Caracteristicile generale ale colecţiilor ............................. 12
1.2.3 Operaţii specifice colecţiilor .............................................. 12
1.2.4 Parcurgerea colecţiilor ...................................................... 13
1.2.5 Criterii de clasificarea a colecţiilor .................................... 13
1.3 Tipuri de date fundamentale în C/C++…………………… …16
1.3.1 Tipuri simple de date......................................................... 16
1.3.2 Pointeri .............................................................................. 19
1.4 Structura de date de tip tablou ............................................ 29
1.4.1 Modul de declarare ........................................................... 29
1.4.2 Prelucrări uzuale efectuate asupra structurii de date de tip
tablou ............................................................................. …..30
1.4.3 Utilizarea funcţiilor din biblioteca <algorithm> .................. 35
1.4.4 Utilizarea containerului <vector> ...................................... 41
A. Prezentarea generală a variabilelor de tip vector .......... 41
B. Utilizarea variabilelor de tip vector ................................. 42
C. Funcții conținute de un vector ........................................ 44
1.4.5 Containerului <string> ....................................................... 48
A. Utilizarea variabilelor de tip string .................................. 48
B. Proprietăți generale ale variabilelor de tip string ............ 49
C. Citirea și afișarea unui string .......................................... 50
D. Funcții conținute de un string ......................................... 50
1.5 Structura de date de tip listă înlănţuită .............................. 57
1.5.1 Generalităţi........................................................................ 57
1.5.2 Liste dinamice simple ....................................................... 58
1.5.3 Liste dinamice duble ......................................................... 64
1.5.4 Containerul list .................................................................. 70
A. Prezentarea generală a variabilelor de tip list ................ 70
B. Utilizarea variabilelor de tip list ....................................... 71
C. Operații implementate pentru variabilele de tip list ........ 72
D. Funcții conținute de o variabilă de tip list ....................... 72
E. Accesarea elementelor unei liste cu iteratori .................. 75
1.6. Structura de date de tip stivă ............................................. 80
1.6.1. Generalităţi....................................................................... 80
1.6.2. Implementarea unei stive folosind structura de date de tip
tablou unidimensional .......................................................... 80
1.6.3. Implementarea unei stive folosind structura de date de listă
înlănţuită .............................................................................. 83
1.6.4. Containerul stack ............................................................. 86
A. Prezentarea generală a variabilelor de tip stack ............ 86
B. Utilizarea facilităților oferite de containerul stack ........... 86
C. Operații implementate pentru variabilele de tip stack .... 87
D. Funcții conținute de o variabilă de tip stack ................... 88
1.6.5. Aplicaţie a structurii de date de tip stivă: forma fără
paranteze a unei expresii matematice (forma poloneză) ... 88
1.7 Structura de date de tip coadă ............................................ 95
1.7.1 Generalităţi........................................................................ 95
1.7.2. Implementarea unei cozi folosind structura de date de tip
tablou unidimensional .......................................................... 96
1.7.3. Implementarea unei cozi folosind structura de date de listă
înlănţuită .............................................................................. 98
1.7.4. Containerul queue.......................................................... 100
A. Prezentarea generală a variabilelor de tip queue ........ 101
B. Utilizarea variabilelor de tip queue ............................... 102
C. Operații implementate pentru variabilele de tip queue.103
D. Funcții conținute de o variabilă de tip queue ............... 103
1.7.5. Aplicaţii ale structurii de tip coadă ................................. 104
A. Sortarea radix (sortarea cu găleți) ................................ 104
B. Algoritmul Lee .............................................................. 106
1.8. Cozi cu două capete (cozi duble) ..................................... 109
1.8.1 Generalităţi; containerul <deque> .................................. 109
1.8.2 Utilizarea variabilelor de tip deque .................................. 109
1.8.3. Operaţii implementate pentru variabile de tip deque ..... 111
1.8.4. Funcţii conţinute de o variabilă de tip deque ................. 111
1.9 Probleme propuse .............................................................. 113
CAPITOLUL 2. GRAFURI NEORIENTATE.................................. 121
2.1 Introducere în teoria grafurilor .......................................... 121
2.2. Definiţia noţiunii de graf ................................................... 122
2.3. Noţiuni de bază pentru grafuri neorientate ..................... 124
2.4. Exemple de grafuri particulare……………………………..126
2.5. Lanţ, ciclu, conexitate……………………………………..…129
2.6. Metode de reprezentare pentru grafuri neorientate….,...134
2.6.1 Matricea de adiacenţă .................................................... 134
2.6.2 Liste de adiacenţă ........................................................... 135
2.6.3 Lista de muchii ................................................................ 136
2.7 Traversarea grafurilor ........................................................ 142
2.7.1 Traversarea în adâncime (DFS) ..................................... 142
2.7.2 Traversarea în lăţime (BFS)............................................ 145
2.7.3 Aplicaţii ale algoritmilor de traversare ............................. 152
A. Determinarea muchiilor critice dintr-un graf conex ....... 152
B. Determinarea unui ciclu eulerian întrun graf eulerian ... 154
2.8 Probleme propuse .............................................................. 159
CAPITOLUL 3. GRAFURI ORIENTATE .................................. .... 163
3.1 Introducere .......................................................................... 163
3.2 Noţiuni de bază ................................................................... 164
3.3. Drum, circuit, tare-conexitate ........................................... 165
3.4 Metode de reprezentare a digrafurilor .............................. 167
3.4.1 Matricea de adiacenţă .................................................... 167
3.4.2 Liste de adiacenţă ........................................................... 168
3.4.3 Lista de arce ................................................................... 169
3.5 Drumuri de cost minim în grafuri orientate ...................... 175
3.5.1 Reprezentarea grafurilor ponderate ................................ 176
3.5.2 Algoritmul Dijkstra ........................................................... 177
3.5.3 Algoritmul Roy-Floyd....................................................... 181
3.6 Flux maxim într-o reţea de transport ................................ 186
3.6.1 Definiţii ............................................................................ 186
3.6.2 Determinarea fluxului maxim într-o reţea de trasport ..... 187
3.7 Sortarea topologică ............................................................ 194
3.8 Probleme propuse .............................................................. 198
INTRODUCERE
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
CAPITOLUL 2.
GRAFURI NEORIENTATE
121
2.2. DefiniŃia noŃiunii de graf
122
Evident că în aplicaŃiile cu grafuri este util ca nodurile să fie
identificate printr-un număr, literă etc. Aceste notaŃii ale nodurilor se
numesc etichete. Uzual, etichetele sunt numere naturale
consecutive începând cu 1. În toate capitolele care urmează vom
considera că etichetele nodurilor grafurilor respectă această
ipoteză.
123
2.3. NoŃiuni de bază pentru grafuri neorientate
124
1 7
2
4 6
3 5
fig. 2.3 graf neorientat
125
2.4. Exemple de grafuri particulare
1) graf complet Kn
Proprietate
n × (n − 1)
În graful complet Kn , m = = Cn2
2
Pe baza acestei formule se poate demonstra că numărul total de
n ( n −1)
grafuri neorientate cu n noduri este 2 2
2) graf bipartit
1
4
2
3
4) graf regulat
Este un graf în care toate nodurile au acelaşi grad. Figura 2.5 ilustrază
un astfel de graf:
1
4
2
3
5) graf planar
Este un graf care poate fi desenat fără ca pe desen să se
intersecteze muchii.
1 1
2 2
4 4
5
3 5 3
127
7) grafuri izomorfe
Două grafuri sunt izomorfe dacă unul se poate obŃine din celălalt
printr-o renumerotare a nodurilor. Grafurile din figuria 2.8 sunt
izomorfe.
3
1
2 2
4
4 3 1
fig. 2.8 grafuri izomorfe
8) subgraf
Un subgraf al unui graf dat se obŃine eliminând din graful iniŃial
unul sau mai multe noduri şi a toate muchiile incidente acestor noduri.
3 3
1
2 2
4 4
6 5 6 5
fig. 2.9 graf iniŃial fig. 2.10: subgraf al grafului iniŃial
rezultat prin eliminarea nodului 1
9) graf parŃial
Un graf parŃial al unui graf dat se obŃine prin eliminarea din
graful iniŃial a uneia sau mai multor muchii.
3 3
1 1
2 2
4 4
6 5 6 5
fig. 2.11: graf iniŃial fig. 2.12: graf parŃial al grafului iniŃial
rezultat prin eliminarea muchiilor [6,1],
[2,1], [5,6]
128
2.5. LanŃ, ciclu, conexitate
2
4 6
3 5
fig. 2.13 graf neorientat
• lanŃ elementar de lungime 3: [1,3,4,6];
• lanŃ neelementar de lungime 4: [4,1,2,4,5[;
• ciclu elementar de lungime 5: [2,3,5,4,1,2];
• ciclu neelementar de lungime 6: [1,2,3,4,5,3,1];
129
Un graf conex este un graf în care există cel puŃin un lanŃ între
oricare două noduri.
Dacă un graf nu este conex, el este format din componente
conexe. O componentă conexă este un subgraf maximal conex al unui
graf dat. Graful din fig. 2.15 are trei componente conexe.
Graf eulerian
LanŃ eulerian într-un graf neorientat este un lanŃ simplu care conŃine
toate muchiile grafului. În graful din figura 2.16, lanŃul
[1,2,6,5,4,6,1,3] este eulerian.
3
1
2
4
6 5
fig. 2.16: graf cu lanŃ eulerian
130
2 8 9
1
3 10 7
4 5
6 11
fig. 2.17: graf cu ciclu eulerian
Teorema 5
Un graf este eulerian dacă şi numai dacă oricare vârf al său are
gradul par iar toate muchiile lui se află în aceeaşi componentă conexă.
Deci graful poate fi eulerian şi dacă conŃine noduri izolate.
Graf hamiltonian
3
1
2
4
6 5
fig. 2.18: graf cu lanŃ hamiltonian
131
3
1
2
4
6 5
fig. 2.19: graf cu ciclu hamiltonian
Teorema 6
Un graf este hamiltonian dacă este conex şi dacă oricare vârf al
său are gradul cel puŃin egal cu n/2.
De-a lungul timpului, pentru determinarea unui ciclu hamiltonian printr-
un algoritm cu complexitate cât mai bună s-a depus un efort
remarcabil. Evident putem determina un astfel de ciclu generând câte o
permutare a mulŃimii de noduri şi verificând dacă ea corespunde uni
ciclu, dar o astfel de rezolvare are complexitatea O(n!). Richard
Belmann în [1] prezintă o soluŃie de complexitate O(n2×2n).
Un graf neorientat este arbore dacă este conex şi aciclic. Figura 2.20
ilustrează un arbore cu 6 noduri.
3
1
2
4
6 5
fig. 2.20: graf neorientat de tip arbore
Pentru acest tip de grafuri există câteva teoreme importante:
132
4. Orice muchie eliminăm dintr-un arbore, graful care rezultă
este neconex.
5. Orice muchie adăugăm într-un arbore, graful care rezultă va
conŃine un ciclu.
6. Orice graf conex care are n noduri şi n-1 muchii este arbore.
7. Orice graf aciclic care are n noduri şi n-1 muchii este arbore.
Un graf neorientat este de tip pădure dacă este neconex, dar fiecare
componentă conexă este un subgraf de tip arbore. Figura 2.21
ilustrează un graf neorientat de tip pădure cu 9 noduri.
1 7
8
2
9 4 6
3 5
fig.2.21 graf neorientat de tip pădure
133
2.6. Metode de reprezentare pentru grafuri neorientate
OperaŃie Complexitate
Test dacă două noduri sunt adiacente O(1)
Determinarea tuturor nodurilor adiacente cu un nod dat O(n)
Determinarea muchiilor grafului O(n2)
134
2.6.2 Liste de adiacenŃă
În programe este util ca valorile din liste pot fi sortate, mai ales dacă
configuraŃia grafului nu se modifică pe parcursul programului.
OperaŃie Complexitate
Test dacă două noduri sunt adiacente O(n) sau O(log n)
dacă listele sunt
sortate
Determinarea tuturor nodurilor adiacente cu O(n)
un nod dat
Determinarea muchiilor grafului O(m)
135
2.6.3 Lista de muchii
[1,2],[1,6],[1,3],[2,6],[3,4],[5,4],[4,6],[5,6]
În programe este util ca valorile din cadrul unei perechi să fie sortate iar
întreaga listă să fie sortată după prima valoare din fiecare pereche iar
la egalitatea valorilor din prima poziŃie după a doua valoare din
pereche.
OperaŃie Complexitate
Test dacă două noduri sunt adiacente O(m) sau O(log m)
dacă sortăm lista
Determinarea tuturor nodurilor adiacente cu O(m)
un nod dat
Determinarea muchiilor grafului O(m)
136
• pentru matricea de adiacenŃă, o matrice de valori de tip bool. În
declararea acesteia am presupus că valoarea lui n nu
depăşeşte 100, deci matricea este declarată cu 101 linii şi 101
coloane (în programe nu vom folosi linia şi coloana 0);
• pentru listele de adiacenŃă un tablou de 101 cozi cu două
capete;
• pentru lista de muchii un vector de perechi, în care pentru tipul
perechilor am utilizat un tip structură predefinit (denumit pair).
Acesta este un tip structură în care cele două câmpuri care îl
alcătuiesc au numele first şi second iar tipul acestor câmpuri
poate fi orice tip din C şi se precizează prin paranteze
unghiulare (ca la toate sintaxele care apar la utilizarea tipurilor
generice).
FuncŃiile implementate în program realizează:
• construirea celor trei modalităŃi de reprezentare a grafului pentru
valorile citite dintr-un fişier text al cărui nume este dat prin unul
dintre parametri funcŃiilor respective;
• afişarea fiecărei structuri care reprezintă graful;
• determinarea dacă două noduri date ca parametri sunt
adiacente.
bool A[nmax][nmax];
vector <pair <int,int> > LM;
deque <int> LA[nmax];
afisarelistead(LA,n);
cout<< "dati 2 noduri (intre 1 si " << n << "): ";
cin >> v1 >> v2;
bool rez1 = adiacentelistead(LA,n,v1,v2);
cout << "nodurile "<<v1 << " si " << v2;
if (rez1) cout << " sunt adiacente" << endl;
else cout << " nu sunt adiacente" << endl;
cout << endl;
cout << "-------------------------------------\n";
cout << "LISTA DE MUCHII \n";
citirelistam(LM,n,m,"graf.in");
sort(LM.begin(), LM.end());
afisarelistam(LM);
cout<< "dati 2 noduri (intre 1 si " << n << "): ";
cin >> v1 >> v2;
bool rez3 = adiacentelistam(LM,n,v1,v2);
cout << "nodurile "<<v1 << " si " << v2;
if (rez3)
cout << " sunt adiacente" << endl;
else
cout << " nu sunt adiacente" << endl;
}
Dacă în fişierul de intrare introducem datele pentru graful din figura
2.19, programul va afişa în fereastra consolă următoarele:
MATRICEA DE ADIACENTA
0 1 1 0 0 1
1 0 0 0 0 1
1 0 0 1 0 0
0 0 1 0 1 1
0 0 0 1 0 1
1 1 0 1 1 0
140
dati 2 noduri (intre 1 si 6): 2 4
nodurile 2 si 4 nu sunt adiacente
----------------------------------------------
LISTELE DE ADIACENTA
1 : 2 3 6
2 : 1 6
3 : 1 4
4 : 3 5 6
5 : 4 6
6 : 1 2 4 5
dati 2 noduri (intre 1 si 6): 2 5
nodurile 2 si 5 nu sunt adiacente
----------------------------------------------
LISTA DE MUCHII
1-2 ; 1-3 ; 1-6 ; 2-6 ; 3-4 ; 4-5 ; 4-6 ; 5-6
dati 2 noduri (intre 1 si 6): 2 1
nodurile 2 si 1 sunt adiacente
141
2.7 Traversarea grafurilor
142
primul vecin nevizitat al său şi continuăm
parcurgerea în acelaşi mod. Dacă nici
acest nod nu mai are vecini nevizitaŃi,
revenim în nodul său părinte şi continuăm
în acelaşi mod, până când toate nodurile
accesibile din nodul de start sunt
vizitate.
1 7
8
2
9 4 6
3 5
fig.2.22 graf neorientat pentru ilustrarea metodelor
de traversare a grafurilor
143
Arborele de parcurgere rezultat în urma acestei traversări este
următorul:
1 7
8
2
9 4 6
3 5
fig.2.23 arbore de parcurgere la traversarea în
adâncime a grafului din figura 2.22 pornind din nodul 1
144
2.7.2 Traversarea în lăŃime (BFS)
145
determinării acestor muchii este următoarea: presupunem că prin
aplicarea algoritmului de parcurgere în adâncime am ajuns la un
moment dat în nodul x şi că în T[x] avem deja memorat predecesorul
nodului x. Atunci:
• pentru fiecare vecin i nevizitat a lui x apelăm parcurgerea în
adâncime;
• pentru fiecare vecin i care deja a fost vizitat, cu excepŃia
predecesorului lui x, muchia care leagă pe x de i închide un
ciclu, deci este de întoarcere (şi o reŃinem într-un vector de
muchii declarat ca în programul 2.1). Pentru a nu afişa de două
ori fiecare muchie de întoarcere, vom memora doar muchiile în
care i< x.
bool vizitat[nmax];
void dfs(int x)
{ cout << x << ' ';
146
vizitat[x] = true;
for (int i=1;i<=n;i++)
if (A[x][i]==1 && !vizitat[i])
{ vizitat[i]=true;
T[i]=x;
dfs(i);
}
}
void dfs1(int x)
{ vizitat[x] = true;
for (int i=1;i<=n;i++)
if (A[x][i]==1)
if (!vizitat[i])
{ vizitat[i]=true;
T[i]=x;
dfs1(i);
}
else
{ if (T[x] != i)
if (i<x)
{ muchie aux;
aux.first = i;
aux.second = x;
mi.push_back(aux);
}
}
}
void bfs(int x)
{ queue <int> q;
T1[x]=0;
dist[x]=0;
vizitat[x]=1;
cout << x << ' ';
q.push(x);
while (!q.empty())
{ int v = q.front(); //suntem in nodul v
q.pop();
for (int i=0;i<LA[v].size();i++)
{ int y = LA[v][i];
if (vizitat[y] == false)
{ cout << y << ' ';
T1[y] = v;
dist[y] = dist[v]+1;
147
vizitat[y]=true;
q.push(y);
}
}
}
}
int main()
{ citirematricead(A,n,m,"graf.in");
//afisarematricead(A,n);
cout << "parcurgerea in adancime..."<<endl;
fill(vizitat, vizitat+n+1,false);
dfs(1);
cout << endl<<"vectorul de predecesori afisat in
formatul i-T[i]..."<<endl;
for (int i=1;i<=n;i++)
cout << i << '-'<<T[i]<<" ";
cout << endl << "situatia nodurilor
vizitat/nevizitat" << endl;
for (int i=1;i<=n;i++)
cout << i << '-' << vizitat[i]<<" ";
cout << endl;
citirelistead(LA,n,m,"graf.in");
//afisarelistead(LA,n);
cout << "parcurgerea in latime..."<<endl;
fill(vizitat, vizitat+n+1,false);
bfs(1);
cout << endl<<"vectorul de predecesori afisat in
formatul i-T[i]..."<<endl;
for (int i=1;i<=n;i++)
cout << i << '-'<<T1[i]<<" ";
cout << endl<<"vectorul de distante afisat in
formatul i-d[i]..."<<endl;
for (int i=1;i<=n;i++)
cout << i << '-'<<dist[i]<<" ";
fill(vizitat, vizitat+n+1,false);
dfs1(1);
cout << endl<<"muchiile de intoarcere:" <<endl;
for (int i=0;i<mi.size();i++)
cout << mi[i].first<<'-'<<mi[i].second<<" ";
return 0;
}
148
Programul va afişa în fereastra consolă următoarele (având ca date de
intrare graful din figura 2.22):
parcurgerea in adancime...
1 2 3 5 6 7 4 9 8
vectorul de predecesori afisat in formatul i-T[i]...
1-0 2-1 3-2 4-7 5-3 6-5 7-6 8-1 9-2
situatia nodurilor vizitat/nevizitat
1-1 2-1 3-1 4-1 5-1 6-1 7-1 8-1 9-1
parcurgerea in latime...
1 2 3 4 8 9 5 7 6
vectorul de predecesori afisat in formatul i-T[i]...
1-0 2-1 3-1 4-1 5-3 6-5 7-4 8-1 9-2
vectorul de distante afisat in formatul i-d[i]...
1-0 2-1 3-1 4-1 5-2 6-3 7-2 8-1 9-2
muchiile de intoarcere:
1-3 1-4
149
3
6
1
4
7
2 5
fig.2.24 graf pentru exemplificarea algoritmului de determinare
a timpilor de atingere şi terminare a nodurilor unui graf
int culoare[nmax];
int T[nmax]; //pentru memorarea predecesorilor
int tg[nmax], tn[nmax], timp=0;
int n,m;
deque <int> LA[nmax];
void dfs(int x)
{ cout << x << ' ';
culoare[x] = gri;
timp++;
tg[x] = timp;
for (int i=0;i<LA[x].size();i++)
{
int vecin = LA[x][i];
if (culoare[vecin]==alb)
{
T[vecin]=x;
dfs(vecin);
150
}
}
timp++;
tn[x] = timp;
culoare[x] = negru;
}
int main()
{citirelistead(LA,n,m,"graf.in");
//afisarelistead(LA,n);
fill(culoare, culoare+n+1, alb);
timp = 0;
cout << "ordinea de vizitare a nodurilor:"<<endl;
for (int x=1;x<=n;x++)
if (culoare[x] == alb)
{
dfs(x);
cout << endl;
}
cout<<"vectorul de predecesori afisat in formatul";
cout<<" i-T[i]:"<<endl;
for (int i=1;i<=n;i++)
cout << i << '-'<<T[i]<<" ";
cout << endl << "timpii de colorare gri/negru";
cout<<" a nodurilor:" << endl;
for (int i=1;i<=n;i++)
cout<<i<<": "<<tg[i]<<"/"<<tn[i]<<endl;
return 0;
}
Pentru graful din figura 2.24 programul va afişa în fereastra consolă
următoarele:
ordinea de vizitare a nodurilor:
1 2 4 3 5
6 7
vectorul de predecesori afisat in formatul i-T[i]:
1-0 2-1 3-4 4-2 5-4 6-0 7-6
timpii de colorare gri/negru a nodurilor:
1: 1/10
2: 2/9
3: 4/5
4: 3/8
5: 6/7
6: 11/14
7: 12/13
151
2.7.3 AplicaŃii ale algoritmilor de traversare
Muchiile critice ale unui graf conex sunt acele muchii pe care
dacă le eliminăm, graful nu mai este conex. Este simplu să observăm
că muchiile critice sunt cele care nu fac parte din niciun ciclu.
Pentru a determina aceste muchii am putea proceda astfel:
• să eliminăm fiecare muchie şi apoi să verificăm dacă
graful parŃial rezultat este conex. Algoritmul este corect,
dar complexitatea lui este mare: O(m×(m+n));
• să reŃinem muchiile care formează arborele de
parcurgere şi apoi să determinăm muchiile care nu apar
printre acestea. Dacă sortăm structura în care reŃinem
muchiile din arborele de parcurgere, atunci complexitatea
algoritmului va fi O(m+n) pentru parcurgere şi
O(m×log2n) pentru căutarea fiecărei muchii din graful
iniŃial în structura cu muchiile arborelui de parcurgere
(acesta va avea n-1 muchii). Cum m+n « m×log2n,
complexitatea algoritmului va fi O(m×log2n);
• să modificăm algoritmul de traversare în adâncime după
cum urmează: reŃinem într-un tablou pentru fiecare nod
„nivelul” acestuia. Acest nivel este 1 pentru nodul de
plecare iar pentru fiecare dintre celelalte noduri se
calculează în funcŃia recursivă care implementează
traversarea în adâncime şi este minimul dintre:
- 1+nivelul nodului predecesor;
- nivelul nodurilor adiacente de care respectivul nod
este legat prin muchii de întoarcere.
Evident muchiile critice pot fi numai cele parcurse prin algoritmul
de traversare. O astfel de muchie este critică dacă nivelul nodului
curent este strict mai mare decât nivelul nodului predecesor.
De exemplu, pentru graful din figura 2.22, dacă pornim
traversarea din nodul 1, atunci:
- nodul 1 are nivelul 1;
- nodul 2 are în urma apelului recursiv nivelul 2 ;
- nodurile 3, 4 şi 5 au iniŃial în urma apelurilor recursive nivelurile
3, 4 respectiv 5, dar când din nodul 5 vom „vedea” nodul 2, atunci
nivelul aceluia devine 2, valoare care va fi atribuită şi nivelului nodurilor
4 şi 3.
- nodul 6 are în urma apelului recursiv nivelul 3.
152
2
1
5 6
3 4
fig.2.25 graf pentru exemplificarea algoritmului de
determinare a muchiile critice
154
• trebuie să verificăm dacă graful are toate muchiile într-o
singură componentă conexă; aceasta se face printr-o
parcurgere urmată de verificarea dacă există noduri
nevizitate care au grad nenul. Dacă există astfel de noduri
rezultă că graful are muchii în cel puŃin două componente
conexe, deci nu este eulerian!
• Dacă am trecut de această primă verificare să verificăm dacă
toate gradele nodurilor din graf sunt valori pare. Având
tabloul de grade, acest lucru se poate face foarte uşor.
În cazul în care graful este eulerian, determinarea ciclului
eulerian se poate face după următorul algoritm în care utilizăm,
între altele, două liste: una denumită ciclueuler şi cealaltă
ciclucurent.
ciclu curenta: 10 6 7 5 11 7 8 9 7 10
ciclu euler: 1 2 3 4 10 6 7 5 11 7 8 9 7 10 1
158
2.8 Probleme propuse
159
Problema 7. Într-un graf neorientat cu 10 noduri, fiecare nod cu număr
par este adiacent cu toate nodurile cu număr impar. Ce sa va afişa la o
parcurgere în adâncime a acestui graf plecând din nodul 7. Vecinii unui
nod sunt vizitaŃi în ordinea lor crescătoare.
161
9 11
1 2
2 3
2 7
2 9
3 5
9 8
5 4
5 8
4 6
5 7
5 6
1 6
programul va afişa:
- la cerinŃa a. valorile: 1 2 3 5 6 7
- la cerinŃa b. valorile: 1 2 5 6
ExplicaŃii: graful din exemplu este următorul:
3 4
2
1 7 5 6
9 8
fig.2.26 graful din problema 15.
162
CAPITOLUL 3
GRAFURI ORIENTATE
3.1 Introducere
V = {x1 , x2 ,..., xn }
G (V , E ) =
E = {(a, b) / a, b ∈ V , (a, b) ≠ (b, a )}
163
3.2 NoŃiuni de bază
164
3.3. Drum, circuit, tare-conexitate
Un graf tare conex este un graf în care există cel puŃin un drum
între oricare două vârfuri.
Dacă un graf nu este tare conex, el este format din componente
tare conexe. O componentă tare conexă este un subgraf maximal tare
conex al unui graf dat. Graful din figura 3.2 are trei componente conexe
formate din următoarele submulŃimi de vârfuri: {1}, {2,5}, {3,4,6}
165
3
1
2
4
5 6
fig.3.2: Graf orientat cu 3 componente tare conexe
166
3.4 Metode de reprezentare a digrafurilor
0 0 0 0 0 0
1 0 0 1 1 0
A= 0 0 0 1 0 0
0 0 0 0 0 1
0 1 0 1 0 1
0 0 1 0 0 0
167
3.4.2 Liste de adiacenŃă
168
3.4.3 Lista de arce
169
//program 3.1 metode de reprezentare a digrafurilor
//determinare componente tare conexe
#include <iostream>
#include <fstream>
#include <deque>
#include <algorithm>
#include <vector>
#define nmax 101
using namespace std;
int n,m;
bool A[nmax][nmax]; //matricea de adiacenta
vector <pair <int,int> > LARCE; //lista de arce
deque <int> LA[nmax]; //listele de adiacenta
int ctcx[nmax];
void citiremat(bool A[nmax][nmax], int &n, int &m,
char numefisier[])
{ ifstream fin (numefisier);
fin >> n >> m;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
A[i][j]=0;
for (int i=1;i<=m;i++)
{
int x,y;
fin >> x >> y;
A[x][y] = 1;
}
fin.close();
}
void afisaremat(bool A[nmax][nmax], int n)
{ for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{ cout << A[i][j]<<' ';
if (j==n)
cout << endl;
}
}
int gradinternma(bool A[nmax][nmax], int n, int
varf)
{ int s=0,i;
if (varf<1 || varf > n)
{ cout << "varf inexistent" << endl;
return -1;
}
for (i=1;i<=n;i++)
170
s+=A[i][varf];
return s;
}
void citirela(deque <int> LA[], int &n, int &m, char
numefisier[])
{ ifstream fin (numefisier);
fin >> n >> m;
for (int i=1;i<=m;i++)
{ int x,y;
fin >> x >> y;
LA[x].push_back(y);
}
for (int i=1;i<=n;i++)
sort(LA[i].begin(), LA[i].end());
fin.close();
}
void afisaredeq(deque <int> q)
{ for (int i=0;i<q.size();i++)
cout << q[i] << ' ';
cout << endl;
}
int gradinternla(deque <int> LA[], int n, int varf)
{ if (varf<1 || varf > n)
{ cout << "varf inexistent" << endl;
return -1;
}
int s=0;
for (int i=1;i<=n;i++)
s +=
binary_search(LA[i].begin(),LA[i].end(),varf);
return s;
}
bool existaarcla(deque <int> LA[], int &n, int v1,
int v2)
{ if (v1<1 ||v1>n ) return false;
if (v2<1 ||v2>n ) return false;
return
binary_search(LA[v1].begin(),LA[v1].end(),v2);
}
bool existaarcma(bool A[nmax][nmax], int n, int v1,
int v2)
{ if (v1<1 ||v1>n ) return false;
if (v2<1 ||v2>n ) return false;
return A[v1][v2];
}
171
void citirelarce(vector <pair <int,int> > &LM, int
&n, int &m, char numefisier[])
{ ifstream fin (numefisier);
fin >> n >> m;
for (int i=1;i<=m;i++)
{ pair <int,int> aux;
fin >> aux.first >> aux.second;
LM.push_back(aux);
}
fin.close();
}
void afisarelarce(vector <pair <int,int> > LM)
{ int i;
for (i=0;i<LM.size()-1; i++)
cout<< LM[i].first << '-' << LM[i].second<<" ; ";
cout << LM[i].first << '-' << LM[i].second<< endl;
}
bool viz1[nmax], viz2[nmax];
void dfsd(int x)
{ viz1[x] = true;
for (int i=1;i<=n;i++)
if (A[x][i]==1 && viz1[i] == false)
{ viz1[i]=true;
dfsd(i);
}
}
void dfsi(int x)
{ viz2[x] = true;
for (int i=1;i<=n;i++)
if (A[i][x]==1 && viz2[i] == false)
{ viz2[i]=true;
dfsi(i);
}
}
int main()
{ citiremat(A,n,m,"graf.in");
cout << "Matricea de adiacenta\n";
afisaremat(A,n);
cout<< lista gradelor interne din matrice:\n"<<;
for (int k=1;k<=n;k++)
cout<<k<<":"<<gradinternma(A,n,k)<<" ";
cout<<endl;
cout << "\nListe de adiacenta\n";
citirela(LA,n,m,"graf.in");
172
for (int i=1;i<=n;i++)
{ cout << i << " : ";
afisaredeq(LA[i]);
}
cout << endl;
cout << "lista gradelor interne: " << endl;
for (int k=1;k<=n;k++)
cout<<k<<":"<<gradinternla(LA,n,k)<<" ";
cout << endl;
cout << "\nLista arcelor:\n";
citirelarce(LARCE,n,m,"graf.in");
sort(LARCE.begin(), LARCE.end());
afisarelarce(LARCE);
int v1,v2;
cout<<"dati 2 noduri (intre 1 si "<<n<<"): ";
cin >> v1 >> v2;
bool rez = existaarcma(A,n,v1,v2);
if (rez)
cout<<"exista arcul intre cele doua varfuri\n";
else
cout << "nu exista arc intre aceste varfuri\n";
bool rez1 = existaarcla(LA,n,v1,v2);
if (rez1)
cout<<"exista arcul intre cele doua varfuri\n";
else
cout << "nu exista arcul intre aceste varfuri\n";
int k=0;
for (int i=1;i<=n;i++)
{ if (ctcx[i]==0)
{ k++;
cout<<"componenta tare conexa "<<k<<endl;
fill(viz1+1,viz1+n+1,false);
fill(viz2+1,viz2+n+1,false);
dfsd(i);
dfsi(i);
for (int j=1;j<=n;j++)
if (viz1[j] == true && viz2[j]==true)
{ ctcx[j]=k;
cout << j << ' ';
}
cout << endl;
}
}
return 0;
}
173
Pentru digraful din figura 3.2 programul va afişa în fereastra consolă
următoarele:
Matricea de adiacenta
0 0 0 0 0 0
1 0 0 1 1 0
0 0 0 1 0 0
0 0 0 0 0 1
0 1 0 1 0 1
0 0 1 0 0 0
lista gradelor interne din matrice:
1:1 2:1 3:1 4:3 5:1 6:2
Liste de adiacenta
1 :
2 : 1 4 5
3 : 4
4 : 6
5 : 2 4 6
6 : 3
lista gradelor interne:
1:1 2:1 3:1 4:3 5:1 6:2
Lista arcelor:
2-1 ; 2-4 ; 2-5 ; 3-4 ; 4-6 ; 5-2 ; 5-4 ; 5-6 ; 6-3
174
3.5 Drumuri de cost minim în grafuri orientate
4 2
1
9 8 6 9 9
4 2
3 3
1 6
3 1 7
7
5 2
fig.3.3: graf ponderat
j
cij
i ckj
cik k
A) Matricea ponderilor
0 pentru i = j
hi , j = ∞ daca nu exista arcul (i, j)
C(i, j) daca exista arcul (i, j)
0 469 ∞ ∞
∞ 09 ∞ ∞ 9
∞ ∞ 011 7
H=
8 ∞ 20 3 ∞
∞ ∞ 7 ∞ 0 2
∞ ∞ 3 ∞ ∞ 0
177
Tabloul d ne dă valoarea drumului de cost minim de la vârful sursă la
fiecare dintre celelalte vârfuri ale grafului iar tabloul T ne permite să
determinăm arcele din care sunt formate aceste drumuri.
Algoritmul mai utilizează un tablou v cu semnificaŃia:
• v[i] = true dacă vârful i a fost analizat şi
false în caz contrar.
178
p=1
2 4 2
1 1
9 6
4 4
3 6 3 6
5 5
d: 0 oo oo oo oo oo d: 0 4 6 9 oo oo
T: 0 -1 -1 -1 -1 -1 T: 0 1 1 1 -1 -1
v: f f f f f f v: t f f f f f
p=2 p=3
4 2 4 2
1 1
9 6 9 6 9
4 4
3 6 1 3 6
1
5 5
d: 0 4 6 9 oo 13 d: 0 4 6 7 7 13
T: 0 1 1 1 -1 2 T: 0 1 1 3 3 2
v: t t f f f f v: t t t f f f
p=5 p=4
4 4 2
1 1
6 6
4 4
1 3 6 1 3 6
1 1
5 2 5 2
d: 0 4 6 7 7 9 d: 0 4 6 7 7 9
T: 0 1 1 3 3 5 T: 0 1 1 3 3 5
v: t t t f t f v: t t t t t f
Fig. 3.5 Desfăşurarea algoritmului Dijsktra
179
using namespace std;
int h[nmax][nmax],T[nmax],d[nmax];
bool v[nmax];
int s,i,j,k,p,n,m;
int oo = 1000000;
ifstream fin("grafponderat.in");
void afisaretab(int a[], int n)
{for (i=1;i<=n;i++)
{ cout.width(6); cout << a[i];}
cout << endl;
}
int main()
{ fin >> n >> m;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
if (i==j)h[i][j] = 0;
else h[i][j] =oo;
for (i=1;i<=m;i++)
{ int x,y,cost;
fin >> x >> y >> cost;
h[x][y] = cost;
}
for (i=1;i<=n;i++)
{ for (j=1;j<=n;j++)
{cout.width(4);
if (h[i][j]==oo) cout <<"oo";
else cout << h[i][j];
}
cout << endl;
}
cout << endl;
fill(v,v+n+1,false);
fill(d,d+n+1,oo);
fill(T,T+n+1,-1);
cout << "sursa = "; cin >> s;
d[s]=0; T[s]=0;
for (k=1;k<=n-1;k++)
{ int minim = oo;
for (i=1;i<=n;i++)
if (d[i] <= minim && v[i]==false)
minim=d[i],p=i;
v[p] = true;
for (i=1;i<=n;i++)
if (d[i] > d[p] + h[p][i])
{ d[i] = d[p] + h[p][i];
180
T[i] = p;
}
}
cout << "tabloul d:"<<endl;
afisaretab(d,n);
cout << "tabloul T:"<<endl;
afisaretab(T,n);
return 0;
}
Pentru digraful din figura 3.3 programul va afişa în fereastra consolă
următoarele:
0 4 6 9 oo oo
oo 0 9 oo oo 9
oo oo 0 1 1 7
8 oo 2 0 3 oo
oo oo 7 oo 0 2
oo oo 3 oo oo 0
sursa = 1
tabloul d:
0 4 6 7 7 9
tabloul T:
0 1 1 3 3 5
181
Algoritmul este următorul:
182
//program 3.3 Algoritmul Roy-Floyd
#include <iostream>
#include <fstream>
#include <stack>
#define nmax 101
using namespace std;
int h[nmax][nmax],T[nmax][nmax],D[nmax][nmax];
int oo = 100000;
void afisardrum(int v1, int v2, stack<int>&st)
{ while (v2 != 0)
{ st.push(v2);
v2 = T[v1][v2];
}
}
void afisarematrice(int a[nmax][nmax], int n)
{for (int i=1;i<=n;i++)
{ for (int j=1;j<=n;j++)
{cout.width(4);
if (a[i][j]==oo) cout <<"oo";
else cout << a[i][j];
}
cout << endl;
}
}
int main()
{ int s,i,j,k,p,n,m;
ifstream fin("ponderi.in");
stack <int> st;
fin >> n >> m;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
if (i==j)h[i][j] = 0;
else h[i][j] =oo;
for (i=1;i<=m;i++)
{ int x,y,cost;
fin >> x >> y >> cost;
h[x][y] = cost;
}
//afisarematrice(h,n);
cout << endl;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
{ D[i][j] = h[i][j];
if (D[i][j] != oo && i!=j)
T[i][j] = i;
183
}
for (k=1;k<=n;k++)
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
if (D[i][j] > D[i][k] + D[k][j])
{ D[i][j] = D[i][k] + D[k][j];
T[i][j] = T[k][j];
}
cout << "Matricea D"<<endl;
afisarematrice(D,n);
cout << "Matricea T"<<endl;
afisarematrice(T,n);
cout << "\nDrumul de la varful 5 la varful 1\n";
afisardrum(5,1,st);
while (!st.empty())
{ cout << st.top() << ' ';
st.pop();
}
return 0;
}
184
j. Pentru aceasta se atribuie fiecărui arc din G costul 1 şi în urma
aplicării algoritmului Roy-Floyd se foloseşte matricea D astfel:
dacă D[i][j]<n atunci
există drum de la vârful i la vârful j.
185
3.6 Flux maxim într-o reŃea de transport
3.6.1 DefiniŃii
2 7 3
5 4
3 3 2
1 4 5 9
3 5 9
6 4 6
7 8
Γ = ∑ϕ ( srs, j )
j∈V
adică suma fluxurilor asociate arcelor care pleacă din vârful sursă.
Evident această valoare va fi egală cu suma fluxurilor asociate arcelor
care intră în vârful destinaŃie.
Figura 3.7 ilustrează un flux pentru reŃeaua de transport din fig. 3.6.
Valorile fluxului ataşat fiecărui arc sunt scrise în chenar. Valoarea
fluxului din reŃeaua ilustrată în această figură este 6.
2 7 3
5 1
1 4 1
2 3 2
1 4 5 9
4 1 2
3 1 5 3
4 6 4
6 7 8
1 4
fig.3.7: reŃea de transport
188
fluxului dacă se caută la fiecare iteraŃie drumul de lungime minimă.
Pentru aceasta, drumurile în creştere se vor determina printr-un
algoritm foarte asemănător cu cel de traversare în lăŃime. Deosebirea
faŃă de cel prezentat în capitolele anterioare este că în acest caz se va
putea parcurge un arc şi în sens invers dacă fluxul asociat lui la
momentul efectuării parcurgerii este strict pozitiv. Complexitatea
algoritmului în acest caz este O(n×m2). O complexitate și mai bună
este dată de algoritmul de preflux [8] care a condus la algoritmul de flux
maxim prezentat de Goldberg şi Tarjan cu complexitatea O(n2×m).
Să ilustrăm aplicarea algoritmului Edmonds–Karp pentru reŃeaua
de transport din figura 3.6:
2 7 3
5 0
0 4 0
2 3 2
1 4 5 9
0 0 0
3 0 5 0
9 0
6 4 6
7 8
0 0
2 7 3
5 0
0 4 0
2 3 2
1 4 5 9
2 2 2
3 0 5 0
9 0
6 4 6
7 8
0 0
fig.3.8.b: flux actualizat după determinarea primului drum în creştere.
Drumul în creştere de lungime minimă pentru această configuraŃie este
1,6,7,8,9. (ObservaŃie: Drumul 1,2,3,5,9 nu este în creştere deoarece arcul
(5,9) este „saturat”: valoarea fluxului este egală cu valoarea capacităŃii).
Valorile reziduale ale arcelor care îl compun sunt: (1,6):3, (6,7):4, (7,8):6,
(8,9):9 => valoarea reziduală a drumului în creştere este 3.
189
2 7 3
5 0
0 4 0
2 3 2
1 4 5 9
2 2 2
3 3 5 0
9 3
6 2 6
7 8
3 3
fig.3.8.c: fluxul după determinarea celui de-al doilea drum în creştere.
Drumul în creştere de lungime minimă pentru această configuraŃie este
1,2,3,5,4,7,8,9. (ObservaŃie: arcul 5,4 este parcurs în sens invers!). Valorile
reziduale ale arcelor care îl compun sunt: (1,2):5, (2,3):7, (3,5):4, (5,4):2,
(4,7):5, (7,8):3, (8,9):6 => valoarea reziduală a drumului în creştere este 2.
2 7 3
5 2
2 4 2
2 3 2
1 4 5 9
2 0 2
3 3 5 2
9 5
6 2 6
7 8
3 5
fig.3.8.d: fluxul după determinarea celui de-al treilea drum în creştere. Nu
mai există drumuri în creştere, deci fluxul maxim din reŃea este 7.
192
gasit un drum de lungime 3 capacitate reziduala 2
format din arcele: 5-9 , 4-5 , 1-4 ,
193
3.7 Sortarea topologică
194
lenjerie 7 ciorapi 5
15/16 9/12
ceas 8
17/18
pantaloni 6 pantofi 9
13/14 10/11
curea 3 cămaşă 2
6/7 5/8
cravată 1
1/4
sacou 4
2/3
int culoare[nmax];
int T[nmax]; //pentru memorarea predecesorilor
int tg[nmax], tn[nmax], timp;
int n,m;
deque <int> LA[nmax];
list <string> listatopo;
195
string numeactiune[nmax] =
{"","cravata","camasa","curea","sacou","ciorapi",
"pantaloni", "lenjerie","ceas","pantofi"
};
void citirelistead(deque <int> LA[], int &n, int &m,
char numefisier[])
{ ifstream fin (numefisier);
fin >> n >> m;
for (int i=1;i<=m;i++)
{
int x,y;
fin >> x >> y;
LA[x].push_back(y);
}
for (int i=1;i<=n;i++)
sort(LA[i].begin(), LA[i].end());
fin.close();
}
196
int main()
{
citirelistead(LA,n,m,"digraf.in");
fill(culoare, culoare+n+1, alb);
timp = 0;
for (int x=1;x<=n;x++) /*1*/
if (culoare[x] == alb)
{
dfs(x,listatopo);
}
cout << "nodurile sortate topologic:\n";
afisarelist(listatopo);
return 0;
}
197
3.8 Probleme propuse
0 ∞ ∞ ∞ ∞ 1 5 12 ∞
11 0 ∞ 13 7 ∞ 4 2 ∞
1 ∞ 0 9 8 3 ∞ ∞ ∞
6 8 ∞ 0 ∞ ∞ ∞ 8 2
13 2 ∞ ∞ 0 8 ∞ 13 ∞
13 12 ∞ 10 ∞ 0 ∞ ∞ 3
∞ 8 ∞ ∞ ∞ 8 0 ∞ 3
9 4 1 ∞ ∞ 6 ∞ 0 9
∞ 8 ∞ ∞ 3 ∞ ∞ ∞ 0
198
Problema 4. ConcepeŃi un algoritm pentru determinarea drumurilor de
cost minim care leagă un unic vârf sursă cu toate celelalte vârfuri din
digraf mai eficient decât algoritmul Dijsktra.
(IndicaŃie: sortaŃi topologic vârfurile grafului, apoi parcurgeŃi structura
liniară rezultată şi aplicaŃi operaŃiunea de relaxare fiecărui vârf spre
care pleacă câte un arc din vârful curent.)
0 5 8 6 9 1 1 2 6
3 0 8 2 2 1 2 2 7
3 5 0 6 3 1 1 2 6
4 5 8 0 9 1 1 4 4
3 5 8 2 0 1 2 2 7
3 5 8 6 9 0 2 2 6
3 5 8 6 9 7 0 2 7
3 8 8 6 3 1 1 0 6
3 5 8 2 9 1 2 2 0
199
- pe următoarele m linii câte două numere v1 şi v2 1≤v1,v2≤n,
reprezentând câte o muchie a grafului;
DeterminaŃi un cuplaj maxim în acest graf.
(IndicaŃie: transformaŃi graful într-o reŃea de transport în care muchiile
vor fi orientate de la una dintre partiŃii la cealaltă partiŃie – vezi
observaŃiile finale de la subcapitolul 3.4.1. Fiecare muchie
(transformată deci într-un arc) va avea capacitate egală cu 1. CalculaŃi
fluxul maxim din această reŃea iar valoarea acestuia va reprezenta
numărul de muchii din cuplajul maxim).
200