+ All Categories

SDA_c4

Date post: 08-Jul-2016
Category:
Upload: andy-cioacata
View: 216 times
Download: 2 times
Share this document with a friend
Description:
Metode si algoritmi de sortare
22
97 4. Grafuri orientate şi neorientate Pentru a exemplifica un graf foarte simplu, să ne imaginăm harta unui oraş ca un ansamblu de străzi şi să ne propunem să descriem un traseu dintr-un punct x într-un punct y. Vom reprezenta intersecţiile străzilor prin puncte şi străzile dintre intersecţii prin linii (arce). Traseul astfel descris între cele două puncte x, y reprezintă un graf. Dacă nu sunt restricţii în ceea ce priveşte direcţia de parcurgere a străzilor avem un graf neorientat. Dacă însă străzile nu pot fi parcurse decât într -o anumită direcţie (cum ar fi străzile cu sens unic pentru autovehicule) atunci vom avea un graf orientat. Dacă deplasarea implică şi nişte costuri (timp, combustibil) atunci pe graf fiecărei legături (arc) între două intersecţii (noduri ale grafului) se va specifica şi costul respectiv. În acest caz vom avea şi o probelmă de drum optim sau cea de drum de cost minim. Se numeşte graf neorientat o pereche ordonată de mulţimi (V, M), unde V este mulţimea finită şi nevidă de vârfuri sau noduri, iar M este o mulţime neordonată de perechi de elemente din V, numite muchii sau arce. Gradul unui nod (vârf) dintr-un graf reprezintă numărul de muchii care trec prin nodul respectiv. Un nod cu gradul zero nu este legat la nici o muchie, este un nod izolat, iar unul care are gradul unu este un nod terminal. Într- un graf cu m muchii suma gradelor tuturor muchiilor este egală cu 2*m, deoarece fiecare muchie contribuie cu câte două unităţi (una la nodul i, şi una pentru nodul j) la suma gradelor grafului. Un graf poate fi reprezentat în mai multe moduri: prin matricea de adiacenţă sau drumuri, prin vector de muchii sau prin lista vecinilor. Reprezentarea prin matricea de adiacenţă (A) pentru un graf cu n noduri şi m muchii va fi o matrice n*n cu m valori diferite de 0 deasupra diagonalei principale şi tot atâtea dedesubtul acesteia, întrucât matricea unui graf neorientat este simetrică. Dacă există muchia (i,j) atunci a[i][j]=a[j][i]=1, iar în caz contrar a[i][j]=a[j][i]=0. De obicei se iniţializează matricea A cu 0 şi apoi se citesc muchiile grafului şi se actualizează valorile acelor respective din matricea grafului. Matricea drumurilor (sau costuri) conţine pentru muchiile existente (deci care aveau în matricea de adiacenţă valoarea 1) valoarea costului (lungimea drumului) arcului respectiv, iar pentru celelalte (care nu sunt conectate, şi care aveau valoarea 0 în matricea de adiacenţă) valoarea infinit (sau o valoare foarte mare, pentru reprezentarea lor în calculator). Reprezentarea grafului prin vector de muchii presupune că fiecare muchie a grafului poate fi privită ca o structură cu două componente, şi anume cele două vârfuri, la care se poate adăuga şi costul (drumul) asociat muchiei. Alte noţiuni legate de grafuri sunt noţiunile de lanţ şi ciclu. Se numeşte lanţ o succesiune de vârfuri {v 1 , v 2 , . . ., v n } cu proprietatea că oricare două vârfuri (v i , v j ) sunt adiacente. Dacă toate vârfurile sunt distincte, lanţul se numeşte elementar, în caz contrar lanţul este ne-elementar. De exemplu: avem lanţurile elementare {1, 2, 3, 5, 6, 8}, {1, 4, 3, 5, 6, 8} şi lanţurile ne-elementare {7, 3, 5, 6, 3, 2}, {4, 3, 5, 6, 3, 2}. Un ciclu este un lanţ {v 1 , v 2 , . . ., v n } cu proprietatea că v 1 = v n şi toate muchiile sunt distincte. Dacă toate vârfurile sunt distincte, ciclul se numeşte elementar, în caz contrar ciclul este ne-elementar. 5 8 4 1 2 3 6 7
Transcript
Page 1: SDA_c4

97

4. Grafuri orientate şi neorientate

Pentru a exemplifica un graf foarte simplu, să ne imaginăm harta unui oraş ca un ansamblu de

străzi şi să ne propunem să descriem un traseu dintr-un punct x într-un punct y. Vom reprezenta

intersecţiile străzilor prin puncte şi străzile dintre intersecţii prin linii (arce). Traseul astfel descris între

cele două puncte x, y reprezintă un graf. Dacă nu sunt restricţii în ceea ce priveşte direcţia de

parcurgere a străzilor avem un graf neorientat. Dacă însă străzile nu pot fi parcurse decât într-o anumită

direcţie (cum ar fi străzile cu sens unic pentru autovehicule) atunci vom avea un graf orientat.

Dacă deplasarea implică şi nişte costuri (timp, combustibil) atunci pe graf fiecărei legături (arc)

între două intersecţii (noduri ale grafului) se va specifica şi costul respectiv. În acest caz vom avea şi o

probelmă de drum optim sau cea de drum de cost minim.

Se numeşte graf neorientat o pereche ordonată de mulţimi (V, M), unde V este mulţimea finită şi

nevidă de vârfuri sau noduri, iar M este o mulţime neordonată de perechi de elemente din V, numite

muchii sau arce.

Gradul unui nod (vârf) dintr-un graf reprezintă numărul de muchii care trec prin nodul

respectiv. Un nod cu gradul zero nu este legat la nici o muchie, este un nod izolat, iar unul care are

gradul unu este un nod terminal. Într- un graf cu m muchii suma gradelor tuturor muchiilor este egală

cu 2*m, deoarece fiecare muchie contribuie cu câte două unităţi (una la nodul i, şi una pentru nodul j)

la suma gradelor grafului.

Un graf poate fi reprezentat în mai multe moduri: prin matricea de adiacenţă sau drumuri, prin

vector de muchii sau prin lista vecinilor.

Reprezentarea prin matricea de adiacenţă (A) pentru un graf cu n noduri şi m muchii va fi o

matrice n*n cu m valori diferite de 0 deasupra diagonalei principale şi tot atâtea dedesubtul acesteia,

întrucât matricea unui graf neorientat este simetrică. Dacă există muchia (i,j) atunci a[i][j]=a[j][i]=1,

iar în caz contrar a[i][j]=a[j][i]=0. De obicei se iniţializează matricea A cu 0 şi apoi se citesc muchiile

grafului şi se actualizează valorile acelor respective din matricea grafului. Matricea drumurilor (sau

costuri) conţine pentru muchiile existente (deci care aveau în matricea de adiacenţă valoarea 1)

valoarea costului (lungimea drumului) arcului respectiv, iar pentru celelalte (care nu sunt conectate, şi

care aveau valoarea 0 în matricea de adiacenţă) valoarea infinit (sau o valoare foarte mare, pentru

reprezentarea lor în calculator).

Reprezentarea grafului prin vector de muchii presupune că fiecare muchie a grafului poate fi

privită ca o structură cu două componente, şi anume cele două vârfuri, la care se poate adăuga şi costul

(drumul) asociat muchiei.

Alte noţiuni legate de grafuri sunt noţiunile de lanţ şi ciclu. Se numeşte lanţ o succesiune de

vârfuri {v1, v2, . . ., vn} cu proprietatea că oricare două vârfuri (vi, vj) sunt adiacente. Dacă toate

