Sunteți pe pagina 1din 14

Ministerul Educaiei a Republicii Moldova

Universitatea Tehnic a Moldovei


Facultatea Calculatoare, Informatic i Microelectronic

RAPORT
Lucrarea de laborator nr. 3
Tema:

Algoritmii greedy

La disciplina: Analiza i proiectarea algoritmilor

A efectuat:
student a gr. TI-151 Poseletchi Cristian
A verificat:
lect. sup. Bagrin Veronica
Chiinu 2016
Scopul lucrrii:

1. Studierea tehnicii greedy.


2. Analiza i implementarea algoritmilor greedy.

Sarcina de baz:
1. De studiat tehnica greedy de proiectare a algoritmilor.
2. De implementat ntr-un limbaj de programare algoritmii Kruskal, Prim i Dijkstra.
3. De fcut analiza empiric a algoritmilor Kruskal i Prim.

Consideraii teoretice

Tehnica greedy
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.

1. Arbori pariali de cost minim


Fie G = <V, M> un graf neorientat conex, unde V este mulimea vrfurilor i M este
mulimea muchiilor. Fiecare muchie are un cost nenegativ (sau o lungime nenegativ).
Problema este s gsim o submulime A M, astfel nct toate vrfurile din V s rmn
conectate atunci cnd sunt folosite doar muchii din A, iar suma lungimilor muchiilor din A s
fie cat mai mic. Cutm deci o submulime A de cost total minim. Aceast problem se mai
numete i problema conectrii oraelor cu cost minim, avnd numeroase aplicaii.

Graful parial <V, A> este un arbore i este numit arborele parial de cost minim al
grafului G (minimal spanning tree). Un graf poate avea mai muli arbori pariali de cost minim.
Vom prezenta doi algoritmi greedy care determin arborele parial de cost minim al unui graf.
n terminologia metodei greedy, vom spune c o mulime de muchii este o soluie, dac
constituie un arbore parial al grafului G, i este fezabila, dac nu conine cicluri. O mulime
fezabil de muchii este promitoare, dac poate fi completat pentru a forma soluia optim.
O muchie atinge o mulime dat de vrfuri, dac exact un capt al muchiei este n mulime.
Mulimea iniiala a candidailor este M. Cei doi algoritmi greedy aleg muchiile una cate una
intr-o anumita ordine, aceast ordine fiind specific fiecrui algoritm.

1.1. 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
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.

1.2. 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}

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.

2. Cele mai scurte drumuri care pleac din acelai punct


Fie G = <V, M> un graf orientat, unde V este mulimea vrfurilor i M este mulimea muchiilor.
Fiecare muchie are o lungime nenegativa. Unul din vrfuri este ales c vrf surs. Problema este de a
determina lungimea celui mai scurt drum de la surs ctre fiecare vrf din graf.
Se va folosi un algoritm greedy, datorat lui Dijkstra (1959). Notm cu C mulimea vrfurilor
disponibile (candidaii) i cu S mulimea vrfurilor deja selectate. n fiecare moment, S conine acele vrfuri
a cror distan minim de la surs este deja cunoscut, n timp ce mulimea C conine toate celelalte vrfuri.
La nceput, S conine doar vrful surs, iar n final S conine toate vrfurile grafului. La fiecare pas, adugam
n S acel vrf din C a crui distan de la surs este cea mai mic.
Se spune, c un drum de la surs ctre un alt vrf este special, dac toate vrfurile intermediare de-a
lungul drumului aparin lui S. Algoritmul lui Dijkstra lucreaz n felul urmtor. La fiecare pas al
algoritmului, un tablou D conine lungimea celui mai scurt drum special ctre fiecare vrf al grafului. Dup
ce se adaug un nou vrf v la S, cel mai scurt drum special ctre v va fi, de asemenea, cel mai scurt dintre
toate drumurile ctre v. Cnd algoritmul se termin, toate vrfurile din graf sunt n S, deci toate drumurile de
la surs ctre celelalte vrfuri sunt speciale i valorile din D reprezint soluia problemei.
Presupunem c vrfurile sunt numerotate, V = {1, 2, ..., n}, vrful 1 fiind sursa, i c matricea L d
lungimea fiecrei muchii, cu L[i, j] = , dac muchia (i, j) nu exist. Soluia se va construi n tabloul
D[2 .. n]. Algoritmul este:

function Dijkstra(L[1 .. n, 1 .. n])


{iniializare}
C {2, 3, ..., n}
{S = V \C exist doar implicit}
for i 2 to n do D[i] L[1, i]
{bucla greedy}
repeat n2 times
v vrful din C care minimizeaz D[v]
C C \ {v}
{si, implicit, S S {v}}
for fiecare w C do
D[w] min(D[w], D[v]+L[v, w])
return D

