Documente Academic
Documente Profesional
Documente Cultură
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.
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.
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;
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");
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.
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++;
}
...
return 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