Sunteți pe pagina 1din 9

Metoda Greedy

Consideraii teoretice
Explicarea numelui
n limba englez cuvntul greedy nseamn lacom. Algoritmii de tip greedy sunt algoritmi lacomi.
Ei vor s construiasc ntr-un mod ct mai rapid soluia problemei, fr a sta mult pe gnduri.
Algoritmii de tip greedy se caracterizeaz prin luarea unor decizii rapide care duc la gsirea
unei soluii a problemei. Nu ntotdeauna asemenea decizii rapide duc la o soluie optim, dar vom
vedea c exist anumite tipuri de probleme unde se pot obine soluii optime sau foarte apropiate de
optim.

Aplicabilitatea algoritmilor de tip Greedy


Algoritmii de tip Greedy se aplic la acele probleme unde datele de intrare sunt organizate sub
forma unei mulimi A i se cere gsirea unei submulimi BA care s ndeplineasc anumite condiii
astfel nct s fie acceptat ca soluie posibil.
n general pot s existe mai multe submulimi BA care s reprezinte soluii posibile ale
problemei. Dintre toate aceste submulimi B se pot selecta, conform unui anumit criteriu, anumite
submulimi B* care reprezint soluii optime ale problemei. Scopul este de a gsi, dac este posibil,
una din mulimile B*. Dac acest lucru nu este posibil, atunci scopul este gsirea unei mulimi B
care s fie ct mai aproape de mulimile B*, conform criteriului de optimalitate impus.

Modul de lucru al algoritmilor de tip Greedy


Construirea mulimii B se face printr-un ir de decizii. Iniial se pornete cu mulimea vid (B = ).
Fiecare decizie const n alegerea unui element din mulimea A, analiza lui i eventual introducerea
lui n mulimea B. n funcie de modul n care se iau aceste decizii, mulimea B se va apropia mai
mult sau mai puin de soluia optim B*. n cazul ideal vom avea B = B*.
Algoritmii de tip greedy nu urmresc s determine toate soluiile posibile i s aleag dintre
ele, conform criteriului de optimalitate impus, soluiile optime. Dup cum spune i numele,
algoritmii de tip greedy sunt caracterizai prin lcomie i nu au rbdarea s investigheze toate
variantele posibile de alegere a soluiei. Ei ncep construirea unei soluii pornind de la mulimea
vid, apoi lucreaz n pai, ntr-un mod ct se poate de hotrt: la fiecare pas se ia cte o decizie i
se extinde soluia cu cte un element.
La fiecare pas se analizeaz cte un element din mulimea A i se decide dac s fie sau nu
inclus n mulimea B care se construiete. Astfel se progreseaz de la cu un sir de mulimi
intermediare (, B0, B1, B2, ...), pn cnd se obine o soluie final B.

Implementare
Ca i schem general de lucru, exist dou variante de implementare a algoritmilor de tip Greedy.
Prima variant folosete dou funcii caracteristice: alege i posibil. alege este o funcie care

are rolul de a selecta urmtorul element din mulimea A care s fie prelucrat. Funcia posibil verific
dac un element poate fi adugat soluiei intermediare Bi astfel nct noua soluie Bi+1 care s-ar
obine s fie o soluie valid. Prezentm n continuare pseudocodul pentru aceast prim variant
greedy. Se consider c numrul de elemente al mulimii A este n.
B = multimea vida
for (i=0; i<n; i++)
{
x = alege(A)
if (posibil(B,x))
* adauga elementul x la multimea B
}

Dificultatea la aceast prim variant const n scrierea funciei alege. Dac funcia alege este bine
conceput, atunci putem fi siguri c soluia B gsit este o soluie optim. Dac funcia alege nu este
foarte bine conceput, atunci soluia B gsit va fi doar o soluie posibil i nu va fi optim. Ea se
poate apropia ns mai mult sau mai puin de soluia optim B*, n funcie de criteriul de selecie
implementat.
A doua variant de implementare difer de prima prin faptul c face o etap iniial de
prelucrare a mulimii A. Practic se face o sortare a elementelor mulimii A, conform unui anumit
criteriu. Dup sortare, elementele vor fi prelucrate direct n ordirea rezultat. Prezentm n
continuare pseudocodul pentru aceast a doua variant greedy.
B = multimea vida
prelucreaza(A)
for (i=0; i<n; i++)
{
x = A[i]
if (posibil(B,x))
* adauga elementul x la multimea B
}

