Sunteți pe pagina 1din 39

Grafuri neorientate

1.Terminologie
Graf neorientat= o pereche de multimi =(V, E)unde V este o mulime
finit nevida de elemente numite noduri iar E o multime de perechi
neordonate din V, numite muchii. Notm graful cu G=(V, E).
Intr-un un graf G=(V, E) neorientat relaia binar este simetric:
(v,w)E atunci (w,v) E.
Nod = element al mulimii V, unde G=(V, E) este un graf neorientat.
Muchie = element al mulimii E ce descrie o relaie existent ntre dou
noduri din V, unde G=(V, E) este un graf neorientat;
1

In figura alaturata:
V={1,2,3,4,5,6} sunt noduri
E={[1,2], [1,4], [2,3],[3,4],[3,5]} sunt muchii
G=(V, E)=({1,2,3,4,5,6}, {[1,2], [1,4], [2,3],[3,4],[3,5]})

6
5

2
3

Adiacenta: ntr-un graf neorientat existena muchiei (v,w) presupune c w este


adiacent cu v i v adiacent cu w.

n exemplul din figura de mai sus vrful 1 este adiacent cu 4 dar 1 i 3 nu


reprezint o pereche de vrfuri adiacente.
Inciden = o muchie este incident cu un nod dac l are pe acesta ca
extremitate. Muchia (v,w) este incident n nodul v respectiv w.
Grad = Gradul unui nod v, dintr-un graf neorientat, este un numr natural
ce reprezint numrul de noduri adiacente cu acesta (sau numarul de
muchii incidente cu nodul respectiv)
Nod izolat = Un nod cu gradul 0.
Nod terminal= un nod cu gradul 1
1

Nodul 5 este terminal (gradul


1).

6
5

Nodul 6 este izolat (gradul 0)


3

Nodurile 1, 2, 4 au gradele
egale cu 2.
Daca un graf neorientat are m muchii atunci suma gradelor tuturor
nodurilor este 2m

In orice graf G exista un numar par de noduri de grad impar


Lan = este o secven de noduri ale unui graf neorientat G=(V,E), cu
proprietatea c oricare dou noduri consecutive din secventa lant sunt
adiacente:
L=[w1, w2, w3,. . ,wn] cu proprietatea c (wi, wi+1)E pentru 1i<n.
Lungimea unui lan = numrul de muchii din care este format.
Lan simplu = lanul care conine numai muchii distincte
Lan compus= lanul care nu este format numai din muchii distincte
Lan elementar = lanul care conine numai noduri distincte
Ciclu = Un lan n care primul nod coincide cu ultimul.
Ciclul este elementar dac este format doar din noduri distincte,
excepie fcnd primul i ultimul. Lungimea unui ciclu nu poate fi 2.
Succesiunea de vrfuri 2, 3, 5, 6
reprezint
un
lan
simplu
i
elementar de lungime 3.
Lanul 5 3 4 5 6 este simplu dar nu
este elementar.
Lanul 5 3 4 5 3 2 este compus i
nu este elementar.
Lanul 3 4 5 3 reprezint un ciclu
elementar
Graf partial = Dac dintr-un graf G=(V,E) se suprim cel puin o muchie
atunci noul graf G=(V,E), E E se numete graf parial al lui G (are
aceleasi noduri si o parte din muchii).

6
5

2
3

6
5

2
3

G1 este graf partial al lui G

Subgraf = Dac dintr-un graf G=(V,E) se suprim cel puin un nod


mpreun cu muchiile incidente lui, atunci noul graf G=(V,E), E E si
VV se numete subgraf al lui G.

6
5

2
3

2
3

G1 este subgraf al lui G

Graf regulat = graf neorientat n care toate nodurile au acelai grad;


1

6
5

2
3

Graf complet = graf neorientat G=(V,E) n care exist muchie ntre


oricare dou noduri.
Numrul de muchii ale unui graf complet este: nr*(nr-1)/2.Unde nr
este numarul de noduri
1

2
3

graf complet. Nr de muchii: 4x(4-1)/2 = 6

Graf conex = graf neorientat G=(V,E) n care pentru orice pereche de


noduri (v,w) exist un lan care le unete.
1

6
5

2
3

graf conex

nu este graf conex

Component conex = subgraf al grafului de referin, maximal n


raport cu proprietatea de conexitate (ntre oricare dou vrfuri exist lan);
1

6
5

2
3

graful nu este conex. Are 2 componente conexe:


1, 2 si 3, 4, 5, 6

Lan hamiltonian = un lan elementar care conine toate nodurile unui


graf
1

6
5

2
3

L=[2 ,1, 6, 5, 4, 3] este lant hamiltonian


Ciclu hamiltonian = un ciclu elementar care conine toate nodurile
grafului
1

6
5

2
3

C=[1,2,3,4,5,6,1] este ciclu hamiltonian

Graf hamiltonian = un graf G care conine un ciclu hamiltonian


Graful anterior este graf Hamiltonian.
Daca G este un graf cu n>=3 noduri astfel incat gradul fiecarui nod este
mai mare sau egal decat n/2 atunci G este hamiltonian
Lan eulerian = un lan simplu care conine toate muchiile unui graf

Lantul: L=[1.2.3.4.5.3.6.2.5.6] este lant eulerian


Ciclu eulerian = un ciclu simplu care conine toate muchiile grafului
Ciclul: C=[1.2.3.4.5.3.6.2.5.6.1] este ciclu eulerian
Graf eulerian = un graf care conine un ciclu eulerian.
Condiie necesar i suficient: Un graf este eulerian dac i numai dac
oricare vrf al su are gradul par.

REPREZENTAREA GRAFURILOR NEORIENTATE

Fie G=(V, E) un graf neorientat.


Exista mai multe modalitati de reprezentare pentru un graf neorientat,
folosind diverse tipuri de structuri de date. Reprezentarile vor fi utilizate in diversi
algoritmi si in programele care implementeaza pe calculator acesti algoritmi.

Matricea de adiacent matricea boolean

Matricea de adiacent asociat unui graf neorientat cu n noduri se defineste


