Explorați Cărți electronice
Categorii
Explorați Cărți audio
Categorii
Explorați Reviste
Categorii
Explorați Documente
Categorii
Parcurgerea grafurilor
Atestat la informatica
Parcurgerea grafurilor
Scop:
Parcurgerea in latime se foloseste: - pentru Inteligenta Artificiala (metoda neinformata de cautare... mai multe la cursul de IA); - intr-o multime de aplicatii din teoria grafurilor (in special legate de drum minim de la un nod dat la un alt nod, componente conexe, grafuri bipartite); - in jocuri pe calculator, pentru gasirea drumurilor, in special in jocuri Real Time Strategy (un mic exemplu in aplicatie); Parcurgerea in adancime se foloseste: - pentru Inteligenta Artificiala (metoda neinformata de cautare... mai multe la cursul de IA), insa combinarea sa cu euristici poate fi mai fructuoasa decat la parcurgerea in latime; - sortarea topologica si alte aplicatii cu grafuri legate de componente conexe si tare conexe.
1. Intro:
Def.: Graful este o pereche de multimi G=(V,E). Multimea V contine nodurile grafului (vertices), iar multimea E contine muchiile (edges) sale, fiecare muchie stabilind o relatie de vecinatate intre doua noduri. O parcurgere isi propune sa ia in discutie fiecare nod al grafului exact o singura data, pornind de la un nod ales, numit in continuare nod sursa. Parcurgerea in latime viziteaza intai toti vecinii unui nod dat si numai dupa ce epuizeaza toti vecinii trece la un alt nod. Parcurgerea in adancime isi propune sa mearga din vecin in vecin al vecinului cat de mult poate, adancindu-se astfel in structura grafului.
Parcurgerea in latime foloseste o coada (Q) intr-un mod asemanator celui folosit de algoritmul AC-3. Initial in aceasta coada se gaseste doar nodul sursa. Vizitand pe rand vecinii sai ii adaugam si pe ei in coada. La sfarsit, dupa ce nu mai sunt vecini ai sursei nevizitati, scoatem nodul sursa din coada.
Pentru fiecare din nodurile prezente in coada, aplicam procedura de mai sus. Atentie insa: vizitand vecinii unui nod trebuie sa verificam ca acestia nu au mai fost deja vizitati (adica sunt inca albi).
...
In afara de coada q mai sunt necesare sau utile cateva structuri de date. a) Putem diferentia intre nodurile nevizitate, cele asezate in coada si cele complet prelucrate printr-un vector denumit culoare: c[nod] = alb, daca nodul nu a fost vizitat gri, daca nodul a fost vizitat si asezat in q negru, daca nodul a fost prelucrat si scos din q b) Un vector d (distanta) care sa retina distanta (in numar de muchii) fata de sursa se poate dovedi util in unele probleme. c) Vectorul de parinti p este necesar pentru a reface drumul de la sursa la un alt nod dupa parcurgere.
2.2. Algoritmul
Algoritmul conform specificatiilor de mai sus:
// =============== initializari =================== pentru fiecare nod u { c[u] = alb; d[u] = infinit; p[u] = null; } c[sursa] = gri; d[s] = 0; q = {s}; // ================ algoritm =================== cattimp (q nu este vida) { v = extrage nod din coada q; pentru fiecare u dintre vecinii lui v if (c[u] == alb) { c[u] = gri; p[u] = v; d[u] = d[v]+1; insereaza in coada q (u); } c[v] = negru; }
Obs: Pentru ca muchiile care unesc noduri deja gri nu sunt practic folosite, se intuieste asezarea muchiilor folosite intr-un graf conex si fara cicluri (adica un arbore):
Obs: Daca graful are mai multe componente conexe, algoritmul, in forma data aici, va parcurge doar componenta din care face parte nodul sursa. Pe grafuri alcatuite din mai multe componente conexe se vor obtine astfel mai multi arbori, cate unul pentru fiecare componenta.
Semnificatia culorilor ramane aceeasi ca la BFS: - alb daca nodul nu a fost vizitat; - gri daca nodul a fost vizitat dar nu i-am parcurs toti vecinii; - negru daca am parcurs toti vecinii nodului. Vom mai introduce doua variabile care se vor dovedi utile in unele aplicatii: - tDesc (timpul descoperirii): pasul la care se marcheaza nodul cu gri; - tFin (timpul final): pasul la care se marcheaza nodul cu negru.
3.2. Algoritmul: Punand cap la cap informatiile de pana acum obtinem urmatorul algoritm:
// ================== initializari ==================== pentru fiecare nod u al grafului {
culoare[u] = alb; p[u] = NULL; tDesc = 0; tFin = 0; } timp = 0; // === pentru a ne asigura ca se parcurg toate componentele conexe = pentru fiecare nod al grafului daca (culoare[nod] == alb) PARCURGE(u); // ==================== va parcurge o componenta conexa ======= PARCURGE(u) { culoare[u] = gri; tDesc[u] = ++ timp; pentru fiecare v vecin al lui u { daca (culoare[v] == alb) { p[v] = u; PARCURGE[v]; } } culoare[u] = negru; tFin[u] = ++timp; }
Obs: Algoritmul este facut pentru a acoperi toate nodurile, chiar daca graful are mai multe componente conexe. Metoda PARCURGE corespunde acoperirii unei componente conexe. Obs: Ganditi-va la asemanarile cu BKT. Daca am avea un arbore in care fiecare nod ar reprezenta asocierea unei variabile cu o valoare... si de la fiecare nod s-ar porni catre toate atribuirile posibile ale urmatoarei variabile... parca seamana, nu? Mai mult decat atat, si la DFS, ca si la BKT, ne putem intreba in ce ordine sa luam vecinii ca sa ajungem mai repede intr-un nod anume...
Obs.: Si la DFS, ca si la BFS, in urma parcurgerii se obtin arbori. Acestia se numesc arbori de adancime. Spre exemplu:
Evident, pe acest arbore s-ar putea adauga si muchiile pe care nu le-am folosit in parcurgere, dar l-ar face sa nu mai fie arbore. Totusi, in cazul grafurilor orientate, si aceste muchii au o semnificatie speciala.
Am transformat graful din exemplul precedent intr-unul orientat si i-am mai adaugat cateva noduri. Si de data asta i-am figurat toate muchiile. Acestea vor fi: - muchii inainte: muchia (1,4); - muchii inapoi: muchia (3,1); - muchii de traversare: muchia (9,1).
Ei bine, de aici si pana la a obtine o succesiune de noduri ordonate nu este decat un singur pas: avem grija sa asezam nodul radacina al fiecarui subarbore intr-o stiva, iar pentru afisare scoatem pe rand cate un nod din stiva tiparind odata cu el si nodurile din arborele sau in ordinea obtinuta.
Obs: Pentru un graf dat pot exista mai multe insiruiri de noduri care sa respecte cerinta. Convingeti-va ca solutia obtinuta este intr-adevar corecta si ca toate solutiile care se pot obtine prin aceasta metoda sunt si ele corecte. Obs: Nu este singurul algoritm pentru sortare topologica. Optional: Un alt algoritm pentru sortarea topologica:
Algoritmul urmator se aplica recursiv. La fiecare pas se elimina un nod in care nu intra nici o muchie, nod pe care il furnizam catre output. Prin eliminare se intelege implicit ca se elimina si muchiile adiacente nodului respectiv. Din moment ce nu erau muchii care sa intre in el, asta inseamna ca se elimina muchiile care pornesc din el. Daca la un moment dat mai avem noduri si nu mai putem face eliminare, inseamna ca exista un ciclu in graf si nu exista sortare topologica pentru el.
Grafuri neorientate
Graf = orice mulime finit V prevzut cu o relaie binar intern E. Notm graful cu G=(V, E). Graf neorientat = un graf G=(V, E) n care 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 vrfuri din V, unde G=(V, E) este un graf neorientat;
1 6 5 3 4
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]})
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.
2 3 4
Nodurile 1, 2, 4 au gradele egale cu 2. Lan = este o secven de noduri ale unui graf neorientat G=(V,E), cu proprietatea c oricare dou noduri consecutive din lant sunt adiacente: L=[w1, w2, w3,. . ,wn] cu proprietatea c (wi, wi+1) E pentru 1 i<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
6 5 3 4
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.
6 5 3 4
6 5 3 4
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 V V se numete subgraf al lui G.
1 6 5 3 4 6 5 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 4
Graf conex = graf neorientat G=(V,E) n care pentru orice pereche de noduri (v,w) exist un lan care le unete.
6 5 3 4
6 5 3 4
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 3 4
Lan hamiltonian = un lan elementar care conine toate nodurile unui graf
1 6 5 3 4
L=[2 ,1, 6, 5, 4, 3] este lant hamiltonian Ciclu hamiltonian = un ciclu elementar care conine toate nodurile grafului
1 6 5 3 4
Graf hamiltonian = un graf G care conine un ciclu hamiltonian Graful anterior este graf Hamiltonian. Lan eulerian = un lan simplu care conine toate muchiile unui graf
6 5 3 4
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.
Matricea de adiacent matricea boolean Matricea de adiacent asociat unui graf neorientat cu n noduri se defineste astfel: A = (ai j) n x n cu a[i,j]= 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 1, daca [i,j]E 0, altfel
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 4 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 4- reprezentand m, numarul de muchii 4 perechi x-y reprezentand extremitatile celor 4 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 Probleme propuse: Problema 1 Se citeste un graf din fisierul graf.txt: numarul de noduri, numarul de muchii si muchiile. a) sa se afiseze matricea de adiacente
b) Sa se determine gradul unui nod citit c) Sa se afiseze pentru un nod citit nodurile adiacente d) sa se afiseze nodurile incidente cu cea de a x muchie din matrice e) sa se afiseze pentru fiecare nod gradul f) sa se afiseze nodul (nodurile) avand cei mai multi vecini g) sa se afiseze nodurile izolate Problema 2 Sa se det daca o matrice citita dintr-un fisier poate fi matricea unui graf neorientat. In caz afirmativ se va determina cate muchii are graful Problema 3 Sa se genereze un graf avand maxim n noduri si maxim m muchii. Sa se afiseze matricea atasata Observatie: graful nu poate fi decat cel mult complet
Nodul 6 nu are vecini (este izolat) Pentru intreg graful vom avea mai multe liste : 2 4 Nodul 1 : 1 3 2 1 3 4 3 5
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. Probleme propuse : 1. 1. Sa se memoreze un graf neorientat utilizand liste de adiacente. Parcurgand listele de adiacenta rezolvati urmatoarele cerinte : a. b. c. d. e. f. a. b. c. d. e. f. Sa se determine daca graful contine noduri izolate Sa se determine fradul unui nod citit de la tastatura Sa se determine nodul cu cel mai mare grad Sa se determine daca nodurile x si y sunt adiacente Sa se verifice daca graful este regulat Sa se determine daca graful este complet
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 V1 exista un lant de la x la y (implicit si de la y la x) - - nu exista alt subgraf al lui G, G2=(V2, E2) care sa indeplineasca prima conditie si care sa-l contina pe G1
1 6 7 5 3 4
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:
1 6 7 5 3 4
Probleme:
1. 1. Fiind dat un graf memorat prin intermediul matricei de adiacenta sa se determine daca graful este conex. 2. 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. 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 4
-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();}