Proprietatea 3. n algoritmul lui Dijkstra, dac un vrf i


a) este n S, atunci D[i] d lungimea celui mai scurt drum de la surs ctre i;
b) nu este n S, atunci D[i] d lungimea celui mai scurt drum special de la surs ctre i.

La terminarea algoritmului, toate vrfurile grafului, cu excepia unuia, sunt n S. Din proprietatea
precedenta, rezulta c algoritmul lui Dijkstra funcioneaz corect.
Dac dorim s aflm nu numai lungimea celor mai scurte drumuri, dar i pe unde trec ele, este
suficient de adugat un tablou P[2 .. n], unde P[v] conine numrul nodului care l precede pe v n cel mai
scurt drum. Pentru a gsi drumul complet, nu avem dect s urmrim, n tabloul P, vrfurile prin care trece
acest drum, de la destinaie la surs. Modificrile n algoritm sunt simple:
iniializeaz P[i] cu 1, pentru 2 i n;
coninutul buclei for cea mai interioar se nlocuiete cu
if D[w] > D[v]L[v, w] then D[w] D[v]L[v, w]
P[w] v
bucla repeat se execut de n1 ori.
Presupunem c aplicm algoritmul Dijkstra asupra unui graf cu n vrfuri i m muchii. Iniializarea
necesita un timp n O(n). Alegerea lui v din bucla repeat presupune parcurgerea tuturor vrfurilor coninute
n C la iteraia respectiv, deci a n1, n2, ..., 2 vrfuri, ceea ce necesit n total un timp n O(n2). Bucla for
interioar efectueaz n2, n3, ..., 1 iteraii, totalul fiind tot n O(n2). Rezulta c algoritmul Dijkstra necesita
un timp n O(n2). Timpul se poate mbunti, dac se vor folosi n locul matricei de adiacen liste de
adiacen.
Este uor de observat c, ntr-un graf G neorientat conex, muchiile celor mai scurte drumuri de la un
vrf i la celelalte vrfuri formeaz un arbore parial al celor mai scurte drumuri pentru G. Desigur, acest
arbore depinde de alegerea rdcinii i i el difer, n general, de arborele parial de cost minim al lui G.

Problema gsirii celor mai scurte drumuri care pleac din acelai punct se poate pune i n cazul unui
graf neorientat.

Codul surs
Algoritmul Dijkstra
#include <iostream>
#include <conio.h>
using namespace std;
void copyright()
{
cout << "A efectuat: Antoci Anatoli TI-102 (UTM/FCIM) |\n";
cout << "---------------------------------------------\n\n";
}
struct Arc
{
int x, y, c;
Arc()
{
x = 0;
y = 0;
}
Arc(int x, int y)
{
this->x = x;
this->y = y;
}
};
struct Ml
{
int val;
int mark;
};
int na, nv, *s, **el;
Arc *arc;
Ml *d;
void Init()
{
s = new int[nv];

d = new Ml[nv];
for(int i = 0; i < nv; i++)
{
d[i].val = el[1][i+1];
d[i].mark = 0;
s[i] = -1;
}
d[0].mark = 1;
s[0] = 0;
}
void djkstra()
{
int i, j, k, min, pmin, add;
Init();
for (i = 0; i < nv; i++)
{
min=999;
//Cautarea minimului din d
for (j = 0; j < nv; j++)
if (d[j].val < min && d[j].mark == 0)
{
min = d[j].val;
pmin = j;
}
add = min;
s[i] = pmin;
d[pmin].mark = 1;
//Cautarea minimizarea drumurilor
for (j = 0; j < nv; j++)
if ((el[pmin+1][j+1] + add) < d[j].val)
d[j].val = el[pmin+1][j+1]+add;
}
cout << "\n\n\nSolutia este multimea D = [";
for (i = 1; i <nv; i++)
cout << " " << d[i].val;
cout << " ]";
}
void MATL() // Matricea L
{
int i, j;
el = new int*[nv+1];

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


{
el[i] = new int[nv+1];
for (j = 0; j <= nv; j++)
el[i][j] = 999;
}
for (i = 0; i < na; i++)
el[arc[i].x][arc[i].y] = arc[i].c;
}
void read() // Citeste datele de pe tastatura
{
cout << "Introduceti nr. de virfuri: ";
cin >> nv;
cout << "Introduceti nr. de arce: ";
cin >> na;
cout << "Introduceti nodurile si costurile intre ele:\n";
arc = new Arc[na];
for(int i=0; i<na; i++)
cin >> arc[i].x >> arc[i].y >> arc[i].c;
}
main()
{
copyright();
read();
MATL();
djkstra();
getch();
delete [] arc;
delete [] d;
delete [] s;
delete [] el;
}
Algoritmul Kruskal
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<iostream>
#define pb push_back
using namespace std;