astfel: A = (ai j)

nxn

cu

1, daca [i,j]E
a[i,j]=
0, altfel

Observatii:

Matricea de adiacenta asociat unui graf neorientat este o matrice simetric


Suma elementelor de pe linia k reprezint gradul nodului k
Suma elementelor de pe coloana k reprezint gradul nodului k

Fie graful din figura urmatoare:

Fie graful din figura urmatoare:

6
5

2
3

A=

0 1 0 1 0 0 nodul 1 are gradul 2


1 0 1 0 0 0 nodul 2 are gradul 2
0 1 0 1 1 0 nodul 3 are gradul 3
1 0 1 0 0 0 nodul 4 are gradul 2
0 0 1 0 0 0 nodul 5 are gradul 1
0 0 0 0 0 0 nodul 6 are gradul 0

Numarul de noduri este 6 si numarul de muchii este 5


Matricea este simetrica si patratica avand 6 linii si 6 coloane
Diagonala principala contine numai valori nule

Pentru a prelucra graful se citesc:


6- reprezentand n, numarul de noduri
5- reprezentand m, numarul de muchii
5 perechi x-y reprezentand extremitatile celor 5 muchii:
1-2 => a[1,2]=a[2,1]=1
1-4 => a[1,4]=a[4,1]=1
2-3 => a[2,3]=a[3,2]=1
3-4=> a[3,4]=a[4,3]=1
3-5 => a[3,5]=a[5,3]=1

Listele de adiacenta a nodurilor


Reprezentarea in calculator a unui graf se poate face utilizand listele de adiacenta a varfurilor,
adica pentru fiecare varf se alcatuieste lista varfurilor adiacente cu el.
Fie graful din figura urmatoare:
1

6
5

2
3

Lista vecinilor nodului 3: 2, 4, 5 (noduri adiacente)


1

6
5

2
3

Nodul 6 nu are vecini (este izolat)


Pentru intreg graful vom avea mai multe liste :
Nodul 1 : 2
Nodul 2 :

Nodul 3 : 2

Nodul 4 : 1

Nodul 5 : 3

Ordinea nodurilor in cadrul unei liste nu este importanta


Pentru a genera o astfel de lista vom defini tipul nod :
struct nod {int nd;
nod *next;};
Toate listele se vor memora utilizand un vector de liste :
nod *L[20];
Datele de intrare : numarul de noduri si muchiile se vor citi din fisier :
O solutie de implementare este urmatoarea :
#include<conio.h>
#include<fstream.h>
struct nod
{int nd;
nod *next;};
nod *L[20];
void afisare(int nr_nod) //afiseaza lista vecinilor nodului nr_nod
{nod *p=L[nr_nod];
if(p==0)
cout<<nr_nod<<" este izolat "<<endl;
else
{cout<<"lista vecinilor lui "<<nr_nod<<endl;
nod *c=p;
while(c)
{cout<<c->nd<<" ";
c=c->next;}
cout<<endl;}
}
void main()
{fstream f;int i,j,n;
nod *p,*q;
f.open("graf.txt",ios::in); //citirea datelor din fisier
clrscr();
f>>n;
while(f>>i>>j)
{p=new nod; //se adauga j in lista vecinilor lui i
p->nd=j;
p->next=L[i];
L[i]=p;
q=new nod; //se adauga i in lista vecinilor lui j
q->nd=i;

q->next=L[j];
L[j]=q;
}
f.close();
cout<<endl<<"listele de adiacente ";
for(i=1;i<=n;i++)
afisare(i);
getch();
}
Observatie : In exemplul anterior adaugarea unui nou element in lista se face inainte
celorlalte din lista corespunzatoare.
Aceste doua moduri de reprezentare (prin matrice de adiacenta si prin liste de vecini) se
folosesc dupa natura problemei. Adica, daca in problema se doreste un acces frecvent la
muchii, atunci se va folosi matricea de adiacenta; daca numarul de muchii care se reprezinta
este mult mai mic dect nxn, este de preferat sa se folosesca listele de adiacenta, pentru a se
face economie de memorie.
Parcurgerea
Rezolvarea multor probleme de grafuri, presupune parcurgerea lor de la un anumit nod.
Pentru explorarea grafurilor, exista doua tipuri de algoritmi: de explorarea in latime si de
explorare in adancime.
Explorarea grafurilor in latime
La explorarea in latime, dupa vizitarea nodului initial, se exploreaza toate nodurile adiacente
lui, se trece apoi la primul nod adiacent si se exploreaza toate nodurile adiacente acestuia si
neparcurse inca, s.a.m.d.
Fiecare nod se parcurge cel mult odata (daca graful nu este conex nu se vor putea parcurge
toate nodurile)
De exemplul pentru garful din figura de mai jos, se va proceda in felul urmator:
se porneste din nodul 1, (se poate incepe de la oricare alt nod)
1

6
5

2
3

se exploreaza in vontinuare vecinii acestuia : nodul 2 si apoi 4,

6
5

2
3

se obtine

1,2,4

dupa care din 2 se exploreaza nodul adiacent acestuia 3. Nodul 1 nu se mai viziteaza odata
1

6
5

2
3

se obtine 1,2,4,3

In continuare ar trebui parcursi vecinii lui 4 (1,2,4,3 ) dar acesta nu mai


are vecini nevizitati si se trece la vecinii lui 3 : 1,2,4,3 respectiv nodul 5 :
1

6
5

2
3

se obtine 1, 2, 4, 3, 5

Nodul 6 ramane neparcurs


Algoritmul
Se va folosi o coada in care se inscriu nodurile in forma in care sunt parcurse: nodul initial
varf (de la care se porneste), apoi nodurile a,b,..., adiacente lui varf, apoi cele adiacente lui a,
cele adiacente lui b,... ,s.a.m.d.
Coada este folosita astfel:
- se pune primul nod in coada;
- se afla toate varfurile adiacente cu primul nod si se introduc dupa primul nod
- se ia urmatorul nod si i se afla nodurile adiacente
- procesul se repeta pana cand se ajunge la sfarsitul cozii
-Graful se va memora utilizand matricea de adiacenta a[10][10]
-pentru memorarea succesiunii nodurilor parcurse se va folosi un vector c[20] care va
functiona ca o coada
-pentru a nu parcurge un nod de doua ori se va folosi un vector boolean viz[20] care va
retine :
- viz[k]=0 daca nodul k nu a fost vizitat inca
- viz[k]=1 daca nodul k a fost vizitat
-doua variabile : prim si ultim vor retine doua pozitii din vectorul c si anume :

