You are on page 1of 17

1.

Metoda Greedy................................................................................................ 2
1.1 Consideraii teoretice................................................................................ 2
Explicarea numelui.......................................................................................... 2
Aplicabilitatea algoritmilor de tip Greedy........................................................2
Modul de lucru al algoritmilor de tip Greedy...................................................2
1.2 Implementare............................................................................................ 2
1.3 Exemple.................................................................................................... 3
Problema comis-voiajorului.............................................................................. 3
2. Metoda Divide and Conquer............................................................................ 7
2.1 Consideraii teoretice................................................................................ 7
Aplicabilitatea algoritmilor de tip Divide and Conquer....................................7
Modul de lucru al algoritmilor de tip Divide and Conquer................................7
2.2 Implementare............................................................................................ 7
2.3 Exemple.................................................................................................... 8
Turnurile din Hanoi.......................................................................................... 8
Determinarea minimului i maximului dintr-un ir de numere.........................9
3 Probleme propuse.......................................................................................... 12
3.1 Conectarea oraelor cu cost minim.........................................................12
3.2 Cele mai apropiate puncte de pe o dreapt............................................15

1. Metoda Greedy

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

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

1.3 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(2 n)). 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 d i,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 1 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.

2. Metoda Divide and Conquer

2.1 Consideraii teoretice


Aplicabilitatea algoritmilor de tip Divide and Conquer
Metoda de rezolvare Divide and Conquer se poate aplica la problemele care se
pot descompune n subprobleme de aceeai natur cu problema principal, dar
de dimensiuni mai mici.
La unele probleme aceast posibilitate de descompunere n subprobleme de
acelai tip este evident. Vom vedea ns c sunt i probleme unde o asemenea
descompunere nu apare de la prima vedere.

Modul de lucru al algoritmilor de tip Divide and Conquer


Se poate pune ntrebarea cum rezolvm subproblemele?. Rspunsul este: n
acelai mod n care am rezolvat problema principal. Metoda Divide and
Conquer se preteaz foarte bine la implementri recursive. Din moment ce tim
s mprim problema principal n subprobleme, ce ne oprete s facem acelai
lucru cu fiecare subproblem n parte? Putem mpari fiecare subproblem n
subsubprobleme, pe care la rndul lor le imprim n subsubsubprobleme,
.a.m.d.

Cnd ne oprim cu aceste mpriri recursive? Atunci cnd ajungem la


subprobleme de dimensiuni att de mici nct rezolvarea lor este trivial.