vârfurile sunt distincte, lanţul se numeşte elementar, în caz contrar lanţul este ne-elementar.

De exemplu:

avem lanţurile elementare {1, 2, 3, 5, 6, 8}, {1, 4, 3, 5, 6, 8} şi lanţurile ne-elementare {7, 3, 5, 6, 3, 2},

{4, 3, 5, 6, 3, 2}.

Un ciclu este un lanţ {v1, v2, . . ., vn} cu proprietatea că v1 = vn şi toate muchiile sunt distincte.

Dacă toate vârfurile sunt distincte, ciclul se numeşte elementar, în caz contrar ciclul este ne-elementar.

5

8

4

1

2 3

6

7

Page 2: SDA_c4

98

Să considerăm problema care verifică dacă o secvenţă de vârfuri dată reprezintă un lanţ

elementar sau ne-elementar, într-un graf neorientat. Aceasta înseamnă să verificăm în matricea de

adiacenţă existenţa muchiilor (vi, vj) ce aparţin lanţului de testat, vi şi vj fiind vârfuri succesive din

lanţul de testat. După determinarea existenţei lanţului verificăm dacă este elementar sau nu, adică dacă

toate vârfurile sunt distincte sau nu.

/* lantgraf.c - programul determina daca exista un lant dat intr-un graf */

#include <stdio.h>

#include <conio.h>

#define DIMN 6 // numar maxim noduri

#define DIMM 10 // numar maxim muchii

void citire_graf (int a[DIMN+1][DIMN+1], int *pn, int *pm){

int i, j, k;

clrscr();

printf("\nDati numar noduri, graf neorientat, n: ");

scanf("%d", pn);

printf("Nr. de muchii: ");

scanf("%d", pm);

for (i=1; i<= *pn; i++)

for (j=1; j<=*pn; j++)

a[i][j]=0; // initializare matrice drumuri (costuri) pt. graf

printf("\nCitire graf prin muchiile sursa.\n");

for (k=1; k<= *pm; k++)

{

printf("Muchie, varf sursa, dest: ");

scanf ("%d%d", &i, &j);

a[j][i]=a[i][j]=1; // pt. graf neorientat costul (i,j)=cost(j,i)

}

}

void citire_lant(int l[DIMM+1], int *pdl){

int i;

printf("\nDati numar noduri pentru lantul de testat: ");

scanf("%d", pdl);

for (i=1; i<= *pdl; i++)

{

printf("Varful %d din lant: ", i);

scanf ("%d", &l[i]);

}

printf("\nLantul cautat in garf este urmatorul:\n");

for (i=1; i<= *pdl; i++)

printf("%d -> ", l[i]);

}

int test_lant(int g[DIMN+1][DIMN+1], int l[DIMM+1], int dl, int *elem){

int i, j;

*elem=1; // initializam cu 1 (presupunem ca este lant elementar)

for (i=1; i<=dl; i++)

if (!g[l[i]][l[i+1]])

return 0; // nu este lant, un varf nu este in matrice

for (i=1; i<=dl-1; i++)

Page 3: SDA_c4

99

for (j=i+1; j<=dl; j++)

if(l[i]==l[j])

*elem=0; // este ne-elementar

return 1;

}

void main()

{

int a[DIMN+1][DIMN+1], l[DIMM+1]; // matricea de adiacenta si lantul cautat

int n, nm, dl, elementar;

citire_graf (a, &n, &nm);

citire_lant (l, &dl);

if (test_lant(a, l, dl, &elementar)){

printf("\n\nEste un lant ");

if (!elementar)

printf("ne-");

printf("elementar!\n");

}

else

printf("\n\nNu este un lant in graf !!!\n");

printf("\n");

}

Un alt exemplu de prelucrare de graf ar putea fi problema colorării muchiilor unui graf. Se cere

să se determine toate modurile posibile de colorare a muchiilor unui graf neorientat cu n vârfuri şi m

muchii, utilizând c culori, cu restricţia ca oricare două muchii ce pleacă din acelaşi vârf să fie colorate

diferit (să nu aibă aceeaşi culoare). Vom memora colorile muchiilor grafului într-un vector culoare ce

va avea m componente. Vom utiliza metoda backtracking şi vom valida o culoare culoare[i] dacă

valoarea respectivă se mai află în vectorul culoare pe un nivel k pentru muchia k, dacă aceste două

muchii nu ajung în acelaşi vârf (adică, nu se intersectează).

/* colorgrf.c - programul determina toate solutiile pentru colorarea celor

m muchii ale unui graf cu n noduri, utilizand c culori diferite,

cu restrictia ca doua muchii ce se intersecteaza intr-un nod sa

nu aiba aceeasi culoare

Graful este reprezentat ca un vector de muchii

*/

#include <stdio.h>

#include <conio.h>

#define DIMN 6 // numar maxim noduri

#define DIMM 10 // numar maxim muchii

typedef struct{

int v1, v2; // cele doua varfuri ale unei muchii

} VECT_MUCHII;

int nr_sol=0;

void citire_graf (VECT_MUCHII vm[DIMM+1], int a[DIMN+1][DIMN+1], int *pn, int *pm){

int i, j, k;

clrscr();

printf("\nDati numar noduri, graf neorientat, n: ");

Page 4: SDA_c4

100

scanf("%d", pn);

printf("Nr. de muchii: ");

scanf("%d", pm);

for (i=1; i<= *pn; i++)

for (j=1; j<=*pn; j++)

a[i][j]=0; // initializare matrice drumuri (costuri) pt. graf

printf("\nCitire graf prin muchiile sursa.\n");

for (k=1; k<= *pm; k++)

{

printf("Muchie: varfuri sursa, dest: ");

scanf ("%d%d", &vm[k].v1, &vm[k].v2);

a[vm[k].v1][vm[k].v2]=a[vm[k].v2][vm[k].v1]=1; // costul (i,j)=cost(j,i)

}

}

void tipar_culoare(int culoare[DIMM+1], VECT_MUCHII vm[DIMM+1], int nm){

int i;

for (i=1; i<=nm; i++) // tipareste culoarea pentru muchia 'i', specificand

if (vm[i].v1 < vm[i].v2) // varful sursa si cel destinatie, in ordine

printf("m(%d-%d)=c%d, ", vm[i].v1, vm[i].v2, culoare[i]);

else

printf("m(%d-%d)=c%d, ", vm[i].v2, vm[i].v1, culoare[i]);

printf("\n");

if ((nr_sol+1)%20==0){

printf("\nAfisarea continua dupa apasarea unei taste !!!\n");

getch();

}

}

int validare_culoare (VECT_MUCHII vm[DIMM+1], int culoare[DIMM+1], int p){

int k;

for (k=1; k<p; k++)

if (culoare[k] == culoare [p])

if (vm[k].v1==vm[p].v1 || vm[k].v1==vm[p].v2 ||

vm[k].v2==vm[p].v1 || vm[k].v2==vm[p].v2)

return 0;

return 1;

}

void incerc_culoare(VECT_MUCHII vm[DIMM+1], int culoare[DIMM+1],

int c, int nm, int p){

int i, j;

for (i=1; i<=c; i++)

{culoare[p]=i;

if (validare_culoare(vm, culoare, p))

if (p==nm) // solutie completa (toate nodurile colorate)

{tipar_culoare(culoare, vm, nm);

nr_sol++;

}

else

incerc_culoare(vm, culoare, c, nm, p+1);

// culoare[p]=0; // sterge culoarea incercata

Page 5: SDA_c4

101

}

}