- prim este indicele componentei pentru care se parcurg vecinii


(indexul componentelor marcate cu rosu in sirurile parcurse anterior ). Prin urmare
Varf=c[prim], este elementul pentru care se determina vecinii (nodurile adiacente)
-ultim este pozitia in vector pe care se va face o noua inserare in
vectorul c (evident, de fiecare data cand se realizeaza o noua inserare se mareste vectorul)
-vecinii nodului varf se cauta pe linia acestui varf : daca a[varf][k]=1 inseamna ca
nodurile varf si k sunt adiacente. Pentru ca nodul k sa fie adaugat in coada trebuie ca nodul sa
nu fi fost vizitat : viz[k]=0
#include<fstream.h>
#include<conio.h>
int a[10][10],c[20],viz[10];
int n,m,prim,ultim,varf;
void bf_iterativ() //parcurgerea in latime
{int k;
while(prim<=ultim)
{varf=c[prim];
for(k=1;k<=n;k++)
if(a[varf][k]==1&&viz[k]==0) //il adaug pe k in coada daca este vecin pt. varf si nu a fost
vizitat
{ultim++;
c[ultim]=k;
viz[k]=1;}
prim++;
}
}
void main()
{clrscr();
int x,y;
fstream f; //memorare graf in matrice de adiacenta
f.open("muchii.txt",ios::in);
f>>n>>m;
for(int i=1;i<=m;i++)
{f>>x>>y;
a[x][y]=a[y][x]=1;
}
cout<<"matricea de adiac "<<endl; // afisare matrice de adiacenta
for( i=1;i<=n;i++)
{for(int j=1;j<=n;j++)
cout<<a[i][j]<<" ";
cout<<endl;
}
int nd;
prim=ultim=1;
cout<<"nodul de inceput=";

cin>>nd; // nodul de la care se porneste parcurgerea


viz[nd]=1;
c[prim]=nd;
bf_iterativ();
for(i=1;i<=ultim;i++) //afisarea cozii
cout<<c[i]<<" ";
getch();
}
Varianta recursiva de parcurgere se obtine modificand functia de parcurgere iterativa
adaugand conditia necesara autoapelului:
void bf_recursiv() //parcurgerea in latime
{int k;
if(prim<=ultim)
{varf=c[prim];
for(k=1;k<=n;k++)
if(a[varf][k]==1&&viz[k]==0) //il adaug pe k in coada daca este vecin pt. varf si nu a fost
vizitat
{ultim++;
c[ultim]=k;
viz[k]=1;}
prim++;
bf_recursiv();
}
}
Parcurgerea in latime a grafurilor memorate prin liste este similara cu diferenta ca vecinii unui
nod adaugat in coada se cauta in lista corespunzatoare lui :
#include<conio.h>
#include<fstream.h>
struct nod
{int nd;
nod *next;};
nod *L[20];
int viz[100]; //marchez cu 1 nodurile vizitate
int m,n;
int prim,ultim,C[100];
void bfi_lis()
{int varf,nr;
nod *p;
while(prim<=ultim)
{varf=C[prim];
p=L[varf]; // se parcurge lista elementelor din varful cozii
while(p)
{nr=p->nd;
if(viz[nr]==0) //numai daca nu a fost vizitat

{ultim++; //maresc coada


C[ultim]=nr; //il adaug in coada
viz[nr]=1;}; //il marchez ca fiind vizitat
p=p->next;
}
prim++; //avansez la urmatorul nod din coada
}
}
void afisare(int nr_nod)
{nod *p=L[nr_nod];
if(p==0)
cout<<nr_nod<<" este izolat "<<endl;
else
{cout<<"lista vecinilor lui "<<nr_nod<<endl;
nod *c=p;
while(c)
{cout<<c->nd<<" ";
c=c->next;}
cout<<endl;}
}
void main()
{fstream f;
int i,j;
nod *p,*q;
f.open("muchii.txt",ios::in);
clrscr();
f>>n>>m;
while(f>>i>>j)
{p=new nod;
p->nd=j;
p->next=L[i];
L[i]=p;
q=new nod;
q->nd=i;
q->next=L[j];
L[j]=q;
}
f.close();
cout<<endl<<"listele de adiacente ";
for(i=1;i<=n;i++)
afisare(i);

int ndr;
cout<<endl<<"nodul de inceput ";
cin>>ndr;
viz[ndr]=1;
prim=ultim=1;
C[prim]=ndr;
cout<<endl<<"parcurgere in latime "<<endl;
bfi_lis();
for(i=1;i<=ultim;i++)
cout<<C[i]<<" ";
getch();
}
Functia recursiva :
void bfr_lis()
{int varf,nr;
nod *p;
if(prim<=ultim)
{varf=C[prim];
p=L[varf];// se parcurge lista elementelor din varful cozii
while(p)
{nr=p->nd;
if(viz[nr]==0)//numai daca nu a fost vizitat
{ultim++;//maresc coada
C[ultim]=nr;//il adaug in coada
viz[nr]=1;};//il marchez ca fiind vizitat
p=p->next;}
prim++; //avansez la urmatorul nod din coada
bfr_lis();
}
}
Parcurgerea grafurilor in adancime (depth first)
Parcurgerea unui graf in adancime se face prin utilizarea stivei (alocate implicit
prin subprograme recursive).
Pentru fiecare nod se parcurge primul dintre vecinii lui neparcursi inca
Dupa vizitarea nodului initial x 1, se exploreaza primul nod adiacent lui fie acesta
x2 , se trece apoi la primul nod adiacent cu x 2 si care nu a fost parcurs inca ,
s.a.m.d.
Fiecare nod se parcurge cel mult odata (daca graful nu este conex nu se vor
putea parcurge toate nodurile)
De exemplul pentru garful din figura de mai jos, se va proceda in felul urmator:
se porneste din nodul 1, (se poate incepe de la oricare alt nod)