La a doua variant, dificultatea funciei alege nu a disprut, ci s-a transferat funciei prelucreaza.
Dac prelucrarea mulimii A este bine fcut, atunci se va ajunge n mod sigur la o soluie optim.
Altfel se va obine doar o soluie posibil, mai mult sau mai puin apropiat de optim.

Exemple
Problema comis-voiajorului
Enun Se condider n orae. Se cunosc distanele dintre oricare dou orae. Un comis-voiajor
trebuie s treac prin toate cele n orae. Se cere s se determine un drum care pornete dintr-un ora,
trece exact o dat prin fiecare din celelalte orae i apoi revine la primul ora, astfel nct lungimea
drumului s fie minim.
Rezolvare Pentru gsirea unei soluii optime la aceast problem este nevoie de algoritmi cu timp
de rulare foarte mare (de ordin exponenial O(2n)). n situaiile practice asemenea algoritmi cu timp
foarte mare de rulare nu sunt acceptabili. Ca urmare se face un compromis i se accept algoritmi
care nu gsesc soluia optim ci doar o soluie aproape de optim, dar au n schimb un timp de rulare
mic. Propunem n continuare o soluie greedy la aceast problem. Ideea este urmtoarea. Se
pornete dintr-un ora oarecare. Se caut drumul cel mai scurt care pleac din oraul respectiv ctre
orae nevizitate nc. Se parcurge acel drum i se ajunge ntr-un alt ora. Aici din nou se caut cel
mai scurt drum ctre oraele nevizitate nc. Se parcurge i acest drum, ajungndu-se ntr-un nou

ora. Repetnd aceti pai se parcurg toate oraele. La final se parcurge drumul care duce napoi
spre primul ora.
S considerm exemplul din figura 1. Avem 4 orae cu distanele reprezentate n figur.

Figura 1: Reea de orae pentru problema comis-voiajorului

Pornim vizitarea oraelor din oraul 0. De aici alegem drumul cel mai scurt ctre oraele nevizitate,
i anume (0,2) de lungime 2. Ajuni n oraul 2, alegem din nou drumul cel mai scurt spre oraele
nevizitate, i anume (2,3) de lungime 1. Din oraul 3 mai avem doar un singur ora nevizitat, 1, aa
c alegem drumul spre el (3,1) de lungime 1. n acest moment am parcurs toate oraele i ne
rentoarcem n oraul 0 pe drumul (1,0) de lungime 4. Drumul rezultat este 0, 2, 3, 1, 0, iar distana
total de parcurs este 2 + 1 + 1 + 4 = 8.
Implementare Distanele ntre orae le memorm ntr-un tablou bidimensional D. Distana ntre
oraele (i,j) va fi memorat n elementul di,j al matricii. n termeni Greedy, mulimea iniial A este
mulimea tuturor perechilor de orae. Pentru reeaua de orae din figura 2 mulimea A conine
elementele {(0,1), (0,2), (0,3), (1,2), (1,3), (2,3)}. Mulimea B care trebuie gsit va conine o parte
din aceste perechi de orae, i anume acele perechi care nlnuite s formeze un drum ce trece prin
toate oraele. Dac avem un numr de n orae, atunci mulimea B va conine n perechi de orae.
n implementare nu vom lucra cu mulimea A sub aceast form explicit de perechi de
orae, ci vom folosi matricea distanelor D. De asemenea drumul comis-voiajorului nu l vom pstra
sub form de perechi de orae, ci sub forma unui sir al oraelor.
Pentru a memora drumul parcurs de comis-voiajor, folosim un tablou unidimensional drum.
n acest tablou vom memora indicii oraelor parcuse, n ordinea parcurgerii.
Pentru a ti care orae au fost parcurse, facem o marcare logic a oraelor folosind un tablou
unidimensional vizitat. Elementele din acest tablou care au valoarea 1 reprezint orae vizitate.
Cod surs n continuare este prezentat codul surs n limbajul C care implementeaz algoritmul
descris mai sus.
#include <stdio.h>
/* Numarul maxim de orase. */
#define N_MAX 30
/* Constanta care se foloseste ca valoare
de initializare la cautarea minimului. */
#define MINIM 10000