void main(){

VECT_MUCHII vm[DIMM+1]; // vectorul de muchii (muchie=doua varfuri)

int culoare[DIMM+1], a[DIMN+1][DIMN+1]; // vect culori si matricea de adiacenta

int n, nm, c;

citire_graf (vm, a, &n, &nm);

printf("\nNumarul de culori cu care se coloreaza: ");

scanf("%d", &c);

incerc_culoare(vm, culoare, c, nm, 1);

if(nr_sol)

printf("\nNumarul total de solutii este: %d\n", nr_sol);

else

printf("\nProblema nu are solutii");

printf("\n");

}

O altă problemă legată de grafuri neorientate ar fi cea care generează, utilizând metoda

backtracking, toate ciclurile elementare dintr-un graf. Problema nu diferă mult de cea precedentă. Se

modifică condiţia de validitate, acum toate vârfurile din vectorul ce conţine soluţia trebuie să fie

diferite, cu excepţia primului care va fi egal cu ultimul, şi cea care determină dacă soluţia este

completă, adică există cel puţin trei vârfuri şi ultimul este identic cu primul (lungimile lor vor fi

diferite).

/* ciclugrf.c - programul determina toate ciclurile elementare dintr-un

graf cu n noduri si m muchii. */

#include <stdio.h>

#include <conio.h>

#define DIMN 10 // numar maxim noduri

#define DIMM 100 // numar maxim muchii

typedef struct{

int v1, v2; // cele doua varfuri ale unei muchii

} VECT_MUCHII;

int nr_sol=0;

void citire_graf (VECT_MUCHII vm[DIMM+1], int a[DIMN+1][DIMN+1], int *pn, int *pm){

int i, j, k;

clrscr();

printf("\nDati numar noduri, graf neorientat, n: ");

scanf("%d", pn);

printf("Nr. de muchii: ");

scanf("%d", pm);

for (i=1; i<= *pn; i++)

for (j=1; j<=*pn; j++)

a[i][j]=0; // initializare matrice drumuri (costuri) pt. graf

printf("\nCitire graf prin muchiile sale.\n");

for (k=1; k<= *pm; k++)

{

printf("Muchia %d, varfuri: ", k);

scanf ("%d%d", &vm[k].v1, &vm[k].v2); // costul (i,j)=cost(j,i)

Page 6: SDA_c4

102

a[vm[k].v1][vm[k].v2]=a[vm[k].v2][vm[k].v1]=1;

}

}

void init_ciclu (int ciclu[DIMM+1], int nm){

int i;

for (i=1; i<=nm; i++)

ciclu[i]=0;

}

void tipar_ciclu(int ciclu[DIMM+1], int p){

int i;

for (i=1; i<p; i++) // tipareste ciclul 'p'

printf("%d -> ", ciclu[i]);

printf("%d\n", ciclu[p]);

if ((nr_sol+1)%20==0){

printf("\nAfisarea continua dupa apasarea unei taste !!!\n");

getch();

}

}

int validare_ciclu (int a[DIMN+1][DIMN+1], int ciclu[DIMM+1], int p){

int k;

if ( !a[ciclu[p]][ciclu[p-1]] ) // daca nu exista muchia -> nu e valid ciclul

return 0;

for (k=2; k<p; k++) // daca varful mai exista in solutia 'ciclu', pana

if (ciclu[k] == ciclu [p]) // la pozitia 'p-1' -> nu e solutie corecta

return 0;

return 1;}

void incerc_ciclu(int a[DIMN+1][DIMN+1], int ciclu[DIMM+1], int n, int p){

int i;

for (i=1; i<=n; i++)

{ciclu[p]=i;

if (validare_ciclu(a, ciclu, p))

if (ciclu[p]==ciclu[1] && p>3) // solutie completa

{tipar_ciclu(ciclu, p); // (nod final = nod initial

nr_sol++; // si sunt cel putin 3 noduri)

}

else

incerc_ciclu(a, ciclu, n, p+1);

// ciclu[p]=0; // sterge ciclul incercat

}

}

void main()

{

VECT_MUCHII vm[DIMM+1]; // vectorul de muchii (muchie=doua varfuri)

int ciclu[DIMM+1], a[DIMN+1][DIMN+1]; // vect culori si matricea de adiacenta

int n, nm;

citire_graf (vm, a, &n, &nm);

printf("\nCiclurile din graf sunt urmatoarele:\n");

init_ciclu(ciclu, nm);

incerc_ciclu(a, ciclu, n, 1);

Page 7: SDA_c4

103

if(nr_sol)

printf("\nNumarul total de solutii este: %d\n", nr_sol);

else

printf("\nProblema nu are solutii");

printf("\n");

}

4.1. Parcurgerea grafurilor neorientate Prin parcurgerea unui graf se înţelege vizitarea tuturor nodurilor (vârfurilor) sale într-o anumită

ordine, pe baza unui anumit criteriu. Există doi algoritmi de parcurgere a grafurilor neorientate:

algoritmul de parcurgere în lăţime BF, şi algoritmul de parcurgere în adâncime DF.

Algoritmul de parcurgere în lăţime porneşte parcurgerea cu vizitarea un vârf de start, pentru

care se vizitează toţi vecinii săi. Pentru fiecare din aceste vârfuri (vecini) se vizitează vecinii lor care nu

au fost vizitaţi. Procedeul contină în acest mod până când au fost vizitate toate vârfurile. Pentru graful

prezentat anterior, acest algoritm va furniza următoarea parcurgere:

Parcurgerea grafului Noduri vizitate (vecini) Observaţii

3 – vârful de start 2, 4, 5, 6, 7

2 – vecin al nodului 3 1 3 a fost vizitat

4 – vecin al nodului 3 - 1 şi 3 au fost vizitate

5 – vecin al nodului 3 - 3 şi 6 au fost vizitate

6 – vecin al nodului 3 8 3, 5 şi 7 au fost vizitate

Ordinea vizitării vârfurilor în parcurgerea BF este următoarea: 3, 2, 4, 5, 6, 7, 1, 8,

Pentru a memora ordinea de parcurgere a grafului vom folosi un vector de tip coadă C, în care printr-un

capăt vom introduce vârfurile (de start şi apoi vecinii, adică vârfurile cele nevizitate), iar prin celălalt

vom extrage vârfurile vizitate. De fapt extragerea înseamnă nu elimiarea din vector ci actualizarea

corespunzătoare a poziţiei (indecelui) vârfului extras. Cele două poziţii, de introducere şi respectiv de

extragere, vor fi reprezentate de pi (poziţie introducere) şi pe (poziţie extragere). Pentru graful anterior

evoluţia cozii este următoarea:

pi, pe

Introducem în coadă vârful de start 3. 1

3

pi pe Extragem vârful 3 şi

introducem vecinii săi nevizitaţi

(2, 4, 5, 6, 7).

2 6

3 2 4 5 6 7

pi pe Extragem vârful 2 şi

introducem vecinii săi

nevizitaţi (1)

3 7

3 2 4 5 6 7 1

pi pe Extragem 4 şi 5 care nu

au vecini nevizitaţi, iar

pentru 6 introducem 8.

6 8

3 2 4 5 6 7 1 8

Pe lângă coada C, vom mai utiliza un vector V care specifică dacă un vârf a fost vizitat sau nu

(0-nu a fost vizitat, 1-a fost vizitat). Dimensiunile celor doi vectori vor fi egale cu numărul de noduri

Page 8: SDA_c4

104

din graf. Ambii vectori vor fi iniţializaţi cu 0. Când se introduce un nou vârf k în coada C, vom memora