6
5

2
3

se exploreaza in vontinuare primul vecin al acestuia acestuia : nodul 2,


1

6
5

2
3

se obtine

1,2

dupa care din 2 se exploreaza un nod adiacent cu acesta si care nu a fost vizitat :
3.( Nodul 1 nu se mai viziteaza odata)
1

6
5

2
3

se obtine 1,2,3

In continuare ar trebui sa se parcurga vecinul lui 3 nevizitat : 4


1

6
5

2
3

se obtine 1, 2, 3, 4

Pentru nodul 4 ar trebui sa se parcurga primul sau vecin neparcurs (nodul 1 dar
acesta a fost deja parcurs. Si nodul 3 a fost parcurs. Din 4 nu mai avem ce vizita
si se trece la nivelul anterior din stiva, la nodul 3 :
1, 2, 3, 4 Se parcurge vecinul sau nevizitat, nodul 5 .

6
5

2
3

SE obtine : 1, 2, 3, 4 , 5.

Nodul 3 nu mai are vecini nevizitati si se trece pe nivelul anterior din stiva, nodul
2 : 1, 2, 3, 4 , 5. Nici acesta nu mai are vecini nevizitati si se trece pe nivelul
anterior la nodul 1 : 1, 2, 3, 4 , 5. Cum nici acesta nu mai are vecini nevizitati se
incheie algoritmul.
Nodul 6 ramane nevizitat.
Algoritmul
-Graful se va memora utilizand matricea de adiacenta a[10][10]
-pentru a nu parcurge un nod de doua ori se va folosi un vector boolean vizcare
va retine :
- viz[k]=0 daca nodul k nu a fost vizitat inca
- viz[k]=1 daca nodul k a fost vizitat
-ca si la parcurgerea in latime vecinii unui nod se cauta pe linia acestui nod :
daca a[nod][k]=1 inseamna ca nodurile nod si k sunt adiacente. Pentru ca nodul
k sa fie fie parcurs trebuie ca nodul sa nu fi fost vizitat : viz[k]=0

#include<fstream.h>
#include<conio.h>
int a[20][20],n,m,viz[100],gasit;

void dfmr(int nod)


{
cout<<nod<<" ";
viz[nod]=1;
for(int k=1;k<=n;k++)
if(a[nod][k]==1&&viz[k]==0)
dfmr(k);

void main()
{int x,y,j;
fstream f;
clrscr();
f.open("graf.txt",ios::in);
if(f)
cout<<"ok!"<<endl;
else
cout<<"eroare la deschidere de fisier!";
f>>n>>m;
for(int i=1;i<=m;i++)
{f>>x>>y;
a[x][y]=a[y][x]=1;}
cout<<endl<<"matricea de adiacente"<<endl;
for(i=1;i<=n;i++)
{for(j=1;j<=n;j++)
cout<<a[i][j]<<" ";
cout<<endl;}
cout<<endl<<"parcurgere in adancime incepand de la varful 1"<<endl;
dfmr(1)
getch();}

Matricea lanturilor
Fie G=(V, E) un graf neorientat, unde V are n elemente (n noduri) si E are m elemente (m
muchii). Lui G i se asociaza matricea D numita matricea lanturilor, cu n linii si n coloane.
Astfel: D = (di j)

nxn

cu:

1, daca exista lant de la i la j

d[i,j]=
0, altfel
Matricea lanturilor ca si matricea de adiacenta, in cazul grafurilor neorientate , este o matrice
simetrica (daca exista lant de la i la j exista si lant de la j la i).
Grafului de mai jos i se asociaza urmatoarea matrice a lanturilor:
1

7
5

2
3

0111100
1011100
1101100
1110100
1111000
0000001
0000010

Linia k (sau coloana k) a matricei lanturilor ne arata nodurile j pentru care exista lant de k la j
( si de la j la k). Spre exemplu, pentru nodul 3 .
1

7
5

2
3

0111100
1011100
1101100
1110100
1111000
0000001
0000010

Nu se poate ajunge de la nodul 3 la nodurile 6 sau 7.


Problema:
Fiind dat un graf neorientat, memorat prin intermediul matricei de adiacenta, se cere sa se
creeze si sa se tipareasca matricea lanturilor.
Se observa ca parcurgand in adancime (sau in latime) graful pornind de la nodul nod=3 spre
exemplu vectorul de vizitati este:
1 1 1 1 1 0 0. Daca se inlocuieste viz[nod]=viz[3]=0 se obtine: 1 1 0 1 1 0 0 si se poate incarca
linia 3 din matricea lanturilor cu continultul vectorului de vizitati. Prin urmare se parcurge df
graful pornind de la fiecare nod in parte si se incarca de fiecare data continutul vectorului viz
in matricea lanturilor in linia corespunzatoare. Iata o solutie:
#include<fstream.h>
#include<conio.h>
int a[20][20],n,m,viz[100],gasit,drum[20][20];
void dfmr(int nod)
{ viz[nod]=1;
for(int k=1;k<=n;k++)
if(a[nod][k]==1&&viz[k]==0)

dfmr(k);
}
void main()
{int x,y,j;
fstream f;
clrscr();
f.open("muchii.txt",ios::in); //citire matrice de adiacenta din fisier
if(f)
cout<<"ok!"<<endl;
else
cout<<"eroare la deschidere de fisier!";
f>>n>>m;
for(int i=1;i<=m;i++)
{f>>x>>y;
a[x][y]=a[y][x]=1;}
cout<<endl<<"matricea de adiacente"<<endl;
for(i=1;i<=n;i++)
{for(j=1;j<=n;j++)
cout<<a[i][j]<<" ";
cout<<endl;}
int nod;
for(nod=1;nod<=n;nod++) //parcurgere graf incepand de la fiecare nod
{for(j=1;j<=n;j++)
viz[j]=0; //pentru fiecare nod se face vectorul viz in prealabil 0
dfmr(nod);
viz[nod]=0;
for(j=1;j<=n;j++) //incarcare vector viz in matrice drum
drum[nod][j]=viz[j];
}
cout<<endl<<"matricea drumurilor "<<endl;
for(i=1;i<=n;i++)
{for(j=1;j<=n;j++)
cout<<drum[i][j]<<" ";
cout<<endl;}
getch();}
Componente conexe
Fie G=(V, E) un graf neorientat, unde V are n elemente (n noduri) si E are m
elemente (m muchii).
Definitie: G1=(V1, E1)este o componenta conexa daca:
-

pentru orice pereche x,y de noduri din V 1 exista un lant de


la x la y (implicit si de la y la x)
nu exista alt subgraf al lui G, G 2=(V2, E2) care sa
indeplineasca prima conditie si care sa-l contina pe G1

7
5

2
3

Graful alaturat are doua componente


conexe:
subgraful care contine
nodurile:
12345
-

subgraful care contine


nodurile:
67
-

Observatie: subgraful 1, 2, 3, 4 nu este componenta conexa pentru ( chiar daca


pentru orice pereche x,y de noduri exista un lant de la x la y) deoarece exista
subgraful 1, 2, 3, 4, 5 care il contine si indeplineste aceeasi conditie.

Definitie: Un graf G=(V, E)este conex daca pentru orice pereche x,y de noduri
din V exista un lant de la x la y (implicit si de la y la x).

Observatii:
Un graf este conex daca admite o singura componenta conexa.
Graful anterior nu este conex pentru ca admite doua componente
conexe
Graful urmator este conex:
-

7
5

2
3

Probleme:
1. Fiind dat un graf memorat prin intermediul matricei de adiacenta sa se
determine daca graful este conex.
2. Fiind dat un graf memorat prin intermediul matricei de adiacenta si un
nod k sa se determine componenta conexa careia ii apartine nodul k
3. Sa se afiseze toate componentele conexe ale unui graf neorientat
Indicatii :
-In vectorul viz se incarca numarul componentei conexe astfel pentru graful
urmator, vectorul viz va retine:
viz=[1,1,1,1,2,1,2].

-Numarul de componente conexe este 2.


-Se afiseaza componentele cu numarul componentei conexe egal cu 1: 1,2,3,4,6
-Se afiseaza componentele cu numarul componentei conexe egal cu 2: 5, 7

7
6

2
3

-Incarcarea in viz se realizeaza prin parcurgere df pornind de la fiecare nod


-se porneste de la 1, se parcurge df si se incarca in viz valoarea 1 pt nodurile 1, 2,
3, 4, 6. Viz devine:
1,1,1,1,0,1,0
-se porneste in continuare de la primul nod nevizitat, adica 5 si se incarca
numarul celei de a doa componente, adica 2
Viz devine: 1,1,1,1,2,1,2
-Nu mai sunt noduri nevizitate si algoritmul se incheie.
Iata o solutie:

#include<fstream.h>
#include<conio.h>
int a[20][20],n,m,viz[100],gasit;
int nrc; //pastreaza numarul componentei conexe

void dfmr(int nod)


{ viz[nod]=nrc; //se incarca numarul componentei
for(int k=1;k<=n;k++)
if(a[nod][k]==1&&viz[k]==0)
dfmr(k);
}

void main()
{int x,y,j;
fstream f;
clrscr();
f.open("muchii.txt",ios::in); //memorare matrice de adiacenta
if(f)
cout<<"ok!"<<endl;
else
cout<<"eroare la deschidere de fisier!";
f>>n>>m;
for(int i=1;i<=m;i++)
{f>>x>>y;
a[x][y]=a[y][x]=1;}
cout<<endl<<"matricea de adiacente"<<endl;
for(i=1;i<=n;i++)
{for(j=1;j<=n;j++)
cout<<a[i][j]<<" ";
cout<<endl;}

for(int nod=1;nod<=n;nod++)
if(viz[nod]==0) //se incearca parcurgerea numai daca un nod nu a fost deja
parcurs
{nrc++;
dfmr(nod);}

cout<<endl<<"vectorul viz "<<endl;


for(i=1;i<=n;i++)
cout<<viz[i]<<" ";

cout<<endl<<"Componentele conexe :"<<endl;


for(i=1;i<=nrc;i++)
{cout<<endl<<"componenta "<<i<<endl;
for(j=1;j<=n;j++)
if(viz[j]==i)
cout<<j<<" ";
}
getch();}

Matricea ponderilor
Fie G=(V, E) un graf neorientat, unde V are n elemente (n noduri) si E are m elemente (m
muchii). Atasam fiecarei muchii o pondere (sau cost) ci,j. Spre exemplu, ne putem imagina
nodurile unui graf ca fiind niste repere pe o harta si costurile distante intre repere adiacente.
Matricea ponderilor asociata grafului este o matrice simetrica cu nxn elemente
Astfel: p= (pi j)

nxn

cu:

0, daca i=j
p[i,j]= ci,j, daca [i,j]E
altfel

1
2

2
6

8
3

025
206
6018
510
80

Datele care se vor citi pentru generarea matricii ponderilor sunt:


Numarul de noduri, numarul de muchii si m triplete de forma extremitati muchie si cost
asociat:
5
5
122
145
236
341
358

Problema:
Sa se genereze matricea ponderilor pentru un graf citit (n noduri, m muchii si m triplete).
Indicatii:
- pentru infinit se va folosi o valoare mai mare decat oricare dintre valorile posibile
pentru costuri. De obicei intervalul de valori pentru costuri este precizat.
- Initial se incarca in matrice infinit apoi se suprascrie matricea pentru fiecare triplet
citit:
Ex: cin>>x>>y>>cost
p[x][y]=p[y][x]=cost

Algoritmul lui Roy-Floyd


Fie G=(V, E) un graf neorientat, unde V are n elemente (n noduri) si E are m
elemente (m muchii) memorat prin matricea ponderilor. Se cere ca pentru doua
noduri x,y citite sa se determine lungimea minima a lantului de la x la y.

Astfel:

1
2

10

2
3

8
3

Initial matricea ponderilor pentru nodurile 1 si 4 va retine 10. Se observa ca


lantul 1,2,3,4 determina o suma a costurilor mai mica: 2+3+1=6. Lungime
minima a lantului de la 1 la 4 este 6.

Algoritmul:
-se genereaza matricea ponderilor:
0 2 pinf 10 pinf

1
2

10

2
3

8
3

2 0 3 pinf pinf
pinf 3 0 1 8

10 pinf 1 0 pinf
pinf pinf 8 pinf 0
Unde pinf reprezinta plus infinit

-se incearca pentru oricare pereche de noduri i,j sa se obtina drumuri mai
scurte prin noduri intermediare k (k1n).

Acest lucru se determina comparand lungimea lantului a[i,j] cu lungimea


lantului care trece prin k si daca:
a[i,j] > a[i,k]+a[k,j] atunci se atribuie: a[i,j] a[i,k]+a[k,j]

Astfel generarea matricii drumurilor optime se realizeaza cu urmatoarea


secventa:

for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j]>a[i][k]+a[k][j])
a[i][j]=a[i][k]+a[k][j];

