Sunteți pe pagina 1din 17

Ministerul Educatiei a Republicii Moldova

Universitatea Tehnica aMoldovei

R APO R T
Analiza si Proiectarea Algoritmilor
Lucrarea nr.3
Tema: Tehnica greedy de proiectare a algoritmilor

A elaborat

st. gr. TI-

A verificat:

Bagrin Veronica

Chiinu 2015

Scopul lucrrii:
1. Studierea tehnicii greedy.
2. Analiza i implementarea algoritmilor greedy

Consideratii teoretice:

Algoritmii greedy (greedy = lacom) sunt n general simpli i sunt folosii la probleme de
optimizare, cum ar fi: s se gseasc cea mai bun ordine de executare a unor lucrri pe calculator, s
se gseasc cel mai scurt drum ntr-un graf etc. n cele mai multe situaii de acest fel avem:
o mulime de candidai (lucrri de executat, vrfuri ale grafului etc);
o funcie care verific dac o anumit mulime de candidai constituie o soluie posibil, nu
neaprat optim, a problemei;
o funcie care verific dac o mulime de candidai este fezabil, adic dac este posibil s
completm aceast mulime astfel nct s obinem o soluie posibil, nu neaprat optim, a
problemei;
o funcie de selecie care indic la orice moment care este cel mai promitor dintre candidaii
nc nefolosii;
o funcie obiectiv care d valoarea unei soluii (timpul necesar executrii tuturor lucrrilor
ntr-o anumit ordine, lungimea drumului pe care l-am gsit etc); aceasta este funcia pe care urmrim
s o optimizm (minimizm/maximizm).
Pentru a rezolva problema de optimizare, se caut o soluie posibil care s optimizeze valoarea
funciei obiectiv. Un algoritm greedy construiete soluia pas cu pas. Iniial, mulimea candidailor
selectai este vid. La fiecare pas, se adaug acestei mulimi cel mai promitor candidat, conform
funciei de selecie. Dac, dup o astfel de adugare, mulimea de candidai selectai nu mai este
fezabil, se elimin ultimul candidat adugat; acesta nu va mai fi niciodat considerat. Dac, dup
adugare, mulimea de candidai selectai este fezabil, ultimul candidat adugat va rmne de acum
ncolo n ea. De fiecare dat cnd se lrgete mulimea candidailor selectai, se verific dac aceast
mulime nu constituie o soluie posibil a problemei. Dac algoritmul greedy funcioneaz corect,
prima soluie gsit va fi totodat o soluie optim a problemei. Soluia optim nu este n mod necesar
unic: se poate c funcia obiectiv s aib aceeai valoare optim pentru mai multe soluii posibile.
Funcia de selecie este de obicei derivat din funcia obiectiv; uneori aceste dou funcii sunt chiar
identice.

Algoritmul lui Kruskal


Arborele parial de cost minim poate fi construit muchie, cu muchie, dup urmtoarea metoda a
lui Kruskal (1956): se alege nti muchia de cost minim, iar apoi se adaug repetat muchia de cost
minim nealeas anterior i care nu formeaz cu precedentele un ciclu. Alegem astfel #V1 muchii. Este
uor de dedus c obinem n final un arbore. Este nsa acesta chiar arborele parial de cost minim
cutat?
Proprietatea 1. n algoritmul lui Kruskal, la fiecare pas, graful parial <V, A> formeaz o
pdure de componente conexe, n care fiecare component conex este la rndul ei un arbore parial de
cost minim pentru vrfurile pe care le conecteaz. n final, se obine arborele parial de cost minim al
grafului G.
Pentru a implementa algoritmul, trebuie s putem manipula submulimile formate din vrfurile
componentelor conexe. Folosim pentru aceasta o structura de mulimi disjuncte i procedurile de tip
find i merge (Seciunea 3.5). n acest caz, este preferabil s reprezentm graful c o lista de muchii cu
costul asociat lor, astfel nct s putem ordona aceast list n funcie de cost. Iat algoritmul:
function Kruskal(G = <V, M>)
{iniializare}
sorteaz M cresctor n funcie de cost
n #V
2

A {va conine muchiile arborelui parial de cost minim}