faptul că urmează a fi vizitat făcând atribuirea C[k]=1. La fiecare pas, după ce se extrage un vârf din

coadă, din poziţia pe, se vor introduce pe la celălalt capăt pi vecinii nevizitaţi ai vârfului extras. Pentru

fiecare vecin vom verifica dacă a fost vizitat sau nu, utilizând vectorul V.

În final pentru a lista ordinea vizitării vârfurilor este suficient să tipărim vectorul C. Vectorul va

conţine atâtea vârfuri câte au fost introduse (pi). Întreg programul este prezentat în continuare.

/* LatBFgrf.c – algoritmul de parcurgere in latime BF a unui graf */

#include <stdio.h>

#include <conio.h>

#define DIMN 10 // numar maxim noduri

#define DIMM 100 // numar maxim muchii

void citire_graf (int a[DIMN+1][DIMN+1], int *pn, int *pm){

int i, j, k;

clrscr();

printf("\nDati numar noduri, graf neorientat, n: ");

scanf("%d", pn);

printf("Nr. de muchii: ");

scanf("%d", pm);

for (i=1; i<= *pn; i++)

for (j=1; j<=*pn; j++)

a[i][j]=0; // initializare matrice drumuri (costuri) pt. graf

printf("\nCitire graf prin muchiile sale.\n");

for (k=1; k<= *pm; k++)

{

printf("Muchie, varfuri sursa, dest: ");

scanf ("%d%d", &i, &j);

a[j][i]=a[i][j]=1; // pt. graf neorientat costul (i,j)=cost(j,i)

}

}

void parcurg_lat_BF (int a[DIMN+1][DIMN+1], int C[DIMM+1], int V[DIMM+1],

int n, int prim, int *pdimV){

int k, pi=1, pe=1, v; // init. pozitie intrare/ 'extragere' C(oada)

for (k=1; k<=n; k++)

C[k]=V[k]=0;

C[1]=prim; // primul varf vizitat

V[prim]=1; // varful 'prim' a fost vizitat

while (pe<=pi){

v=C[pe]; // varful 'extras', v, din coada, C

for (k=1; k<=n; k++)

if (a[v][k] && !V[k]){ // nod vecin ?, nevizitat ?

pi++; // se introduce in coada

C[pi]=k; // varful k

V[k]=1; // memorez ca a fost vizitat

}

pe++; // 'extrag' urmatorul varf din coada

}

*pdimV=pi;

}

void main()

Page 9: SDA_c4

105

{

int a[DIMN+1][DIMN+1], l[DIMM+1]; // matricea de adiacenta

int C[DIMN+1], V[DIMN+1]; // coada varfurilor parcurse, vizitate

int n, nm, primul, dimV, k;

citire_graf (a, &n, &nm);

printf("\nProgramul parcurge in latime graful.\n");

printf("\nSpecificati varful de start pentru parcurgere: ");

scanf("%d", &primul);

parcurg_lat_BF(a, C, V, n, primul, &dimV);

printf("\nParcurgerea in latime este urmatoarea:\n");

for (k=1; k<dimV; k++)

printf("%4d - ", C[k]);

printf("%4d\n", C[k]);

printf("Apasati o tasta pentru a termina programul\n");

getch();

}

Dacă în urma parcurgerii grafului (cu algoritmul BF) s-au vizitat toate nodurile, indiferent care

a fost varful de start, atunci graful este conex. Altfel spus graful are o singură componentă care

cuprinde toate vârfurile grafului.

Se numeşte componentă conexă a grafului G = (V, M), un subgraf G1 = (V1, M1), a lui G, conex,

cu proprietatea că nu există nici un lanţ care să lege un vârf dim V1 cu un vârf din V- V1.

Utilizând algoritmul de parcurgere în lăţime BF putem determina dacă un graf este conex, dacă

după parcurgerea lui nu au rămas vârfuri nevizitate (toate componentele lui V sunt 1).

4.2. Arborele parţial de cost minim Un astfel de arbore are costul cel mai mic dintre toţi arborii ce se pot construi cu vârfurile unui

graf neorientat, care are pentru asociat fiecare muchie un anumit cost. Vom prezenta în continuare

algoritmul lui Prim.

Se consideră pe lângă matricea costurilor a, trei vectori cu următoarea semnificaţie:

- vectorul arborelui propriu-zis, ARB, în care fiecare element are valoarea 1, dacă vârful i a

fost deja inclus în arborele parţial, şi 0 în caz contrar; deci iniţial este 0, iar la introducerea

unui vârf i în arbore vom pune V[i]=1;

- vectorul părinţilor, P, în care pentru fiecare vârf adăugat i, elementul P[i] va reprezenta

vârful părinte al lui i;

- vctorul costurilor C, în care pentru fiecare vârf i elementul C[i] va avea valoarea costului

muchiei care îl leagă de părintele său în arbore.

Toţi cei trei vectori vor fi iniţializaţi cu 0.

Se va alege un vârf de plecare (prim), pe care îl includem în arbore (ARB[prim]=1). Apoi, într-

un ciclu vom adăuga câte un nou vârf în arbore, luând în considerare doar muchiile ce au un vârf în

arborele deja construit şi celălalt vârf în afara arborelui, iar dintre aceste muchii o vom alge pe cea care

are costul minim. Pentru această operaţie vom actualiza cei trei vectori. Dacă sunt mai multe care oferă

aceeaşi soluţie de cost minim, vom alege una din ele (prima sau ultima, în funcţie de cum se face

compararea), ceea ce înseamnă că nici soluţia nu este unică.

Să exemplificăm câţiva paşi pe următorul graf:

Page 10: SDA_c4

106

La primul pas se va iniţializa ARB[1]=1 (prim); muchia cu costul minim ce pleacă din vârful 1

este (1-2), de cost 12. La al doilea pas se caută muchii care pleacă fie din 1, şi au celălalt capăt diferit

de 2, fie pleacă din 5, şi au celălalt vârf diferit de 1, cu costul minim. În această situaţie sunt 2 muchii,

de cost minim (17), şi anume (1-5) şi (2-3). În funcţie de semnul comparaţiei (< sau <=) va fi selectată

prima sau cea de-a doua muchie, în cazul programului prezentat va fi aleasă muchia (1-5). În acest caz

se va continua cu alegerea unei muchii ce are un vârf în arborele creat (de ramuri: 1-2 şi 1-5) şi un altul

în afară, de cost minim. Se va determina vârful 4, corespunzător muchiei (5-4), de cost 14 (cost minim

dintre variantele rămase: 2-3/ cost 17, 1-3/ 22, 1-4/28). Deci acum arborele este format din ramurile: 1-

5-4, şi 1-2. La ultimul pas avem de ales între muchiile: 3-2 de cost 17, 3-1 de cost 22, şi 3-4 de cost 15.

Ultima este de cost minim şi va fi cea aleasă, astfel că arborele parţial de cost minim este format din

ramurile: 1-5-4-3 şi 1-2. În continuare este prezentat programul pentru a determina arborele parţial de

cost minim.

/* ArbCsMin.c - programul construieste un arbore partial de cost minim

pentru un graf neorientat, utilizand algoritmul lui Prim */

#include <stdio.h>

#include <conio.h>

#include <values.h>

#define DIMN 10 // numar maxim noduri

#define DIMM 100 // numar maxim muchii