const int maxn = 500;


int GR[maxn],X[maxn],Y[maxn],C[maxn], N,M,ANS,IND[maxn];
vector<int> VANS;
int grupa(int i)
{
if (GR[i] == i) return i;
GR[i] = grupa(GR[i]);
return GR[i];
}
void reuniune(int i,int j)
{
GR[grupa(i)] = grupa(j);
}
bool cmpf(int i,int j)
{
return(C[i] < C[j]);
}
void copyright()
{
cout << "A efectuat: Antoci Anatoli TI-102 (UTM/FCIM) |\n";
cout << "---------------------------------------------\n\n";
}
main()
{
copyright();
cout << "Introduceti nr. virfurilor: ";
cin >> N;
cout << "Introduceti nr. arcelor: ";
cin >> M;
cout << "\nIntroduceti arcul si costul:\n";
for(int i=1; i<=M; ++i)
{
cin >> X[i] >> Y[i] >> C[i];
IND[i] = i;
}
for(int i = 1;i <= N; ++i) GR[i] = i;
for(int i = 1;i <= N; ++i) GR[i] = i;
sort(IND + 1,IND + M + 1,cmpf);
for(int i = 1;i <= M; ++i)
{

if (grupa(X[IND[i]]) != grupa(Y[IND[i]]))
{
ANS += C[IND[i]];reuniune(X[IND[i]],Y[IND[i]]);
VANS.pb(IND[i]);
}
}
cout << "\n---------------------\n";
cout << "Costul minim: " << ANS << endl;
cout << "Arcele:\n";
for(int i = 0;i < N - 1; ++i)
cout << X[VANS[i]] << " " << Y[VANS[i]] << "\n";
}
Algoritmul Prim
#include <iostream>
using namespace std;
int nr_vf, nr_eg;
typedef struct {
int beg, end;
double cost;
} muchie;
muchie * M;
int * APM, *za;
void init() {
int i;
cout << "Introduceti nr. virfurilor: ";
cin >> nr_vf;
cout << "Introduceti nr. arcelor: ";
cin >> nr_eg;
APM = new int[nr_vf - 1];
M = new muchie [nr_eg];
cout << "\nIntroduceti arcul si costul:\n";
for(int i=0; i<nr_eg; ++i)
{
cin >> M[i].beg >> M[i].end >> M[i].cost;
M[i].beg--; M[i].end--;
}
za = new int[nr_vf];
for (i = 0; i < nr_vf; i++)
za[i] = 0;
}

int cut_min() {
int rm; double q = 1.E15;
for (int i = 0; i < nr_eg; i++)
if(za[M[i].beg] ^ za[M[i].end])
if(M[i].cost < q) {
rm = i;
q = M[i].cost;
}
return rm;
}
void apm_Prim(int start) {
za[start] = 1;
for (int i = 0; i < nr_vf - 1; i++) {
int rm = cut_min();
APM[i] = rm;
if(za[M[rm].beg])
za[M[rm].end] = 1;
else za[M[rm].beg] = 1;
}
}

void copyright()
{
cout << "A efectuat: Antoci Anatoli TI-102 (UTM/FCIM) |\n";
cout << "---------------------------------------------\n\n";
}
main()
{
copyright();
double cost_apm = 0;
int i;
init();
cout << "\nIntroduceti varful de plecare: "; cin >> i;
apm_Prim(i - 1);
cout << "\n-------------------------------\n Rezultatele:\n";
for (i = 0; i < nr_vf - 1; i++) {
int rm = APM[i];
cout << (M[rm].beg + 1) << " - " << (M[rm].end + 1) << "\tCost: " << M[rm].cost << endl;
cost_apm += M[rm].cost;
}

cout << "\nCostul minim = " << cost_apm << '\n';


delete [] APM;
delete [] M; delete [] za;
}

Screenshoturile

Fig.1 Algoritmul Dijkstra

Fig.2 Algoritmul Kruskal

Fig.3 Algoritmul Prim

Concluzie
In urma efectuarii lucrarii de laborator 3 am facut cunostinta cu algoritmii greedy. Mai concret cu
algoritmii Kruskal, Prim si Dijkstra. Algoritmul Kruskal si Prim ne ofera posibilitatea sa construim
arborele de cost minim. Algoritmul Dijkstra ne ofera posibilitatea sa determinam drumurile minimale pina
la fiecare virf in parte.
Implementind si analizind algoritmul Kruskal si Prim, am ajuns la concluzia ca acesti algoritmi sunt
foarte rapizi si deci sunt algoritmi eficienti si foarte buni, p-u rezolvarea problemei de construire a
arborelui de cost minim. Algoritmul Dijkstra de asemenea este un algoritm foarte eficient.