iniializeaz n mulimi disjuncte coninnd fiecare cate un element din V
{bucla greedy}
repeat
{u, v} muchia de cost minim care nc nu a fost considerat
ucomp find(u)
vcomp find(v)
if ucomp vcomp then merge(ucomp, vcomp)
A A {{u, v}}
until #A = n-1
return A
Pentru un graf cu n vrfuri i m muchii, presupunnd c se folosesc procedurile find3 i merge3,
numrul de operaii pentru cazul cel mai nefavorabil este n:
O(m log m) pentru a sorta muchiile. Deoarece m n(n1)/2, rezulta O(m log m) O(m log n). Mai
mult, graful fiind conex, din n1 m rezulta i O(m log n) O(m log m), deci
O(m log m) = O(m log n).
O(n) pentru a iniializa cele n mulimi disjuncte.
Cele cel mult 2m operaii find3 i n1 operaii merge3 necesita un timp n O((2mn1) lg* n).
Deoarece O(lg* n) O(log n) i n1 m, acest timp este i n O(m log n).
O(m) pentru restul operaiilor.
Deci, pentru cazul cel mai nefavorabil, algoritmul lui Kruskal necesit un timp n O(m log n).
O alt variant este s pstram muchiile ntr-un min-heap. Obinem astfel un nou algoritm, n
care iniializarea se face ntr-un timp n O(m), iar fiecare din cele n1 extrageri ale unei muchii minime
se face ntr-un timp n O(log m) = O(log n). Pentru cazul cel mai nefavorabil, ordinul timpului rmne
acelai cu cel al vechiului algoritm. Avantajul folosirii min-heap-ului apare atunci cnd arborele parial
de cost minim este gsit destul de repede i un numr considerabil de muchii rmn netestate. n astfel
de situaii, algoritmul vechi pierde timp, sortnd n mod inutil i aceste muchii.

Algoritmul lui Prim


Cel de-al doilea algoritm greedy pentru determinarea arborelui parial de cost minim al unui graf
se datoreaz lui Prim (1957). n acest algoritm, la fiecare pas, mulimea A de muchii alese mpreun cu
mulimea U a vrfurilor pe care le conecteaz formeaz un arbore parial de cost minim pentru
subgraful <U, A> al lui G. Iniial, mulimea U a vrfurilor acestui arbore conine un singur vrf
oarecare din V, care va fi rdcina, iar mulimea A a muchiilor este vid. La fiecare pas, se alege o
muchie de cost minim, care se adaug la arborele precedent, dnd natere unui nou arbore parial de
cost minim. Arborele parial de cost minim creste natural, cu cate o ramur, pn cnd va atinge
toate vrfurile din V, adic pn cnd U = V.
Proprietatea 2. n algoritmul lui Prim, la fiecare pas, <U, A> formeaz un arbore parial de cost
minim pentru subgraful <U, A> al lui G. n final, se obine arborele parial de cost minim al grafului
G.
Presupunem c vrfurile din V sunt numerotate de la 1 la n, V = {1, 2, ..., n}, matricea simetric
C d costul fiecrei muchii, cu C[i, j] = , dac muchia {i, j} nu exist. Folosim dou tablouri
paralele. Pentru fiecare i V \ U, vecin[i] conine vrful din U, care este conectat la i printr-o muchie
de cost minim, mincost[i] d acest cost. Pentru i U, punem mincost[i] = 1. Mulimea U, n mod
arbitrar iniializat cu {1}, nu este reprezentat explicit. Elementele vecin[1] i mincost[1] nu se
folosesc.
function Prim(C[1 .. n, 1 .. n])
{iniializare, numai vrful 1 este n U}
3

A
for i 2 to n do vecin[i] 1
mincost[i] C[i, 1]
{bucla greedy}
repeat n1 times
min +
for j 2 to n do
if 0 < mincost[ j] < min then min mincost[ j]
kj
A A {{k, vecin[k]}}
mincost[k] 1 {adaug vrful k la U}
for j 2 to n do
if C[k, j] < mincost[ j] then mincost[ j] C[k, j]
vecin[ j] k
return A
Bucla principal se execut de n1 ori i, la fiecare iteraie, buclele for din interior necesit un timp n
O(n). Deci, algoritmul Prim necesit un timp n O(n2). Am vzut c timpul pentru algoritmul lui
Kruskal este n O(m log n), unde m = #M. Pentru un graf dens se deduce c m se apropie de n(n1)/2.
n acest caz, algoritmul Kruskal necesit un timp n O(n2 log n) i algoritmul Prim este probabil mai
bun. Pentru un graf rar m se apropie de n i algoritmul Kruskal necesit un timp n O(n log n), fiind
probabil mai eficient dect algoritmul Prim.