void citire_graf (int a[DIMN+1][DIMN+1], int *pn, int *pm){

int i, j, k, cost;

clrscr();

printf("\nDati numar noduri, graf neorientat, n: ");

scanf("%d", pn);

printf("Nr. de muchii: ");

scanf("%d", pm);

for (i=1; i<= *pn; i++)

for (j=1; j<=*pn; j++)

a[i][j]=0; // initializare matrice drumuri (costuri) pt. graf

printf("\nCitire graf prin muchiile sale.\n");

for (k=1; k<= *pm; k++)

{

printf("Muchie, varfuri sursa, dest: ");

scanf ("%d%d", &i, &j);

printf("\tCost(%d-%d)= ", i, j);

scanf("%d", &cost);

a[j][i]=a[i][j]=cost; // pt. graf neorientat cost (i,j)=cost(j,i)

25

28 22

17

12

1

2

3

5

4

15

14

17

Page 11: SDA_c4

107

}

}

void construct_arb_cost_min (int a[DIMN+1][DIMN+1], int ARB[DIMM+1],

int P[DIMM+1], int C[DIMN+1], int n, int prim){

int k, i, j, cost_min, v1, v2;

for (i=1; i<=n; i++){ // initializare vectori V, P, C cu 0

ARB[i]=0; C[i]=0; P[i]=0;

}

ARB[prim]=1; // initializare varf de inceput

for (k=1; k<=n-1; k++){ // se dermina celalate n-1 varfuri ale arborelui

cost_min=MAXINT;

v1=0; v2=0;

for (i=0; i<=n; i++)

for (j=0; j<=n; j++)

if (ARB[i]==1 && ARB[j]==0)// i e in arbore, iar j nu

if(a[i][j]) // si i-j este muchie in graf cu

if (a[i][j]<cost_min){ // cost minim

cost_min=a[i][j];

v1=i; v2=j; // retin muchia

} // in v1-v2

ARB[v2]=1; // adaug varful de cost minim in arborele ARV

P[v2]=v1; // parintele lui 'v2' este 'v1'

C[v2]=cost_min; // retin costul minim pt v1-v2 in C

}

}

void tip_arb_cost_min (int C[DIMN+1], int P[DIMN+1], int nn){

int i, cm=0;

for (i=2; i<nn; i++){ // tiparim parintele-varful si costul ramurii

printf("%2d -%2d (%d) / ", P[i], i, C[i]);

cm+=C[i];

}

cm+=C[nn];

printf("%2d - %2d (%d)\n\t\tcost minim = %d\n", P[i], i, C[i], cm);

}

void main(){

int a[DIMN+1][DIMN+1]; // matricea de adiacenta (costuri/ drumuri)

int ARB[DIMN+1], C[DIMN+1], P[DIMN+1]; // ARB-arb partial, P-parinti, C-cost

int n, nm, primul, dimV, k;

citire_graf (a, &n, &nm);

printf("\nProgramul parcurge in latime graful.\n");

printf("\nSpecificati varful de start pentru parcurgere: ");

scanf("%d", &primul);

construct_arb_cost_min (a, ARB, P, C, n, primul);

printf("\nArborele de cost minim este urmatorul:\n");

tip_arb_cost_min (C, P, n);

printf("\nApasati o tasta pentru a termina programul\n");

getch();

}

Page 12: SDA_c4

108

4.3. Grafuri orientate Un graf orientat este o pereche ordonată de mulţimi (V, M), unde V este mulţimea finită şi

nevidă de vârfuri sau noduri, iar M este o mulţime de perechi ordonate de elemente din V, numite

muchii sau arce. Prin perechi ordonate se înţelege că muchiile sunt diferenţiate prin ordinea de scriere a

vârfurilor, adică arcul (x, y) nu este tot una cu arcul (y, x), cum era în cazul grafurilor neorientate. Ca o

consecinţă şi matricea costurilor (drumurilor) pentru un graf orientat nu mai este simetrică faţă de

diagonala principală (cum este în cazul grafurilor neorientate). Într-un graf orientat avem pentru un arc

dar un nod predecesor (de unde porneşte arcul) şi un nod succesor (unde ajunge arcul respectiv). Dacă

avem un arc (x, x), atunci pleacă din x şi se termină tot în x, şi se numeşte buclă.

Formele de reprezentare a grafurilor orientate sunt aceleaşi ca şi pentru cele orientate: prin

matricea de adiacenţă sau drumuri, prin vector de muchii sau prin lista vecinilor. Dacă în cazul

reprezentării prin matrice, de adiacenţă sau drumuri, diferenţele faţă de graful neorientat au fost

specificate, în cazul reprezentării prin vector de muchii, diferenţa faţă de un graf neorientat constă în

ordinea în care se specifică nodurile (vârfurile) corespunzătoare muchiei (arcului) respectiv: arcul (x, y)

nu este tot una cu arcul (y, x). Dacă folosim pentru reprezentare structura anterioară

(VECTOR_MUCHII) atunci putem nota v1 şi v2 cu vi (vârf iniţial, de pornire a arcului) şi respectiv vf

(vârf final, de terminare arc), şi putem asocia arcului şi costul (lungimea drumului) respectiv.

Definiţiile prezentate pentru grafuri neorientate pot fi extinse şi pentru cele orientate. De

exmplu, un graf orientat este conex dacă oricare ar fi două vârfuri ale sale, există un lanţ care le leagă

(pentru lanţ definiţia este aceeaşi, decât că acum avem un sens de parcurgere al grafului). Un graf

oreintat este tare conex, dacă oricare ar fi două vârfuri ale sale, există un lanţ care le leagă, de la x la y,

precum şi un lanţ de la y la x.

Exemple de probleme ce utilizează grafuri orientate.

1) Se consideră un graf orientat cu n noduri şi m arce; se cere să se determine nodurile izolate

din graf (cele prin care nu trece nici un arc).

/* nodizgrf.c - programul determina toate nodurile izolate dintr-un

graf orientat cu n noduri si m muchii,*/

#include <stdio.h>

#include <conio.h>

#define DIMN 10 // numar maxim noduri

#define DIMM 100 // numar maxim muchii

typedef struct{

int vi, vf; // cele doua varfuri ale unei muchii

} VECT_MUCHII;

void citire_graf (VECT_MUCHII vm[DIMM+1], int a[DIMN+1][DIMN+1],

int *pn, int *pm){

int i, j, k;

clrscr();

printf("\nDati numar noduri, graf orientat, n: ");

scanf("%d", pn);

printf("Nr. de muchii: ");

scanf("%d", pm);

for (i=1; i<= *pn; i++)

for (j=1; j<=*pn; j++)

a[i][j]=0; // initializare matrice drumuri (costuri) pt. graf

Page 13: SDA_c4

109

printf("\nCitire graf prin muchiile sale.\n");

for (k=1; k<= *pm; k++)

{

printf("Muchia %d, varf initial -> final: ", k);

scanf ("%d%d", &vm[k].vi, &vm[k].vf);

a[vm[k].vi][vm[k].vf]=1; // cost muchie vi-> vf

}

}

void init_nod_iz (int nod_iz[DIMM+1], int nn){

int i;

for (i=1; i<=nn; i++)

nod_iz[i]=0;

}

void determin_nod_iz (int a[DIMN+1][DIMN+1], int nod_iz[DIMN+1], int nn){

int i, j;

for (j=1; j<=nn; j++)

for (i=1; i<=nn; i++)

if (a[i][j])

nod_iz[j]++;

}

void tipar_nod_iz(int nod_iz[DIMM+1], int nn){

int i, ni=0;

printf("\nNoduri izolate:\n");

for (i=1; i<=nn; i++) // tipareste 'noduri izolate'

if (!nod_iz[i]){

printf("%5d", i);

ni++;

}

printf("\n");

if (ni)

printf("Numar de noduri izolate: %d.\n", ni);

else

printf("\tNu sunt noduri izolate.\n");

}