/* Numarul de orase. */
int n;
/* Matricea distantelor dintre orase. */
int d[N_MAX][N_MAX];
/* Drumul comis voiajorului. Contine
indicii oraselor in ordinea in care
sunt ele parcurse. */
int drum[N_MAX];
/* Vector care memoreaza care orase au
fost vizitate. vizitat[k] va fi 1 daca
orasul k a fost vizitat, 0 altfel. */
int vizitat[N_MAX];
/* Functie care alege urmatorul element care
sa fie prelucrat din multimea oraselor.
Primeste ca parametru ultimul oras care
a fost vizitat, si returneaza urmatorul
oras care sa fie vizitat precum si lungimea
drumului catre acesta. */
void alege(int ultimul, int *min, int *j_min)
{
int j;

/* Cautam drumul minim de la ultimul


oras pana la orasele neparcurse inca. */
*min = MINIM;
*j_min = -1;
for (j=0; j<n; j++)
if (!vizitat[j])
{
if (d[ultimul][j] < *min)
{
*min = d[ultimul][j];
*j_min = j;
}
}

int main(void)
{
FILE *fin;
int i, j;
int count, cost, min, j_min;
/* Deschidem fisierul pentru citire in mod text. */
fin = fopen("comis.in", "rt");
if (!fin)
{
printf("Eroare: nu pot deschide fisierul.\n");
return -1;
}
/* Citim datele din fisier. */
fscanf(fin, "%d", &n);
for (i=0; i<n; i++)
for (j=0; j<n; j++)
fscanf(fin, "%d", &(d[i][j]));
/* Afisam pe ecran datele preluate din fisier. */
printf("Avem %d orase.\n", n);
printf("Distantele dintre orase sunt:\n");

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


{
for (j=0; j<n; j++)
printf("%d ", d[i][j]);
printf("\n");
}
printf("\n");
/* Initial nici un oras nu este vizitat. */
for (i=0; i<n; i++)
vizitat[i] = 0;
/* Primul oras vizitat este cel cu numarul "0".
Costul total este zero deocamdata. */
drum[0] = 0;
vizitat[0] = 1;
count = 1;
cost = 0;
/* Parcurgem restul de n-1 orase. */
for (i=0; i<n-1; i++)
{
/* Alegem urmatorul oras care sa fie vizitat. */
alege(drum[count-1], &min, &j_min);
/* Parcurgem drumul minim gasit si vizitam
un nou oras. */
printf("Am ales drumul (%d, %d) de cost %d.\n",
drum[count-1], j_min, min);
drum[count] = j_min;
vizitat[j_min] = 1;
count++;
cost += min;

/* Parcurgem drumul de la ultimul oras vizitat


catre primul oras si actualizam costul
total. */
cost += d[drum[n-1]][0];
/* Afisam drumul parcurs. */
printf("\nDrumul are costul %d si este:\n", cost);
for (i=0; i<n; i++)
printf("%d ", drum[i]);
printf("0\n");
return 0;
}

Fiierul cu date de intrare pentru reeaua de orae din figura 2 este urmtorul:
4
0
4
2
7

4
0
2
1

2
2
0
1

7
1
1
0

mbuntiri Algoritmul greedy prezentat se poate mbunti pentru a furniza soluii mai aproape
de soluia optim. O variant de mbuntire este s nu se porneasc doar din primul ora la
parcurgerea drumului. Se poate relua calculul avnd ca punct de pornire fiecare ora pe rnd i se
poate memora minimul global astfel obinut.

Probleme propuse
Conectarea oraelor cu cost minim
Enun Se consider n orae. Pentru diferite perechi de orae (i, j), 0<i<n, 0<j<n se cunoate costul
conectrii lor directe ci,j. Nu toate perechile de orae pot fi conectate; pentru perechile care nu pot fi
conectate nu se precizeaz costul. Se cere s se construiasc o reea prin care oricare dou orae s
fie conectate ntre ele direct sau indirect i costul total al conectrii s fie minim.
Rezolvare Se poate arta c reeaua de conectare cerut este un arbore. Problema mai este
cunoscut i ca problema determinrii arborelui parial de cost minim ntr-un graf. Pentru aceast
problem exist un algoritm greedy de rezolvare numit algoritmul lui Prim. n literatura de
specialitate exist argumentarea matematic a faptului c acest algoritm gsete ntotdeauna soluia
optim de conectare a oraelor.
Se construiete arborele parial minim n manier greedy, adugnd cte un nod la fiecare
pas. La nceput de tot arborele parial este vid, nu conine nici un nod. Primul pas const n
adugarea unui nod arbitrar n arbore. Pe urm, la fiecare pas se caut muchia de cost minim care
pornete dintr-un nod deja adugat la arbore i ajunge ntr-un nod care nu este n arbore. Se adaug
n arbore nodul n care sfrete muchia gsit.
S considerm spre exemplu o reea de 7 orae numerotate de la 0 la 6. Costurile de
conectare a oraelor sunt redate n figura 2.

Figura 2: Costuri de conectare a oraelor pentru problema


conectrii oraelor cu cost minim

Arborele minim este redat cu linii ngroate. El a fost construit pas cu pas, conform procedeului
descris mai sus. Iniial arborele a fost vid. La primul pas s-a adugat un nod arbitrar, i anume nodul
0.
Pe urm s-a ales muchia de cost minim care pleac din nodul 0 ctre celelalte noduri.
Muchia de cost minim a fost (0,2) de cost 10. Nodul 2 a fost adugat n arbore.
La urmtorul pas s-a ales muchia de cost minim care pleac din nodurile 0 sau 2 ctre
celelalte noduri. Muchia aleas a fost (2,1) de cost 9. Nodul 1 a fost adugat n arbore.
La urmtorul pas s-a ales muchia de cost minim care pleac din nodurile 0, 2 sau 1 ctre
nodurile nc neintroduse n arbore. Muchia aleas a fost (1,5) de cost 3. Nodul 5 a fost adugat n
arbore.
Urmtoarea muchie aleas a fost (5,4) de cost 2. Nodul 4 a fost adugat n arbore. Apoi a
fost aleas muchia (4,6) de cost 2 i nodul 6 a fost adugat i el n arbore.
Pe urm a fost aleas muchia (1,3) de cost 4 i nodul 3 a fost introdus n arbore. n acest
moment algoritmul s-a ncheiat deoarece toate oraele au fost conectate la reea. Costul total al
conectrii a fost 10 + 9 + 3 + 2 + 2 + 4 = 30.
Implementare Matricea costurilor, C, se reine ntr-un tablou bidimensional. Pentru perechile de

orae ntre care nu se poate face legtur se va trece n matricea costurilor valoarea 0. n termeni
Greedy, mulimea noastr iniial de elemente A este muimea tuturor perechilor de orae ntre care
se poate stabili legtur direct. Adic A={(i, j) | c i,j>0}. Pentru graful din figura 1, mulimea A va
conine elementele {(0,1), (0,2), (1,2), (1,3), (1,5), (2,3), (4,5), (4,6), (5,6)}.
Submulimea B pe care o cutm va conine o parte din perechile aflate n mulimea A. Se
poate demonstra c soluia optim B* conine n-1 perechi atunci cnd numrul de orae este n
(presupunem c graful este conex, adic se poate construi o reea care s conecteze toate oraele).
Pentru construirea mulimii B, vom selecta oraele rnd pe rnd pentru a le aduga la reea.
Vom spune c un ora este selectat atunci cnd el a fost conectat la reeaua de orae printr-o muchie
care face parte din mulimea B.
n implementare nu vom lucra cu mulimea A sub forma explicit de perechi, ci vom folosi
matricea costurilor C. Vom eticheta liniile i coloanele matricei costurilor dup cum urmeaz.
Atunci cnd un ora oi este selectat, linia i din matrice se marcheaz, iar coloana i din matrice se
terge.
Pentru a alege urmtorul element din mulimea A care s fie prelucrat, cutm cel mai mic
cost din matricea costurilor din liniile marcate i coloanele care nu sunt terse. S zicem c cel mai
mic cost a fost gsit ca fiind elementul ci_min,j_min. Atunci urmtorul element din mulimea A care va fi
prelucrat este perechea (i_min,j_min).
tergerea coloanelor din matricea costurilor nu va nsemna o tergere fizic, ci doar una
logic. Vom folosi doi vectori prin care vom memora care linii sunt marcate i care coloane sunt
terse.
O schema de cod surs pentru rezolvarea acestei probleme arat astfel:
...
/* Numarul maxim de noduri din graf. */
#define N_MAX 30
/* Numarul de orase. */
int n;
/* Matricea costurilor de conectare a oraselor. */
int c[N_MAX][N_MAX];
/* Vector care indica liniile marcate din matricea
costurilor. marcat[k] va fi 1 pentru liniile
marcate si 0 pentru liniile nemarcate. */
int marcat[N_MAX];
/* Vector care indica coloanele sterse din matricea
costurilor. sters[k] va fi 1 pentru coloanele
sterse si 0 pentru coloanele nesterse. */
int sters[N_MAX];
/* Functie care alege urmatorul element care sa
fie prelucrat din multimea A, adica o pereche
de orase intre care sa se construiasca drum.
Se parcurg liniile marcate si coloanele
nesterse din matricea costurilor si se
alege costul minim.
Se returneaza costul minim gasit, si linia si
coloana unde apare el. */
void alege(int* min, int *i_min, int* j_min)
{
...
}
int main(void)

...
/* Aici memoram muchiile alese pentru a face parte din arbore.
O muchie este memorata de perechea (arbore[i][0], arbore[i][1]). */
int arbore[N_MAX][2];
/* Numarul de muchii introduse in arbore. */
int count = 0;
...
/* Citim din fisier numarul de noduri din graf
si matricea costurilor. */
...
/* Initial nici un nod nu este nici marcat nici
sters. Pentru asta initializam toate elementele
vectorilor marcat si sters cu zero. */
...
/* Pornim de la nodul "0", motiv pentru care
marcam linia 0 si stergem coloana 0. */
marcat[0] = 1;
sters[0] = 1;
...
/* Cat timp mai avem noduri neparcurse. */
while (!gata)
{
/* Alege urmatoarea pereche de noduri care sa fie
prelucrata. Nodul i_min va fi un nod deja parcurs,
iar nodul j_min va fi un nod inca neparcurs. */
alege(&min, &i_min, &j_min);
...
/* Marcam linia noului nod si stergem
coloana lui. */
marcat[j_min] = 1;
sters[j_min] = 1;
/* Adaugam muchia la arbore. */
arbore[count][0] = i_min;
arbore[count][1] = j_min;
count++;
}

...

/* Afisam arborele partial minim pe care l-am gasit. */


...
}

return 0;

Fisierul cu date de intrare pentru graful din figura 2 este urmtorul:


7
0 20 10 0 0 0 0
20 0 9 4 0 3 0
10 9 0 10 0 0 0

0
0
0
0

4
0
3
0

10 0 0 0 0
0 0 0 2 2
0 0 2 0 3
0 0 2 3 0