Listingul programului
I.Algoritmul Kruskal:
#include<stdio.h>
#include<conio.h>
#include<process.h>
#include<iostream>
#include <time.h>
#include <dos.h>
using namespace std;
struct Graf
{
int x;
int y;
int cost;
}*arc,*virf;
int nv,na,*set,*nod,*h,**mad;
void introd()
{
int i,j;
cout<<"\n|V| = ";
cin>>nv;nod=new int[nv];set=new int[nv];h=new int[nv];
for(i=0;i<nv;i++)
{
nod[i]=i;
set[i]=i;
h[i]=0;
}
cout<<"\n|A| = ";
cin>>na;
arc=new Graf[na];
4

cout<<"\nDati x, y, costul :\n";


for(i=0;i<na;i++)
{
cin>>arc[i].x;arc[i].x-=1;
cin>>arc[i].y;arc[i].y-=1;
cin>>arc[i].cost;
}
}
void merge(int a,int b)
{
if(a!=b)
{
if(h[a]==h[b])
{
h[a]++;
set[b]=a;
}
else
{
if(h[a]>h[b]) set[b]=a;
else set[a]=b;
}
}
}
int gaseste(int x)
{
int i,j,r=x;
while(set[r]!=r) r=set[r];
i=x;
while(i!=r)
{
j=set[i];
set[i]=r;
i=j;
}
return r;
}
void SortCost(int &count)
{
int i,j;
Graf tmp;
for(i=0;i<na-1;i++)
{
count++;
for(j=i+1;j<na;j++)
{
count++;
if(arc[i].cost>arc[j].cost)
{
tmp=arc[j];
arc[j]=arc[i];
arc[i]=tmp;
}
5

}
}
}
void Kruskal()
{
int count=0;
SortCost(count);
virf=new Graf[nv-1];
Graf tmp;
int j=0,ucomp,vcomp;
for(int i=0;i<nv-1;i++)
{
count++;
do
{
count++;
tmp=arc[j];
ucomp=gaseste(arc[j].x);
vcomp=gaseste(arc[j].y);
if(ucomp!=vcomp)
{
merge(ucomp,vcomp);
virf[i]=tmp;
break;
}
j++;
}
while (1);
}
cout<<"\n\nNr de iteratii este : "<<count<<"\n\n";
}
void AfisVirf()
{
cout<<"\n\nVirfurile prelucrate sunt : {";
for(int i=0;i<nv;i++)
cout<<nod[i]+1<<",";
printf("%c}",8);
}
void AfisMuchii()
{
cout<<"\nMuchiile ce participa in algoritmul Kruskal sunt :\n";
for(int i=0;i<nv-1;i++)
cout<<"\n("<<virf[i].x+1<<","<<virf[i].y+1<<"); cu costul = "<<virf[i].cost;
}
int main()
{
cout<<"\n\t\tALGORITMUL KRUSKAL\n";
int L=0;
introd();
clock_t start,end;
start=clock();
6

Kruskal();
end=clock();
printf("Timpul de executie este : %.8f\n",((double)(clock()-start))/CLOCKS_PER_SEC);
AfisMuchii();AfisVirf();
for(int i=0;i<nv-1;i++)
L=L+virf[i].cost;
cout<<"\n\nLmin = "<<L;
getch();

}
II.Algoritmul Prim:
#include<stdio.h>
#include<conio.h>
#include<process.h>
#include<iostream>
#include<stdlib.h>
#include<dos.h>
#include<time.h>
using namespace std;
struct Graf
{
int x,y,cost;
Graf(int x,int y,int c)
{
this->x=x;
this->y=y;
this->cost=c;
}
Graf(Graf&a)
{
this->x=a.x;
this->y=a.y;
this->cost=a.cost;
}
}**arc,**virf;
int nv,na,*nod,*u,*vecin,*costmin,**matr;
void introd()
{
int i,j,x,y,c;
cout<<"\n|V| = ";
cin>>nv;
nod=new int[nv];
for(i=0;i<nv;i++)
nod[i]=i;
cout<<"\n|A| = ";
cin>>na;
arc=new Graf*[na];
cout<<"\nDati x, y, costul :\n";
for(i=0;i<na;i++)
{
cin>>x;x-=1;
cin>>y;y-=1;
cin>>c;
arc[i]=new Graf(x,y,c);
7

}
}
void Big()
{
int i,j;
matr=new int*[nv];
for(i=0;i<nv;i++)
{
matr[i]=new int[nv];
for(j=0;j<nv;j++)
matr[i][j]=999;
}
for(i=0;i<na;i++)
{
matr[arc[i]->x][arc[i]->y]=arc[i]->cost;
matr[arc[i]->y][arc[i]->x]=arc[i]->cost;
}
}
Graf *find(int k,int j)
{
for(int i=0;i<na;i++)
if((arc[i]->x==k&&arc[i]->y==j)||(arc[i]->y==k&&arc[i]->x==j))
return arc[i];
return arc[0];
}
void Prim(int &count)
{
int i,j,k,min;
vecin=new int[nv];
costmin=new int[nv];
u=new int[nv];
for(i=0;i<nv;i++)
u[i]=-1;
virf=new Graf*[nv-1];
Graf *tmp;
u[0]=0;//rand()%nv+1;
for(i=0;i<nv;i++)
{
count++;
if(i==u[0]) continue;
vecin[i]=u[0];
costmin[i]=matr[i][u[0]];
}
for(i=0;i<nv-1;i++)
{
count++;
min=999;
for(j=1;j<nv;j++)
{
count++;
if(0<costmin[j]&&costmin[j]<min)
{
min=costmin[j];
8

k=j;
}
}
tmp=find(k,vecin[k]);
virf[i]=tmp;
costmin[k]=-1;
for(j=1;j<nv;j++)
{
count++;
if(matr[k][j]<costmin[j])
{
costmin[j]=matr[k][j];
vecin[j]=k;
}
}
}
}
void AfisVirf()
{
cout<<"\n\nVirfurile prelucrate sunt : {";
for(int i=0;i<nv;i++)
cout<<nod[i]+1<<",";
printf("%c}",8);
}
void AfisMuchii()
{
cout<<"\n\nMuchiile ce participa in algoritmul Prim sunt :\n";
for(int i=0;i<nv-1;i++)
cout<<"\n("<<virf[i]->x+1<<","<<virf[i]->y+1<<"); cu costul = "<<virf[i]->cost;
}
int main()
{
cout<<"\n\t\tALGORITMUL PRIM\n";
int L=0,count=0;
introd();Big();
clock_t start,end;
start=clock();
Prim(count);
end=clock();
cout<<"\nNr. de iteratii este : "<<count;
printf("\n\nTimpul de executie este : %.8f",((double)(clock()-start))/CLOCKS_PER_SEC);
AfisMuchii();AfisVirf();
for(int i=0;i<nv-1;i++)
L=L+virf[i]->cost;
cout<<"\n\nLmin = "<<L;
getch();
}