void main()

{

VECT_MUCHII vm[DIMM+1]; // vectorul de muchii (muchie=doua varfuri)

int nod_iz[DIMM+1], a[DIMN+1][DIMN+1]; // noduri izolate, matr. de adiacenta

int n, nm;

citire_graf (vm, a, &n, &nm);

init_nod_iz (nod_iz, n);

determin_nod_iz (a, nod_iz, n);

tipar_nod_iz (nod_iz, n);

printf("\n");

}

2) Se consideră un grup format din n persoane, care se cunosc sau nu între ele. Se citesc de la tastatură

perechi de numere întregi (i, j), care înseamnă că persoana i o cunoaşte pe persoana j, fără ca relaţia să

Page 14: SDA_c4

110

fie şi reciprocă. Să determinăm persoana cea mai cunoscută din grup şi persoana care cunoaşte cele mai

multe persoane din grup. Relaţia de “cunoaştere” poate fi reprezentată printr-un graf orientat. Pentru

aceasta este suficient să construim doi vectori, unul cu suma valorilor pe linii (număr de persoane

cunoscute de persoana i) şi altul cu suma valorilor pe coloane (numărul respectiv de persoane cunosc

persoana j). Vom selecta din primul vector maximul, adică persoana cu numărul maxim de cunoştinţe

în grup, şi din cel de-al doilea tot maximul, deci persoana cea mai cunoscută.

3) O companie dispune de un număr de sucursale în teritoriu, iar acestea sunt interconectate prin

drumuri, astfel încât pornind dintr-un punct se pot parcurge toate filialele (pe mai multe rute). Se

doreşte să se renunţe la o parte din aceste filiale pentru a reduce şi costurile cu drumurile. Din acest

punct de vedere se doreşte ca suma totală a drumurilor dintre filialele rămase să fie cuprinsă între două

valori date. Filialele ce rămân vor fi păstrate într-un vector solutie, care va conţine submulţimi ale

grafului iniţial, cu condiţia ca ultima filială din vector să fie identică cu prima (pentru a închide

circuitul) şi suma drumurilor să fie cuprinsă între cele două limite. Pentru a genera toate soluţiile se va

utiliza metoda backtracking, iar dacă se mai pune şi condiţia de optim (minim) se poate determina

soluţia optimă.

/* elimgraf.c - programul elimina noduri dintr-un graf, cu conditia

ca suma drumurilor pentru nodurile ramase sa fie cuprinsa intre

doua limite.

Se considera graf cu n noduri si m muchii, ce reprezinta configuratia

in teritoriu a unei companii impreuna cu sucursalele sale. Sucursalele

sunt interconectate intre ele prin drumuri de lungime data.

Matricea de adiacenta contine costurile (drumurile) intre aceste

filiale. Se doreste renuntarea la un numar de filiale, astfel incat

suma drumurilor intre filialele ramase sa fie cuprinsa intre 2 valori */

#include <stdio.h>

#include <conio.h>

#define DIMN 10 // numar maxim noduri

#define DIMM 100 // numar maxim muchii

typedef struct{ // cele doua varfuri ale unui arc orientat

int vi, vf; // vi, vf - varfurile initial si final al arcului

} VECT_MUCHII;

int nr_sol=0;

void citire_graf (VECT_MUCHII vm[DIMM+1], int a[DIMN+1][DIMN+1],

int *pn, int *pm){

int i, j, k, drum;

clrscr();

printf("\nDati numar noduri, graf orientat, n: ");

scanf("%d", pn);

printf("Nr. de muchii: ");

scanf("%d", pm);

for (i=1; i<= *pn; i++)

for (j=1; j<=*pn; j++)

a[i][j]=0; // init.(0) matrice drumuri (costuri) pt. graf

printf("\nCitire graf prin muchiile sale.\n");

for (k=1; k<= *pm; k++)

{

printf("Muchia %d, varf sursa -> destinatie: ", k);

Page 15: SDA_c4

111

scanf ("%d%d", &vm[k].vi, &vm[k].vf);

printf("Cost (%d -> %d)= ", vm[k].vi, vm[k].vf);

scanf ("%d", &drum);

a[vm[k].vi][vm[k].vf]=drum;

}

printf("\nMatricea drumurilor pentru graful introdus este:\n");

for (i=1; i<=*pn; i++){

for (j=1; j<=*pn; j++)

printf("%3d", a[i][j]);

printf("\n");

}

}

void init_solutie (int solutie[DIMM+2], int nm){

int i; // initializeaza solutia cu 0

for (i=1; i<=nm; i++)

solutie[i]=0;

}

int suma_drum_sol(int a[DIMN+1][DIMN+1], int solutie[DIMM+2],

int p, int dmin, int dmax){

int k, suma_drum=0; // drumul total al solutiei determinate

for (k=1; k<p; k++)

suma_drum+=a[solutie[k]][solutie[k+1]];

if (suma_drum >= dmin && suma_drum <= dmax)

return suma_drum; // daca satisface conditia returneaza suma

return 0; // altfel returneaza 0

}

void tipar_solutie(int a[DIMN+1][DIMN+1], int solutie[DIMM+2],

int p, int dmin, int dmax){

int i;

for (i=1; i<p; i++) // tipareste solutia 'p'

printf("%d -> ", solutie[i]);

printf("%d\n\tSuma drumurilor=%d\n", solutie[p],

suma_drum_sol(a, solutie, p, dmin, dmax));

if ((nr_sol+1)%10 == 0){ // s-a afisat un ecran, continua ? -> apasa tasta

printf("\nAfisarea continua dupa apasarea unei taste!\n");

getch();

}

}

int validare_solutie (int a[DIMN+1][DIMN+1], int solutie[DIMM+2], int p){

int k;

if ( !a[solutie[p-1]][solutie[p]] ) // daca nu e drum -> nu e valida solutia

return 0;

for (k=2; k<p; k++) // daca varful mai exista in 'solutia' pana la

if (solutie[k]==solutie[p]) // pozitia 'p-1' -> nu e solutie corecta

return 0; // varful mai poate fi doar pe prima pozitie

return 1;

}

void incerc_solutie(int a[DIMN+1][DIMN+1], int solutie[DIMM+2],

Page 16: SDA_c4

112

int n, int p, int dmin, int dmax){

int i;

for (i=1; i<=n; i++)

{solutie[p]=i; // initializare varf pe pozitia 'p'

if (validare_solutie(a, solutie, p))

if (solutie[1] == solutie[p] && p > 3 && // sol completa ?

suma_drum_sol(a, solutie, p, dmin, dmax))

{tipar_solutie(a, solutie, p, dmin, dmax);

nr_sol++;

}

else

incerc_solutie(a, solutie, n, p+1, dmin, dmax);

solutie[p]=0; // sterge ciclul incercat

}

}

void main(){

VECT_MUCHII vm[DIMM+1]; // vectorul de muchii (muchie=doua varfuri)

int sol[DIMM+2], a[DIMN+1][DIMN+1]; // vect culori si matricea de adiacenta

int n, nm, dmin, dmax;

citire_graf (vm, a, &n, &nm);

printf("Limitele pentru suma drumurilor din graf (min, max): ");

scanf("%d%d", &dmin, &dmax);

init_solutie(sol, nm);

printf("\nSolutiile din graf sunt urmatoarele:\n");

incerc_solutie(a, sol, n, 1, dmin, dmax);

if(nr_sol)

printf("\nNumarul total de solutii este: %d\n", nr_sol);

else

printf("\nProblema nu are solutii");

printf("\n");

}