Se obtine:
0 2 5 6 13

1
2

10

2
3

8
3

2 0 3 4 11
53018

64109
13 11 8 9 0

In continuare, dupa determinarea matricii lanturilor optime, pentru doua noduri


citite x, y se cere sa se reconstituie un lant optim de la x la y (pot fi mai multe
solutii).

Solutie:

-se determina daca exista un lant de la x la y (ar putea sa nu existe un astfel de


lant daca graful nu este conex):
cand a[x,y]
-se descompune drumul de la x la y prin k atunci cand: a[x][y]=a[x][k]+a[k][y];
-

pentru un astfel de algoritm se utilizeaza strategia Divide et Impera

Iata o solutie:

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

const pinf=1000; //pentru plus infinit


int a[20][20],n,m;

void citire_cost()
{fstream f;
int i,j,x,y,c;
f.open("roy.in",ios::in);
if(f)
cout<<"deschiderea a reusit";
else
cout<<"eroare la deschidere!";
cout<<endl;
f>>n>>m;
//initializare matrice
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j)

a[i][j]=0;
else
a[i][j]=pinf;
for(i=1;i<=m;i++)
{f>>x>>y>>c;
a[x][y]=a[y][x]=c;}
}

void afisare_mat()
{for(int i=1;i<=n;i++)
{for(int j=1;j<=n;j++)
if(a[i][j]==pinf)
cout<<"pinf ";
else
cout<<a[i][j]<<" ";
cout<<endl;}
}