2.2 Implementare
n general implementarea metodei Divide and Conquer se face prin funcii
recursive. De regul vom avea o singur funcie care primete ca parametri
informaiile necesare pentru a rezolva o subproblem i returneaz rezultatele
pentru subproblema respectiv.
Funcia va determina dac subproblema este una trivial, caz n care va calcula
direct soluia pentru ea. Dac subproblema nu este una trivial, atunci funcia va
mpri subproblema n subsubprobleme i se va auto-apela n mod recursiv
pentru fiecare din ele. Pe urm va combina rezultate obinute pentru
subsubprobleme i va gsi soluia pentru subproblem.
Prezentm schema general de lucru a unei asemena funcii, n pseudocod:
function divide(* parametri care definesc o subproblema)
{
if (* subproblema este una triviala)
{
* rezolva subproblema in mod direct
* returneaza rezultatele
}
else
{
* imparte subproblema in subsubprobleme
* pentru fiecare subsubproblema
* apeleaza divide(subsubproblema)

* combina rezultatele subsubproblemelor


* returneaza rezultatele pentru subproblema

Pentru a rezolva problema principal, tot ce trebuie fcut este s se apeleze


funcia recursiv cu acei parametri care definesc problema principal.

2.3 Exemple
Turnurile din Hanoi
Enun Fie trei tije notate cu a, b i c. Pe tija a se afl n discuri de dimensiuni
diferite, aezate n ordinea descresctoare a diametrelor, astfel nct discul cu
diametrul cel mai mare se afl cel mai jos, iar discul cu diametrul cel mai mic se
afl n vrful stivei. S se gseasc o modalitate de a muta toate cele n discuri
de pe tija a pe tija b, folosind tija intermediar c, atfel nct n final discurile s fie
ordonate tot descresctor. n timpul operaiilor care se fac, este interzis
plasarea unui disc mai mare peste un disc mai mic.
Rezolvare Problema noastr iniial este s mutm n discuri de pe tija a pe tija
b, folosind tija intermediar c. O putem codifica n felul urmtor: (n,a,b,c). Dac
am gsi o modalitate de a muta n-1 discuri de pe tija a pe tija intermediar c,
atunci am putea s mutm discul cel mai mare de pe tija a pe tija b. Pe urm ar

trebui s aducem cele n-1 discuri de pe tija c pe tija b i problema ar fi rezolvat.


Pentru a muta n-1 discuri de pe tija a pe tija c, putem folosi ca tij intermediar
tija b. La fel, pentru a muta napoi cele n-1 discuri de pe tija c pe tija b, putem
folosi ca tij intermediar tija a.
Putem reformula cele zise mai sus n felul urmtor: problema (n,a,b,c) se rezum
la problema (n-1,a,c,b), urmat de mutarea discului de diametru maxim de pe a
pe b, urmat de problema (n-1,c,b,a).
Implementare Implementarea se face printr-o funcie recursiv. Funcia
primete patru parametri: numrul de discuri de pe tija iniial, tija initial, tija
final i tija intermediar. Se descompune problema in subprobleme, n modul
descris mai sus. Cazul trivial este acela cnd avem de mutat un singur disc i n
aceast situaie discul este mutat direct.
Cod surs Prezentm n continuare codul surs n limbajul C care rezolv
problema:
#include <stdio.h>
/* Functie care muta n discuri de pe tija initiala
pe tija finala, folosind o tija intermediara.
Rezolvarea se face in maniera Divide and Conquer. */
void hanoi(int n, char t_initial, char t_final,
char t_intermediar)
{
/* Daca avem mai mult de o tija de mutat,
atunci descompunem problema in subprobleme. */
if (n > 1)
{
hanoi(n-1, t_initial, t_intermediar, t_final);
printf("%c -> %c\n", t_initial, t_final);
hanoi(n-1, t_intermediar, t_final, t_initial);
}
/* Daca avem un singur disc de mutat, atunci
il mutam direct. La acest nivel problema are
o rezolvare triviala. */
else
{
printf("%c -> %c\n", t_initial, t_final);
}
}
int main(void)
{
/* Numarul de discuri. */
int n;
/* Citim numarul de discuri de la tastatura. */
printf("Introduceti numarul de discuri:");
scanf("%d", &n);
/* Apelam functia recursiva. */
hanoi(n, 'a', 'b', 'c');
return 0;
}

Determinarea minimului i maximului dintr-un ir de numere


Enun Se d un ir de n numere reale {x 0, x1, ..., xn-1}. S se determine valoarea
minim i valoarea maxim din acest ir de numere.
Rezolvare Metoda imediat de rezolvare este parcurgerea ntregului ir i
inspectarea fiecrui element de dou ori, o dat pentru aflarea minimului i a
doua oar pentru aflarea maximului. Codul surs n limbajul C pentru aceast
metod imediat este:
#include <stdio.h>
/* Declaram sirul de numere direct din cod. Alternativ
el poate fi citit de la tastatura sau din fisier. */
#define N 10
int x[] = {10, 5, 23, -11, 4, 2, 0, -6, 66, 40};
int main(void)
{
/* Folosim doua variabile pentru a stoca minimul
si maximul gasite. */
int min, max;
/* Vom contoriza numarul de comparatii care se fac
pentru gasirea minimului si maximului. */
int comp = 0;
int i;
/* Afisam sirul de numere. */
printf("Avem %d numere.\n", N);
for (i=0; i<N; i++)
printf("%d ", x[i]);
printf("\n\n");
/* Initializam minimul si maximul cu prima valoare
din sir. */
min = x[0];
max = x[0];
/* Parcurgem intreg sirul si actualizam minimul si
maximul atunci cand e cazul. */
for (i=1; i<N; i++)
{
/* Facem o comparatie pentru minim. */
comp++;
if (min > x[i])
min = x[i];

/* Si o comparatie pentru maxim. */


comp++;
if (max <x[i])
max = x[i];

/* Afisam rezultatele. */
printf("Minimul este %d.\n", min);
printf("Maximul este %d.\n", max);

printf("Comparatii facute: %d.\n", comp);


}

return 0;

Dac analizm metoda de mai sus, vom vedea c ea face comparaii inutile,
deoarece orice element care este candidat pentru minim nu poate fi n acelai
timp candidat pentru maxim, i invers. Deci este redundant s testm fiecare
element n parte att pentru minim ct i pentru maxim.
Putem aplica tehnica Divide and Conquer, mprind irul de numere n dou
pri. Determinm minimul i maximul pentru fiecare din cele dou pri, iar pe
urm determinm maximul global prin compararea celor dou maxime pariale,
iar minimul global prin compararea celor dou minime pariale.
Implementare Pentru implementare vom defini o funcie recursiv ce va cuta
minimul i maximul ntr-o secven a irului. Iniial vom apela aceast funcie
pentru ntregul ir. Funcia se va apela pe ea nsi, recursiv, pentru jumtarea
stng i pentru jumtatea dreapt a secvenei.
Cod surs Prezentm n continuare codul surs n limbajul C pentru rezolvarea
problemei.
#include <stdio.h>
/* Declaram sirul de numere direct din cod. Alternativ
el poate fi citit de la tastatura sau din fisier. */
#define N 10
int x[] = {10, 5, 23, -11, 4, 2, 0, -6, 66, 40};
/* Numaram cate comparatii se fac in total. */
int comp = 0;
/* Functie care determina minimul si maximul dintr-o
secventa a sirului de numere. Secventa este
delimitata de indicii "st" si "dr". Valorile minime
si maxime gasite vor fi returnate prin pointerii
"min" si "max" primiti ca si parametru. */
void minmax(int st, int dr, int *min, int *max)
{
int mijloc, min_st, max_st, min_dr, max_dr;
printf("Caut in secventa [%d..%d].\n", st, dr);
/* Daca secventa contine un singur numar, atunci
el este atat minim cat si maxim. */
if (st == dr)
{
*min = x[st];
*max = x[st];
}
/* Daca secventa contine doua numere, atunci
facem o comparatie pentru a gasi minimul si
maximul. */
else if (st == dr - 1)
{
comp++;
if (x[st] < x[dr])
{

*min = x[st];
*max = x[dr];
}
else
{

*min = x[dr];
*max = x[st];

}
}
/* Daca avem mai multe numere, atunci divizam
problema in subprobleme. */
else
{
/* Divizare. */
mijloc = (st + dr) / 2;
minmax(st, mijloc, &min_st, &max_st);
minmax(mijloc+1, dr, &min_dr, &max_dr);

/* Combinarea rezultatelor partiale.


Comparam minimele partiale intre ele
si maximele partiale intre ele. */
comp++;
if (min_st < min_dr)
*min = min_st;
else
*min = min_dr;
comp++;
if (max_st > max_dr)
*max = max_st;
else
*max = max_dr;

}
int main(void)
{
int min, max;
int i;
/* Afisam sirul de numere. */
printf("Avem %d numere.\n", N);
for (i=0; i<N; i++)
printf("%d ", x[i]);
printf("\n\n");
/* Apelam functia recursiva. */
minmax(0, N-1, &min, &max);
/* Afisam rezultatele. */
printf("\n");
printf("Minimul este %d.\n", min);
printf("Maximul este %d.\n", max);
printf("Comparatii facute: %d.\n", comp);
}

return 0;

Dac rulm n paralel cele dou programe, vom vedea c ntradevr pentru
acelai ir de numere metoda Divide and Conquer face mai puine comparaii
dect metoda clasic.

3 Probleme propuse

3.1 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 c i,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) | ci,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 c i_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 4 10 0 0 0 0
0 0 0 0 0 2 2
0 3 0 0 2 0 3
0 0 0 0 2 3 0

3.2 Cele mai apropiate puncte de pe o dreapt


Se dau N puncte n plan, situate pe o dreapt paralel cu axa OX. S se
determine perechea de puncte care sunt cel mai apropiate unul de altul. Dac
exist mai multe asemenea perechi, se va determina una din ele.
Datele de intrare se citesc din fiierul puncte.in. Pe prima linie din fiier apare
N, numrul de puncte. Pe a doua linie apar N valori reale, reprezentnd
coordonatele X ale celor N puncte. Coordonatele Y ale punctelor nu se
precizeaz, deoarece toate punctele au aceeai coordonat Y. n fiier punctele
apar sortate cresctor n ordinea coordonatei X.
Programul va scrie n fiierul puncte.out dou valori reale reprezentnd
coordonatele celor mai apropiate dou puncte.
Rezolvarea divide and conquer se face n felul urmtor:
se mparte mulimea de puncte n dou jumti;
punctele fiind sortate dup coordonata X, avem trei variante posibile:
fie perechea pe care o cutm se afl n prima jumtate;
fie perechea se afl n a doua jumtate;
fie un punct al perechii se afl n prima jumtate i cellalt punct n a doua
jumtate;
n manier divide and conquer rezolvm subproblemele pentru cele dou
jumti;

memorm distana minim gsit pentru prima jumtate i pentru a doua


jumtate;
verificm dac exist o pereche de puncte cu un punct din prima jumtate i
un punct din a doua jumtate astfel nct s obinem o distan mai mic:
se poate demonstra faptul c singura variant n care am putea obine o
distan mai mic este folosind cel mai din dreapta punct din prima
jumtate mpreun cu cel mai din stnga punct din a doua jumtate;
n final pstrm distana cea mai scurt din cele trei variante.

You might also like