Analiza comparativa a rezultatelor:


I.Kruskal:

10

11

II.Prim

12

13

14

Analiza impirica a algoritmilor:


Exemplu
Algoritmu
l

Nr.iteratii

Timpul

Kruskal

10

70

0.00100000s

Prim

10

61

0.00100000s

Exemplu
Algoritmu
l

Nr.iteratii

Timpul

Kruskal

23

299

0.00100000s

Prim

23

145

0.00200000s
15

Prim
Prim/timp

Prim/iteratii

160

145

140
120
100
80

61

60
40
20
0

Kruskal
350

299

300
250
200

Kruskal/Iteratii

Kruskal/timp

150
100

70

50
0

16

Concluzia final:
Efectuind lucrarea data ne-am facut cunoscuti cu diferiti algoritmi greedy.Totodata am
analizat eficienta lor pentru diferite cazuri ale problemei. Implementind si analizind
algoritmul Kruskal si Prim am concluzionat ca acesti algoritmi sunt rapizi si deci sunt
algoritmi eficienti si foarte buni pentru rezolvarea problemei de construire a arborelui
de cost minim.Observ ca dindui numarul de virufuri 6 in cazul algoritmului Kruskal
numarul de iteratii este mai mare si timpul este constan.Dindui numarul de virfuri 9 la
Kruskal numarul de iteratii creste mult mai mult decit la Prim in schim din punct de
vedre a timpului Kruskal ramine constant iar Prim incepe a creste.Astfel reiese ca daca
vom da un numar mare de virfuri la Kruskal va creste in iterati dar timpul va fi favorabil
in schimb algoritmul Prim ve avea un numar mic de iteratii da timpul de executie va
creste semnificativ.

17

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