void genarare_matrice_drumuri_optime()
{for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j]>a[i][k]+a[k][j])
a[i][j]=a[i][k]+a[k][j];
}

void descompun_drum(int i,int j) //realizeaza descompunerea portiunii de la i la j


prin k
{int g=0,k=1;

while(k<=n&&!g)
{if(i!=k&&j!=k)
if(a[i][j]==a[i][k]+a[k][j])
{descompun_drum(i,k);
descompun_drum(k,j);
g=1;} //g marcheaza daca se poate realiza descompunerea
k++;
}
if(!g)
cout<<j<<" "; //cand drumul nu mai poate fi descompus afisez extremitatea
finala

void scriu_drum(int nodini,int nodfin) // functia primeste ca parametri cele doua


noduri pt care se determina optimul
{if(a[nodini][nodfin]<pinf)
{cout<<"lantul de la "<<nodini<<" la "<<nodfin<<" are lungimea "<<a[nodini]
[nodfin];
cout<<endl<<"un drum optim este: "<<endl;
cout<<nodini<<" ";
descompun_drum(nodini,nodfin); // apeleaza functia care afiseaza efectiv lantul
}
else
cout<<"nu exista drum de la "<<nodini<<" la "<<nodfin;
}

void main()
{clrscr();int x,y;

citire_cost();
cout<<endl<<"matricea ponderilor "<<endl;
afisare_mat();
genarare_matrice_drumuri_optime();
cout<<endl<<"matricea drumurilor optime "<<endl;
afisare_mat();
cout<<endl<<"Se determina drumul minim intre varfurile x si y "<<endl;
cout<<"x=";
cin>>x;
cout<<"y=";
cin>>y;
scriu_drum(x,y);
getch();
}

Graf hamiltonian
Fie G=(V, E) un graf neorientat, unde V are n elemente (n noduri) si E are m
elemente (m muchii).

Lan hamiltonian = un lan elementar care conine toate nodurile unui graf

6
5

2
3

L=[2 ,1, 6, 5, 4, 3] este lant hamiltonian

Ciclu hamiltonian = un ciclu elementar care conine toate nodurile grafului

6
5

2
3

C=[1,2,3,4,5,6,1] este ciclu hamiltonian

Graf hamiltonian = un graf G care conine un ciclu hamiltonian


Graful anterior este graf Hamiltonian.
Fiind dat un graf neorientat memorat prin matricea de adiacente sa se determine
daca graful este Hamiltonian . In caz afirmativ sa se afiseze ul ciclu Hamiltonian
altfel se va afisa un mesaj.
Pentru a rezolva problema vom utiliza tehnica backtracking. Vom incarca in stiva
noduri distincte si adiacente, astfel incat pornind de la problema clasica de
backtracking a permutarilor vom testa valoarea de pe nivelul k astfel:
Sa fie un nod adiacent cu precedentul adaugat. E necesar ca: a[st[k1]][st[k]]=1, in caz contrar se returneaza 0
Nodul adaugat sa nu se regaseasca pe nivelurile anterioare . Trebuie
ca st[k]st[i] unde i{1,2k-1}, in caz contrar se returneaza 0
Pentru a se incheia ciclul este necesar ca primul si ultimul nod sa fie
adiacente. Adica:
a[st[1]][st[n]]=1, in caz contrar se returneaza 0

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

int st[100],n,m,k,a[20][20];
int ns;
//k este este nivelul din stiva (indexul - vetorul solutie),curent

int e_valid()
{if(k>1)
if(!a[st[k-1]][st[k]])

return 0;
else
for(int i=1;i<=k-1;i++)//parcurg nivelurile anterioarenivelului curent
if(st[i]==st[k])
return 0;
if(k==n)
if(!a[st[1]][st[k]])
return 0;
return 1;
}

void afisare()
{for(int i=1;i<=n;i++)
cout<<st[i]<<" ";
cout<<st[1];
k=0; //determina iesirea la prima solutie
ns++;
}void back()
{k=1; //pe primul nivel initial
while(k>0)//cand k va descreste la 0 algoritmul se incheie
if(st[k]<n)
{st[k]++;
if(e_valid())//daca elementul incarcat este valid
if(k==n)//verific daca am ajuns la solutia completa.
afisare();

else //daca nu am solutia completa urc in stiva (maresc vectorul,


adica pe k)
{k++;
st[k]=0;}

}
else
k--;

void main()
{clrscr();
fstream f;
f.open("ham.in",ios::in);
int u,v;
if(f)
cout<<"ok!";
else
cout<<"eroare";
cout<<endl;
f>>n>>m;
for(int i=1;i<=m;i++)
{f>>u>>v;
a[u][v]=a[v][u]=1;

cout<<"matricea de adiac "<<endl; // afisare matrice de adiacenta


for( i=1;i<=n;i++)
{for(int j=1;j<=n;j++)
cout<<a[i][j]<<" ";
cout<<endl;
}
back();
if(ns==0)
cout<<nu exista solutii;
getch();
}

Grafuri euleriene
Fie G=(V, E) un graf neorientat, unde V are n elemente (n noduri) si E are
m elemente (m muchii).
Lan eulerian = un lan simplu care conine toate muchiile unui graf

Lantul: L=[1.2.3.4.5.3.6.2.5.6] este lant eulerian


Ciclu eulerian = un ciclu simplu care conine toate muchiile grafului
Ciclul: C=[1.2.3.4.5.3.6.2.5.6.1] este ciclu eulerian
Graf eulerian = un graf care conine un ciclu eulerian.

Condiie necesar i suficient: Un graf este eulerian dac i numai dac


oricare vrf al su are gradul par.
Observatie: graful poate fi eulerian si daca contine noduri izolate.
Problema: fiind dat un graf fara noduri izolate sa se determine daca este
eulerian. In caz afirmativ se vor afisa toate ciclurile euleriene care incep cu
un nod nd citit.
- vom determina daca graful este conex
- vom determina daca fiecare nod are grad par
- vom genera toate ciclurile euleriene utilizand tehnica backtracking.
Astfel:
o primul nod va trebui sa fie nd
o un nou x=st[k], adaugat in stiva trebuie sa fie adiacent cu
anteriorul (y=st[k-1])
o muchia x-y nu trebuie sa mai fi fost adaugata inca odata
o ultimul nod, care incheie ciclul, trebuie sa fie incident cu
primul
O solutie:
#include<fstream.h>
#include<conio.h>
int st[100];
int k,nd;
int a[10][10],viz[10],n,m;
void df_r(int nod) //parcurgere in adancime
{int k;
cout<<nod<<" ";
viz[nod]=1;
for(k=1;k<=n;k++)
if(a[nod][k]&&!viz[k])
df_r(k);
}
int e_valid()
{int x,y;
if(k==1)
if(st[k]!=nd)
return 0;
if(k>1) //sa existe muchie cu precedentul
{x=st[k];
y=st[k-1];
if(a[x][y]==0)
return 0;
}

for(int i=1;i<=k-2;i++)
if((st[i]==x && st[i+1]==y) || (st[i]==y && st[i+1]==x))
return 0; //muchia a mai fost luata odata
//ultima muchie sa fie incidenta cu prima
if(k==m)
if(a[st[m]][st[1]]==0)
return 0;
return 1;}
void tipar()
{for(int i=1;i<=m;i++)
cout<<st[i]<<" ";
cout<<st[1];
cout<<endl;
}
void back()
{ k=1;
while(k>0)
{if(st[k]<n)
{st[k]++;
if(e_valid())
if(k==m)
tipar();
else{k++;
st[k]=0;
}
}
else
k--;}
}
void main()
{clrscr();int x,y;
fstream f;//int a[10][10];// citire matrice din fisier
f.open("matsim.txt",ios::in);
if(f)
cout<<"ok";
else
cout<<"error";
f>>n>>m;
for(int i=1;i<=m;i++)
{f>>x>>y;
a[x][y]=a[y][x]=1;
}

cout<<"matricea de adiac "<<endl; // afisare matrice


for( i=1;i<=n;i++)
{for(int j=1;j<=m;j++)
cout<<a[i][j]<<" ";
cout<<endl;
}
cout<<"nd="; //nodul de la care se porneste
cin>>nd;
df_r(nd);
int s=0;
for(i=1;i<=n;i++)
s+=viz[i]; //pentru a verifica daca graful este conex
if(s!=n)
cout<<"graful nu e conex ";
else
{int gasit=0;
cout<<endl<<"graful e conex!"<<endl;
for(i=1;i<=n;i++) //determin daca toate nodurile au gradele pare
{s=0;
for (int j=1;j<=n;j++)
s+=a[i][j];
if(s%2!=0)
gasit=1;}
if(gasit)
cout<<"am noduri fara grade pare";
else
cout<<"toate nodurile au gradele pare deci graful e eulerian";
}
back();
getch();
}
O varianta mai eficienta de determinare a unui ciclu eulerian este
urmatoarea:
Fie graful din figura urmatoare:

-se determina daca graful contine un ciclu eulerian (toate nodurile au grad
par si este conex)

-se retin gradele tuturor nodurilor in vectorul g. Acesta va retine:


g=[2,4,4,4,4,2]
-ciclul se genereaza pas cu pas retinand nodurile intr-o lista gestionata de
variabilele p si u (prima si ultima componenta)
-se genereaza un ciclu pornind de la nodul 1 care se adauga in lista p
(acesta va fi si u la inceput). In continuare se adauga noduri adiacente cu
informatia retinuta de u si se continua astfel pana se ajunge iar la nodul 1.
De fiecare data cand se adauga o muchie (u->info, i) la ciclu se
micsoreaza gradul lui u->info si lui i iar muchiile (u->info, i) si (i,u->info)
se elimina.
-acest prim ciclu se poate genera parcurgand nodurile grafului
-dupa prima secventa se determina ciclul :

lista va retine : p={1, 2, 3, 1} iar g devine : g=[0, 2, 2, 4, 4, 2]


in continuare se cauta un nou ciclu care sa porneasca de la un nod x
din p si pt care g[x]>0. Primul astfel de nod gasit este : x=2. Acest
nou ciclu este memorat de o noua lista gestinata de p1 si u1. Ciclul
nou se cauta dupa acelasi principiu. Se obtine :
p1={2, 4, 3, 5, 2} iar g devine : g=[0,0,0,1,1,1]
-

Noul ciclu se insereaza dupa x gasit (x=2, se insereaza lista p1 in lista p)


si se obtine : p={1,2,4,3,5,2,3,1}
-mai departe pe acelasi principiu se cauta x (x=4)iar urmatorul ciclu este
p1={4, 5,6,4} si g ajunge la g={0,0,0,0,0,0}. Acesta se insereaza dupa 4
:
Se obtine : p={1,2,4,5,6,4,3,5,2,3,1}

O variabila k retine numarul muchiilor adaugate la ciclu si algoritmul


continua pana k ajunge la m (numarul de muchii).
O solutie de implementare este :
#include<fstream.h>
#include<conio.h>
struct nod{int info;
nod *next;};
int a[20][20],viz[20];
int g[20];
int n,m,k;
void df(int nod)
{viz[nod]=1;
for(int k=1;k<=n;k++)
if(a[nod][k]==1&&viz[k]==0)
df(k);
}
void citire()
{ int x,y;
fstream f; //memorare graf in matrice de adiacenta
f.open("euler.txt",ios::in);
if(f)
cout<<"ok";
else
cout<<"eroare";
cout<<endl;
f>>n>>m;
for(int i=1;i<=m;i++)
{f>>x>>y;
g[x]++; g[y]++;
a[x][y]=a[y][x]=1;
}
}
int verific()
{ for(int i=1;i<=n;i++)
if(g[i]%2==1)
return 0;
df(1);
for(i=1;i<=n;i++)
if(viz[i]==0)
return 0;
return 1;
}

void generezc1(nod*&p,nod*&u,int x)
{nod *q;
q=new nod;
q->info=x;
p=u=q;
do
{ int gas=0;
for(int i=1;i<=n&&!gas;i++)
if(a[i][u->info]==1)
{g[u->info]--;
g[i]--;
k++;
a[i][u->info]=a[u->info][i]=0;
q=new nod;
q->info=i;
u->next=q;
u=q;
gas=1;}
}
while(p->info!=u->info);
u->next=0;
}
void afisare(nod *q)
{while(q)
{cout<<q->info<<" ";
q=q->next;}
}
int cauta(nod *&q)
{
while(q)
{if(g[q->info])
return q->info;
q=q->next;
}
return 0;
}
void main()
{clrscr();
citire();
if(verific()==0)
cout<<"gf nu este eulerian!";
else

{ cout<<"este eulerian!";
nod *p=0,*u;
cout<<endl;
generezc1(p,u,1);
cout<<endl;
nod *p1=0,*u1;
while(k<m)
{nod *q=p;
int x=cauta(q);
generezc1(p1,u1,x);
u1->next=q->next;
q->next=p1->next;
}
afisare(p); }
getch();
}

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

  • MS 2
    MS 2
    Document5 pagini
    MS 2
    Georgiana Bachrouch
    Încă nu există evaluări
  • MS 2
    MS 2
    Document5 pagini
    MS 2
    Georgiana Bachrouch
    Încă nu există evaluări
  • RCP 5667 08.09.05
    RCP 5667 08.09.05
    Document6 pagini
    RCP 5667 08.09.05
    Georgiana Bachrouch
    Încă nu există evaluări
  • MS 2
    MS 2
    Document5 pagini
    MS 2
    Georgiana Bachrouch
    Încă nu există evaluări
  • MS 2
    MS 2
    Document5 pagini
    MS 2
    Georgiana Bachrouch
    Încă nu există evaluări
  • Functia de Conducere A Maduvei Spinarii
    Functia de Conducere A Maduvei Spinarii
    Document10 pagini
    Functia de Conducere A Maduvei Spinarii
    Geta Robu
    Încă nu există evaluări
  • MS 2
    MS 2
    Document5 pagini
    MS 2
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Functia de Conducere A Maduvei Spinarii
    Functia de Conducere A Maduvei Spinarii
    Document10 pagini
    Functia de Conducere A Maduvei Spinarii
    Geta Robu
    Încă nu există evaluări
  • Grafuri Orientate2
    Grafuri Orientate2
    Document25 pagini
    Grafuri Orientate2
    Georgiana Bachrouch
    Încă nu există evaluări
  • Functia de Conducere A Maduvei Spinarii
    Functia de Conducere A Maduvei Spinarii
    Document10 pagini
    Functia de Conducere A Maduvei Spinarii
    Geta Robu
    Încă nu există evaluări
  • Functia de Conducere A Maduvei Spinarii
    Functia de Conducere A Maduvei Spinarii
    Document10 pagini
    Functia de Conducere A Maduvei Spinarii
    Geta Robu
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Orientate2
    Grafuri Orientate2
    Document25 pagini
    Grafuri Orientate2
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Orientate2
    Grafuri Orientate2
    Document25 pagini
    Grafuri Orientate2
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Orientate2
    Grafuri Orientate2
    Document25 pagini
    Grafuri Orientate2
    Georgiana Bachrouch
    Încă nu există evaluări
  • Grafuri Neorientate
    Grafuri Neorientate
    Document39 pagini
    Grafuri Neorientate
    Georgiana Bachrouch
    Încă nu există evaluări