PROIECT ANALIZA ALGORITMILOR - ALGORITMI PENTRU DRUMURI MINIME MULTIPUNCT-MULTIPUNCT

ANALIZA PROGRAMULUI

Programul prezinta doi algoritmi pentru gasirea drumurilor minime intre toate perechile de noduri dintr-un graf orientat (algoritmi multipunct). Cei doi algoritmi care pot fi aplicati grafurilor sunt Floyd-Warshall si Johnson. Aplicatia care prezinta cei doi algoritmi a fost realizata in limbajul Java si este structurata in mai multe clase fiecare indeplinind o anumita functie. Programul ofera utilizatorului posibilitatea sa construiasca un graf orientat si sa-i aplice unul dintre cei doi algoritmi. Acest proiect a fost realizat sub forma unui applet pentru a putea fi rulat pe o pagina de internet, dar totodata poate fi compilat si rulat pe un calculator ca o fereastra independenta de browserul web. In continuare voi prezenta clasele programului si rolul pe care acestea il au:

Majoritatea functiilor folosite, inclusiv cele pentru implementarea algoritmilor se gasesc in clasa Suprafata. Mai jos sunt niste snap-shoturi din timpul rularii programului.

In scopul usurarii prezentarii am adaugat si un exemplu gata construit pe care se poate vedea foarte bine cum functioneaza algoritmii utilizati.
In partea dreapta fereastra principala are mai multe butoane:

ANALIZA ALGORITMILOR

Problema drumurilor optime dintre varfurile unui graf poate fi rezolvata ruland un algoritm punct-multipunct de | V| ori pentru fiecare varf, unde V reprezinta numarul de varfuri ale grafului. Daca toate costurile muchiilor din graf au valori pozitive putem folosi Dijkstra. Daca algoritmul Dijkstra e implementat cu o coada cu prioritate sub forma unui vector atunci complexitatea va fi O(V3+VE)=O(V3).Daca este implementat cu heap-uri binare atunci complexitatea scade la O(VElgV) ceea ce reprezinta o imbunatatire pentru grafurile rare. Ca o alternativa se poate implementa coada cu prioritati folosind un heap Fibonacci care coboara complexitatea la O(V2lgV + VE). Daca graful are muchii cu cost negativ atunci Dijkstra nu mai poate fi folosit si se utilizeaza Bellman-Ford cate odata pentru fiecare nod. Complexitatea rezultata este O(V2E) ceea ce pentru un graf dens inseamna O(V4) (folosind Johnson putem obtine o complexitate mai mica).

FLOYD - WARSHALL

Algoritmul Floyd-Warshall rezolva problema drumurilor optime multipunct-multipunct intr-un graf. Nu se inpun restrictii ale arcelor, ci doar a structurii grafului. Graful nu trebuie sa contina cicluri cu cost negativ. Algoritmul este in categoria algoritmilor de programare dinamica. Solutiile problmei sunt construite recursiv asambland solutii ale unor probleme similere, dar de dimensiuni mai mici. Scris in pseudocod algoritmul Floyd-Warshall arata astfel:

Floyd-Warshall(G){
n = numar_noduri(G);
	for(i=1; i=n; i++)
	for(j=1; j=n; j++) {
		d(i,j)= w(i,j);
		if(w(i,j)=8) p(i,j)= null; else p(i,j)= i;
		}
	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); p(i,j)= p(k,j);}
			}
Daca presupunem ca graful e reprezentat printr-o matrice de adiacenta complexitatea algoritmului este O(n3). La terminarea algoritmului d(i,j) reprezinta costul drumului optim dintre i si j, iar p contine informatia cu care se poate construi acest drum optim. Acest algoritm este foarte folositor pentru grafuri medii ca marime. Codul sursa pentru functia care implementeaza acest algoritm este prezentat mai jos:
		
public void FloydWarshall() {
	fw = true;
	int suma = 0;
	   if (BellmanFord(0) == 0) parent.info.infodoc.showLine("string4");
		
for( int k = 0; k < nr_puncte; k++ )
		for( int i = 0; i < nr_puncte; i++ )
			for( int j = 0; j < nr_puncte; j++ ) {
			if (DC[i][k] == 999 || DC[k][j] == 999) suma = 999;
			else suma = DC[i][k] + DC[k][j];
			if( DC[i][j] > suma ) {
				DC[i][j] = DC[i][k] + DC[k][j];
				Pi[i][j] = Pi[k][j];
					}
				}
}

JOHNSON