4) Într-un grup de n persoane, în care nu toate se cunosc între ele (fiecare cunoaşte o parte dintre ele) o

persoană posedă o carte pe care doreşte să o transmită grupului, pentru a fi citită de toţi membrii

grupului. Fiecare persoană poate transmite cartea doar acelor persoane pe care le cunoaşte, după un

anumit interval de timp (zile), ce depinde de persoana căreia urmeză să-i dea cartea, şi care va fi

specificat pentru fiecare succesor. Să se determine o soluţie optimă, dacă există, astfel încât cartea să

trecă pe la fiecare persoană din grup, doar o singură dată, iar în final să revină la posesorul cărţii, după

un timp minim (optim), ştiind că transmiterea cărţii de la o persoană la alta necesită un anumit interval

de timp (de ordinul zilelor, inclusiv timpul necesar pentru lectură). Pentru fiecare transfer al cărţii

(muchie a grafului), de la persoana i la persoana j, se va specifica durata corespunzătoare.

Această problemă este asemănătoare cu precedenta, cu deosebirea că se pleacă dintr-un punct

(nod) dat şi se ajunge în final la acelaşi nod. Arcele grafului vor fi etichetate cu durata necesară

transferului cărţii de la persoana respectivă la succesor.

/* optigraf.c - programul parcurge optim nodurile dintr-un graf,

plecand dintr-un anumit nod, cu un cost (drum) optim.

Avem grup de n persoane, in care nu toate se cunosc intre ele

(fiecare cunoaste o parte dintre ele); o persoana poseda o carte

Page 17: SDA_c4

113

pe care doreste sa o transmita grupului, pentru a fi citita de toti

membrii grupului. Fiecare persoana poate transmite cartea doar acelor

persoane pe care le cunoaste. Fiecare persoana citeste si da cartea

intr-un anumit interval de timp (zile), ce depinde de persoana

careia urmeaza sa-i dea cartea, si care va fi specificat pentru

fiecare succesor.

Sa se determine o solutie optima, daca exista, astfel incat cartea sa

treca pe la fiecare persoana din grup, doar o singura data, iar

in final sa ajunga la posesorul cartii, astfel incat timpul dupa

care cartea revine la proprietar sa fie minim (optim).

Programul afiseaza toate solutiile, dar o retine pe cea optima. */

#include <stdio.h>

#include <conio.h>

#include <values.h>

#define DIMN 10 // numar maxim noduri

#define DIMM 100 // numar maxim muchii

typedef struct{ // cele doua varfuri ale unui arc orientat

int vi, vf; // vi, vf - varfurile initial si final al arcului

int timp; // timpul necesar pentru a citi cartea

} VECT_MUCHII;

int nr_sol=0, nr_sol_op=0, sol_optima[DIMM+1];

void citire_graf (VECT_MUCHII vm[DIMM+1], int a[DIMN+1][DIMN+1], int *pn){

int i, j, k, durata, pers, np;

clrscr();

printf("\nDati numar noduri, graf orientat, n: ");

scanf("%d", pn);

for (i=1; i<= *pn; i++)

for (j=1; j<=*pn; j++)

a[i][j]=0; // init.(0) matrice drumuri (costuri) pt. graf

printf("\nCitire graf prin varfurile (persoanele) sale.\n");

for (k=1; k<= *pn; k++)

{vm[k].vi=k;

printf("Numarul de persoane cunoscute de persoana (%d): ", k);

scanf("%d", &np);

for (i=1; i<=np; i++){

printf("\tcunostinta(%d): ", i);

scanf("%d", &pers);

vm[k].vf=pers;

printf("\t\ttransferul %d -> %d dureaza (zile): ", k, pers);

scanf("%d", &durata);

a[vm[k].vi][vm[k].vf]=durata;

}

vm[k].timp=durata;

}

printf("\nMatricea costurilor pentru graful introdus este:\n");

for (i=1; i<=*pn; i++){

for (j=1; j<=*pn; j++)

printf("%3d", a[i][j]);

printf("\n");

Page 18: SDA_c4

114

}

}

void init_solutie (int solutie[DIMM+1], int nn){

int i; // initializeaza solutia cu 0

for (i=1; i<=nn; i++)

solutie[i]=0;

}

int timp_sol(int a[DIMN+1][DIMN+1], int solutie[DIMM+1], int p){

int k, total_timp=0; // timpul total al solutiei determinate

for (k=1; k<p; k++)

total_timp+=a[solutie[k]][solutie[k+1]];

total_timp+=a[solutie[p]][solutie[1]];

return total_timp;

}

void tipar_solutie(int a[DIMN+1][DIMN+1], int solutie[DIMM+1], int p){

int i;

for (i=1; i<=p; i++) // tipareste solutia 'p'

printf("%d -> ", solutie[i]);

printf("%d\t/timp total=%d/ solutie %d\n\n", solutie[1],

timp_sol(a, solutie, p), nr_sol+1);

if ((nr_sol+1)%10 == 0){ // s-a afisat un ecran, continua ? -> apasa tasta

printf("\nAfisarea continua dupa apasarea unei taste!\n");

getch();

}

}

int validare_solutie (int a[DIMN+1][DIMN+1], int solutie[DIMM+1], int p){

int k;

if ( !a[solutie[p-1]][solutie[p]] ) // nu e legatura -> nu e valida solutia

return 0;

for (k=1; k<p; k++) // daca varful mai exista in 'solutia' pana la

if (solutie[k]==solutie[p]) // pozitia 'p-1' -> nu e solutie corecta

return 0;

return 1;

}

void copie_sol_optima(int solutie[DIMM+1], int sol_optima[DIMM+1], int nn){

int i;

for (i=1; i<=nn; i++)

sol_optima[i]=solutie[i];

}

void incerc_solutie(int a[DIMN+1][DIMN+1], int solutie[DIMM+1],

int n, int p, int varf_init, int *poptim){

int i;

for (i=1; i<=n; i++)

{solutie[p]=i; // initializare varf pe pozitia 'p'

if (validare_solutie(a, solutie, p))

if (p==n && a[solutie[p]][solutie[1]] && // sol completa ?

varf_init==solutie[1])

{tipar_solutie(a, solutie, p);

Page 19: SDA_c4

115

if (timp_sol(a, solutie, p) < *poptim){

copie_sol_optima(solutie, sol_optima, n);

*poptim=timp_sol(a, solutie, p);

nr_sol_op=nr_sol+1; // numarul sol optime

}

nr_sol++; // contor numar solutii

}

else

incerc_solutie(a, solutie, n, p+1, varf_init, poptim);

solutie[p]=0; // sterge varful incercat

}

}

void main(){

VECT_MUCHII vm[DIMM+1]; // vectorul de muchii (muchie=doua varfuri)

int sol[DIMM+1], a[DIMN+1][DIMN+1]; // vect solutie si matricea de adiacenta

int i, n, varf_ini, optim=MAXINT;

citire_graf (vm, a, &n);

printf("Varful initial (persoana de pornire): ");

scanf("%d", &varf_ini);

init_solutie(sol, n);

printf("\nSolutiile din graf sunt urmatoarele:\n");

incerc_solutie(a, sol, n, 1, varf_ini, &optim);

if(nr_sol)

{printf("\nNumarul total de solutii este: %d\n", nr_sol);

printf("\nSolutia optima este:\n");

for (i=1; i<=n; i++) // tipareste solutia optima

printf("%d -> ", sol_optima[i]);

printf("%d\t/timp total=%d/ solutie %d\n\n", sol_optima[1],

timp_sol(a, sol_optima, n), nr_sol_op);

}

else

printf("\nProblema nu are solutii");

printf("\n");

}

Page 20: SDA_c4

116

4.4. Determinarea celor mai scurte drumuri într-un graf Fie G = <V, M> un graf orientat, unde V este mulţimea vârfurilor şi M este mulţimea

muchiilor. Fiecărei muchii i se asociază o lungime (valoare pozitivă). Dorim să calculăm lungimea

celui mai scurt drum între fiecare pereche de varfuri.

Vom presupune ca vârfurile sunt numerotate de la 1 la n şi că matricea L dă lungimea fiecarei

muchii: L[i, i] = 0, L[i, j] >= 0 pentru i <> j, L[i, j] = +∞ dacă muchia (i, j) nu există. Principiul

optimalităţii este: dacă cel mai scurt drum de la i la j trece prin vârful k, atunci porţiunea de drum de la

i la k, cât şi cea de la k la j, trebuie să fie, de asemenea, optime.

Construim o matrice D care să conţină lungimea celui mai scurt drum între fiecare pereche de

vârfuri. Algoritmul de programare dinamică iniţializează pe D cu L. Apoi, efectuează n iteraţii. Dupa

iteraţia k, D va conţine lungimile celor mai scurte drumuri care folosesc ca vârfuri intermediare doar

vârfurile din {1, 2, ..., k}, iar dupa n iteraţii, obtinem rezultatul final. La iteraţia k, algoritmul trebuie să

verifice, pentru fiecare pereche de varfuri (i, j), dacă există sau nu un drum, ce trece prin varful k, care

este mai bun decât drum optim actual, ce trece doar prin varfurile din {1, 2, ..., k-1}. Fie Dk matricea D

dupa iteratia k. Verificarea necesară este atunci:

Dk[i, j] = min(Dk-1[i, j], Dk-1[i, k] + Dk-1[k, j])

S-a considerat că un drum optim care trece o dată prin k nu poate trece de două ori prin k.

Acest algoritm simplu este datorat lui Floyd (1962).

D=L

for (k=1; k<=n; k++)

for (i=1; i<=n; i++)

for (j=1; j<=n; j++)

if ( D[i][k] + D[k][j] < D[i][j] )

D[i][j] = D[i][k] + D[k][j];

Se deduce că algoritmul lui Floyd necesită un timp în O(n3). Un alt mod de a rezolva această

problemă este să aplicăm algoritmul Dijkstra de n ori, alegând mereu un alt vârf sursă. Se obţine un

timp în n O(n2), adica tot în O(n

3). Algoritmul lui Floyd, datorită simplităţii lui, are însă constanta

multiplicativă mai mică, fiind probabil mai rapid în practică. Dacă folosim algoritmul Dijkstra-

modificat în mod similar, obţinem un timp total în O(max(mn, n2) log n), unde m = număr muchii M.

Dacă graful este rar, atunci este preferabil să aplicăm algoritmul Dijkstra-modificat de n ori; dacă

graful este dens (m n2), este mai bine sa folosim algoritmul lui Floyd.

De obicei, dorim să aflăm nu numai lungimea celui mai scurt drum, dar şi traseul său. În acestă

situaţie, vom construi o a doua matrice P, iniţializată cu zero. Bucla cea mai interioară a algoritmului

devine

if ( D[i][k] + D[k][j] < D[i][j] )

{D[i][j] = D[i][k] + D[k][j];

P[i][j] = k; }

Când algoritmul se opreşte, P[i][j] va conţine vârful din ultima iteraţie care a cauzat o

modificare în D[i][j]. Pentru a afla prin ce vârfuri trece cel mai scurt drum de la i la j, testăm elementul

P[i][j]. Dacă P[i][j] = 0, atunci cel mai scurt drum este chiar muchia (i, j). Dacă P[i][j] = k, atunci cel

mai scurt drum de la i la j trece prin k şi urmează să testăm recursiv elementele P[i][k] şi P[k][j] pentru

a găsi şi celelalte vârfuri intermediare.

În continuare este prezentat întreg programul pentru acest algoritm.

/* Floyd.c - programul determina cele mai scurte drumuri dintr-un graf,

utilizand algoritmul Floyd (sau Djikstra modificat),

care poate fi aplicat atat pentru grafuri orientate cat si pentru

Page 21: SDA_c4

117

cele neorientate (se elimina linia care face simetrica matricea

drumurilor, din functia de citire graf). */

#include <stdio.h>

#include <conio.h>

#include <values.h>

#define DIM 6

int D[DIM+1][DIM+1], P[DIM+1][DIM+1], t[DIM+1];

int i, j, k, n, nm, sursa, dest;

char rasp[10];

void main(){

clrscr();

printf("\nDati numar noduri graf, n: ");

scanf("%d", &n);

printf("Nr. de muchii: ");

scanf("%d", &nm);

for (i=1; i<=n; i++)

for (j=1; j<=n; j++)

{D[i][j]=0; // initializare matrice drumuri (costuri) pt. graf

P[i][j]=0; // initializare predecesori

}

printf("Se citeste graful prin muchii, nod sursa, dest, si costul asociat.\n");

for (k=1; k<=nm; k++){

printf("Muchiile sursa, dest: ");

scanf ("%d%d", &i, &j);

printf("Cost(%d,%d): ", i, j);

scanf("%d", &D[i][j]); // daca graful e orientat se elimina linia urmatoare

D[j][i]=D[i][j]; // pt. graf neorientat costul (i,j)=cost(j,i)

}

for (i=1; i<=n; i++)

for (j=1; j<=n; j++)

if (D[i][j]==0) // initializare costuri pentru noduri fara

D[i][j]=1000; // legaturi (o valoare foarte mare)

else // initializare predecesor nod 'j'

P[i][j]=i; // cu nodul liniei (precedent) 'i'

for (k=1; k<=n; k++) // algoritmul Floyd de determinare a drumului minim

for (i=1; i<=n; i++) // intre doua noduri 'i' si 'j'

for (j=1; j<=n; j++) // se verifica toate cazurile i->k->j

if (i != j && D[i][j] > D[i][k]+D[k][j])

{ // daca se determina un k pt. care distanta

D[i][j]=D[i][k]+D[k][j];// este minima

P[i][j]=P[k][j]; // se actualizeaza distanta

} // si se retine nodul precedent lui j

for (i=1; i<=n; i++){ // afisare matrice distante (costuri)

for (j=1; j<=n; j++)

printf("%5d", D[i][j]);

printf("\n");}

do {

printf("\nDoriti distanta (costul) intre doua noduri (da/nu)? ");

fflush(stdin); // golire buffer tastatura

Page 22: SDA_c4

118

gets(rasp);

if (rasp[0]!='d' && rasp[0]!='D')

{fflush(stdin);

printf("\nApasati o tasta pentru a termina programul !\n");

getch();

exit(1);

}

printf("Drumul (costul) intre nodurile sursa, destinatie: ");

scanf("%d%d", &sursa, &dest);

k=dest;

i=0;

while (P[sursa][k] && P[sursa][k] != sursa)

{ // se determina lantul de invers succesori, de la dest

t[++i]=k; // la sursa, cu cond. sa existe conexiunea

k=P[sursa][k]; // sursa-k, si continua pana ajunge k la sursa

}

t[++i]=k;

t[++i]=sursa;

printf("Drumul (%d->%d): ", sursa, dest);

for (j=i; j>0; j--)

{

if (j!=1) printf("%2d -> ", t[j]);

else printf("%2d.", t[j]);

}

printf("\tcost: %d\n", D[sursa][dest]);

} while (rasp[0]=='d' || rasp[0]=='D');

}