Este un algoritm util in cazul grafurilor rare si este asimptotic mai bun decat Floyd-Warshall in cazul acestor grafuri. Algoritmul fie intoarce o matrice a drumurilor optime, fie avertizeaza ca in graf exista cel putin un ciclu cu cost negativ. Johnson foloseste ca subrutine Dijkstra si Bellman-Ford. Algoritmul foloseste tehnica recalcularii costurilor drumurilor dintre nodurile grafului folosind algoritmul Dijkstra pentru fiecare nod. In cazul in care se folosesc heap-uri binare complexitatea este O(VElgV). Daca graful are muchii negative algoritmul calculeaza un nou set de muchii nenegative care ne permit sa aplicam aceeasi metoda. Noul set de muchii trebuie sa indeplineasca urmatoarele conditii: - pentru oricare perechi de varfiri u,v un drum optim de la u la v este egal cu un drum optim si de la v la u folosind noile costuri; - pentru oricare muchie (u,v) costul e nenegativ; Procesarea matricei de adiacenta pentru a obtine noile costuri poate fi realizata in O(VE). Pentru a calcula noile costuri se adauga grafului inca un nod care are legaturi cu toate celelalte noduri (prin muchii de lungime 0). Ca acest nod sa poata fi adaugat ne trebuie o noua matrice cu un rang mai mare. Ruland Bellman-Ford pentru acest nod se obtin niste drumuri optime, retinute in vectorul h, care vor fi folosite la crearea noilor costuri. Codul sursa pentru acest algoritm este dat mai jos:

public void Johnson() {
		johnson = true;  	
	  for(int i=0;i < nr_puncte + 1;i++) {
		for(int j=0;j < nr_puncte+1;j++)
			if (i==0) G[i][j] = 0;
			else  if (j==0) G[i][j] = 999;		//se calculeaza noua matrice G 
				 else G[i][j] = D[i-1][j-1];      // pe baza celei initiale
			}

		
	   calcLaturi();      // functia care enumera laturile grafului (nod1,nod2)
	   //se ruleaza Bellman-Ford
   if (BellmanFord(0,G)==0) parent.info.infodoc.showLine("string4");
	   else  for(int i=0;i < nr_puncte+1;i++)
				h[i]=d[i];

	    //se calculeaza noile costuri	
	    for(int i=0;i < nr_laturi;i++) {
	   G[e[i][0]][e[i][1]] = G[e[i][0]][e[i][1]] + h[e[i][0]] - h[e[i][1]];
	   if (e[i][0]!=0 && e[i][1]!=0) DC[e[i][0]-1][e[i][1]-1] = G[e[i][0]][e[i][1]];
	   }
	  //ruleaza Dijkstra pentru toate nodurile
		for(int i=0;i < nr_puncte;i++) {
			Dijkstra(i);
			for(int j=0;j < nr_puncte;j++) {
				if (t[j]== -1) Pi[i][j] = t[j];
				else if (i!=j ) Pi[i][j] = t[j]+1;
						else  Pi[i][j] = t[i];
				int suma = 0;
				if (d[j] == 999 || h[j+1] == 999 || h[i+1] == 999) suma =999;
				else suma = d[j] +h[j+1] - h[i+1];
				matback[i][j] = suma;
				 }
			}
	 	for(int i=0;i < nr_puncte;i++) {
	 		for(int j=0; j < nr_puncte;j++)
	 		DC[i][j] = matback[i][j];
	 		}
	 }

DIJKSTRA SI BELLMAN-FORD

Acestia rezolva problema drumurilor de cost minim punct-multipunct. Ambii folosesc urmatoarele doua functii:

init_graf(G,s){
V = noduri(G);
for-each(u.V) {d(u)=8; p(u)=null;}
d(s)=0;
}

relaxare_arc(u,v){
if(d(v)>d(u)+w(u,v)) {d(v)=d(u)+w(u,v); p(v)=u;}
}

void Dijkstra(int source) {
	int minKey = 0;
	int u = 0;

	initialize(source);
	int x = nr_puncte+1;
	PriorityQueue Q = new PriorityQueue(x,d);


	while(Q.heapSize() > 0) {
	 minKey = Q.getMin();
	 u = Q.extractMin();
		d[u] = minKey;
		s[u] = 1;
		for(int j=1;j <= Q.heapSize();j++)  relax1(Q,u,j);
		Q.makeHeap();	
		}	
		
	}
int BellmanFord(int source) {
		initialize(source);    
		for(int i=0;i < nr_puncte+1;i++)         
			for(int j=0;j < nr_laturi;j++)  
				relax(e[j][0],e[j][1]);
		for(int i=0;i < nr_laturi;i++)
			if(d[e[i][1]] > d[e[i][0]]+G[e[i][0]][e[i][1]]) return 0;
   		return 1;
   }

Algoritmul Dijkstra lucreaza pentru costuri pozitive ale arcelor grafului . Dijkstra poate fi rescris īn scopul determinarii eficiente a nodului cu d(u) minim, eliminat din V īn fiecare etapa a algoritmului. Īn acest caz, multimea V poate fi organizata ca heap minimizant īn raport cu proprietatea d a nodurilor. Folosind notatiile n = card(V) si m = card(E), complexitatea algoritmului de mai sus este

CLASELE PROIECTULUI - COD SURSA