Sunteți pe pagina 1din 7

40 Tehnici de programare

1.4. Metoda „Divide et Impera“


1.4.1. Descrierea metodei „Divide et Impera“
Metoda divide et impera se poate folosi pentru problemele care pot fi descompuse în
subprobleme similare cu problema iniţială (care se rezolvă prin aceeaşi metodă) şi care
prelucrează mulţimi de date de dimensiuni mai mici, independente unele de altele (care
folosesc mulţimi de date de intrare disjuncte).

Scop: identificarea problemelor care pot fi descompuse în subprobleme similare care


folosesc mulţimi de date de intrare disjuncte.
Enunţul problemei 1: Să se calculeze suma elementelor dintr-un vector v care conţine
numere întregi.
Mulţimea datelor de intrare o reprezintă cele n elemente ale vectorului v. Ele pot fi divizate
în câte două submulţimi disjuncte, prin divizarea mulţimii indicilor în două submulţimi.
Mulţimea iniţială a indicilor este determinată de primul indice (s) şi de ultimul indice (d), iar
intervalul indicilor care se divizează este [s,d]. El se divizează în două submulţimi disjuncte,
[s,m] şi [m+1,d], unde m este indicele din mijlocul intervalului: m=(s+d)/2. Astfel, problema
iniţială este descompusă în două subprobleme, fiecare dintre ele constând în calcularea
sumei numerelor dintr-o submulţime de elemente (care corespunde unui subinterval al
indicilor). Descompunerea continuă până când fiecare submulţime conţine un singur
element – şi se poate calcula suma, obţinându-se soluţia subproblemei.
Enunţul problemei 2: Să se calculeze suma 12+23+ ... +n(n+1).
Mulţimea datelor de intrare o reprezintă primele n numere naturale. Mulţimea iniţială este
determinată de primul număr (s=1) şi de ultimul număr (d=n), iar intervalul care se divi-
zează este [s,d]. El se divizează în două submulţimi disjuncte, [s,m] şi [m+1,d], unde m
este numărul din mijlocul intervalului: m=(s+d)/2. Astfel, problema iniţială este descompusă
în două subprobleme, fiecare dintre ele constând în calcularea sumei produselor dintre
două numere consecutive dintr-o submulţime de elemente (care corespunde unui subinter-
val al numerelor). Descompunerea continuă până când fiecare submulţime conţine un
singur element – şi se poate calcula produsul, care se va adăuga la sumă, pentru a obţine
soluţia subproblemei.
Enunţul problemei 3: Să se genereze termenul n al şirului lui Fibonacci.
Şirul lui Fibonacci este definit recursiv: f1=1, f2=1 şi fn= fn-2+ fn-1, pentru n3. Problema
determinării termenului n al şirului lui Fibonacci se poate descompune în două subpro-
bleme: determinarea termenului n-1 şi determinarea termenului n-2. Descompu-
nerea continuă până când trebuie determinaţi termenii f1 şi f2, a căror valoare este
cunoscută.

Metoda Divide et impera se bazează pe descompunerea unei probleme în


subprobleme similare, prin intermediul unui proces recursiv. Procesul recursiv de
descompunere a unei subprobleme în alte subprobleme continuă până se obţine
o subproblemă cu rezolvarea imediată (cazul de bază), după care se compun
soluţiile subproblemelor până se obţine soluţia problemei iniţiale.
Informatică 41
Paşii algoritmului sunt:
PAS1. Se descompune problema în subprobleme similare problemei iniţiale, de dimen-
siuni mai mici, independente unele de altele (care folosesc mulţimi de date de
intrare disjuncte – di).
PAS2. Dacă subproblema permite rezolvarea imediată (corespunde cazului de bază),
atunci se rezolvă obţinându-se soluţia s; altfel, se revine la Pas1.
PAS3. Se combină soluţiile subproblemelor în care a fost descompusă (si) o subproble-
mă, până când se obţine soluţia problemei iniţiale.

1.4.2. Implementarea metodei Divide et Impera


Deoarece subproblemele în care se descompune problema sunt similare cu problema iniţi-
ală, algoritmul divide et impera poate fi implementat recursiv. Subprogramul recursiv
divide_et_impera(d,s), unde d reprezintă dimensiunea subproblemei (corespunde mulţimii
datelor de intrare), iar s soluţia subproblemei, poate fi descris în pseudocod astfel:
divide_et_impera(d,s)
început
dacă dimensiunea d corespunde unui caz de bază
atunci se determină soluţia s a problemei;
altfel
pentru i=1,n execută
se determină dimensiunea d i a subproblemei P i;
se determină soluţia s i a subproblemei P i prin
apelul divide_et_impera(d_i,s_i);
sfârşit_pentru;
se combină soluţiile s 1, s 2, s 3, ..., s n;
sfârşit_dacă;
sfârşit;
Implementarea acestui algoritm în limbajul C++ se face astfel:
/*declaraţii globale pentru datele de intrare ce vor fi divizate în sub-
mulţimi disjuncte pentru subproblemele în care se descompune problema*/
void divizeaza(<parametri: submulţimile>)
{//se divizează mulţimea de date de intrare în submulţimi disjuncte d i}
void combina(<parametri: soluţiile s i care se combină>)
{//se combină soluţiile obţinute s i}
void dei(<parametri: mulţimea de date d şi soluţia s>)
{//declaraţii de variabile locale
if (<este caz de bază>) {//se obţine soluţia corespunzătoare subproblemei}
else
{divizeaza(<parametri: k submulţimi>);
for (i=1;i=k;i++)
dei(<parametri: mulţimea de date d i şi soluţia s i>);
combina(<parametri: soluţiile s i>);}}
void main()
{//declaraţii de variabile locale
//se citesc datele de intrare ale problemei – mulţimea d
dei(<parametri: mulţimea de date d şi soluţia s>);
//se afişează soluţia problemei – s}
42 Tehnici de programare
Exemplul 1. Să se calculeze suma elementelor pare dintr-un vector v care conţine nume-
re întregi. Numărul de elemente ale vectorului (n) şi elementele lui se citesc de la tastatură.
s=1 1 2 3 4 5 d=5
5 10 15 20 25
m=(1+5)/2=3
z=z12345= z123+ z45=10+20=30

1 2 3 4 5
s=1 5 10 15 d=3 s=4 20 25 d=5
m=(1+3)/2=2 m=(4+5)/2=4
z123= z12+ z45= z4+ z5=20+0=20
z3=10+0=10
1 2 3 4 5
s=1 5 10 d=2 15 20 25
m=(1+2)/2=1 s=d=3 s=d=4 s=d=5
z12= z1+ z2=0+10=10 z3=0 z4=20 z5=0

1 2
s=d=1 5 10 s=d=2
z1=0 z2=10
Implementarea metodei divide et impera în acest exemplu se face astfel:
 Subprogramul divizeaza()– Numărul de subprobleme în care se descompune problema
este 2 (k=2). Mulţimea datelor de intrare este divizată în două submulţimi disjuncte, prin
divizarea mulţimii indicilor în două submulţimi disjuncte de indici, adică mulţimea indicilor
[s,d] (unde s este primul indice, iar d ultimul indice) este divizată în două submulţimi disjunc-
te [s,m] şi [m+1,d], unde m este indicele din mijlocul intervalului: m=(s+d)/2. În subpro-
gram, procesul de divizare constă în determinarea mijlocului intervalului – m.
 Subprogramul combina()– Combinarea soluţiei înseamnă adunarea celor două sume
obţinute prin rezolvarea celor două subprobleme. În subprogram sunt combinate cele
două valori obţinute din prelucrarea celor două intervale, adică se adună cele două
valori x şi y, obţinându-se soluţia z.
 Subprogramul dei()– O subproblemă corespunde cazului de bază atunci când sub-
mulţimea conţine un singur element (se poate calcula suma, obţinându-se soluţia subpro-
blemei). Dacă s-a terminat procesul recursiv (prin procesul de divizare, cele două capete
ale intervalului au ajuns să fie identice), atunci se prelucrează cazul de bază (se calcu-
lează suma în variabila z, corespunzătoare soluţiei, astfel: dacă numărul v[s] este par,
atunci suma va fi chiar numărul; altfel, are valoarea 0); altfel, se apelează subproramul
pentru divizarea intervalului, se apelează subprogramul dei() pentru primul interval, se
apelează subprogramul dei() pentru al doilea interval şi se combină cele două rezultate.
#include<iostream.h>
int v[100],n;
void divizeaza(int s,int d,int &m) {m=(s+d)/2;}
void combina(int x,int y,int &z) {z=x+y;}
void dei(int s,int d,int &z)
{int m,x1,x2;
if (d==s)
if (v[s]%2==0) z=v[s]; else z=0;
else
{divizeaza(s,d,m); dei(s,m,x1); dei(m+1,d,x2); combina(x1,x2,z);}}
void main()
{int i,z; cout<<"n= ";cin>>n;
for(i=1;i<=n;i++) {cout<<"v["<<i<<"]="; cin>>v[i];}
dei(1,n,z); cout<<"suma= "<<z;}
Exemplul 2: Să se calculeze suma 12+23+ ... +n(n+1).
s=1 1 2 3 4 5 d=5
m=(1+5)/2=3
z=z12345= z123+ z45=20+50=70

s=1 1 2 3 d=3 s=4 4 5 d=5


m=(1+3)/2=2 m=(4+5)/2=4
z123= z12+ z3=8+12=20 z45= z4+ z5=20+30=50

s=1 1 2 d=2 3 4 5
m=(1+2)/2=1 s=d=3 s=d=4 s=d=5
z12= z1+ z2=2+6=8 z3=34=12 z4=45=20 z5=56=30

s=d=1 1 2 s=d=2
z1=12=2 z2=23=6
Implementarea metodei divide et impera în acest exemplu se face astfel:
 Subprogramul divizeaza()– Numărul de subprobleme în care se descompune pro-
blema este 2 (k=2). Mulţimea datelor de intrare este divizată în două submulţimi disjunc-
te, prin divizarea mulţimii primelor n numere naturale în două submulţimi disjuncte, adi-
că mulţimea [s,d] (unde s este primul număr din mulţime, iar d ultimul număr din
mulţime) este divizată în două submulţimi disjuncte, [s,m] şi [m+1,d], unde m este nu-
mărul din mijlocul intervalului: m=(s+d)/2. În subprogram, procesul de divizare constă
în determinarea mijlocului intervalului, m.
 Subprogramul combina()– Combinarea soluţiei înseamnă adunarea celor două sume
obţinute prin rezolvarea celor două subprobleme. În subprogram sunt combinate cele
două valori obţinute din cele două intervale (se adună cele două valori, x şi y) obţinân-
du-se soluţia z.
 Subprogramul dei()– O subproblemă corespunde cazului de bază atunci când sub-
mulţimea conţine un singur element (se poate calcula termenul sumei – produsul celor
două numere consecutive – obţinându-se soluţia subproblemei). Dacă s-a terminat
procesul recursiv (prin procesul de divizare, cele două capete ale intervalului au ajuns
să fie identice), atunci se prelucrează cazul de bază (se calculează produsul în variabila
z, corespunzătoare soluţiei); altfel, se apelează subprogramul pentru divizarea inter-
valului, se apelează subprogramul dei() pentru primul interval, se apelează subpro-
gramul dei() pentru al doilea interval şi se combină cele două rezultate.
#include<iostream.h>
int n;
void divizeaza(int s,int d,int &m) {m=(s+d)/2;}
void combina(int x,int y,int &z) {z=x+y;}
void dei(int s,int d,int &z)
{int m,x1,x2;
if (d==s) z=s*(s+1);
else {divizeaza(s,d,m); dei(s,m,x1); dei(m+1,d,x2); combina(x1,x2,z);}}
void main()
{int z; cout<<"n= ";cin>>n; dei(1,n,z); cout<<"suma ="<<z; }
Complexitatea algoritmului divide et impera
Metoda divide et impera se bazează pe rezolvarea recursivă a subproblemelor în care
este divizată problema iniţială.
Atunci când un algoritm conţine un apel recursiv, timpul său de execuţie este dat de o formulă
recursivă care calculează timpul de execuţie al algoritmului pentru o dimensiune n a datelor
de intrare, cu ajutorul timpilor de execuţie pentru dimensiuni mai mici. Timpul de execuţie al
unui algoritm care foloseşte metoda divide et impera se bazează pe calculul timpilor de exe-
cuţie ai celor trei etape de rezolvare a problemei.
Metoda divide et impera se recomandă în următoarele cazuri:
 algoritmul obţinut este mai eficient decât algoritmul clasic (iterativ) – de exemplu, algo-
ritmul de căutare într-un vector sortat şi algoritmii pentru sortarea unui vector;
 rezolvarea problemei prin divizarea ei în subprobleme este mai simplă decât rezol-
varea clasică (iterativă) – de exemplu, problema turnurilor din Hanoi şi generarea unor
modele fractale.

1.4.3. Căutarea binară


Să se caute, într-un şir de numere întregi ordonate strict crescător (sau varianta strict des-
crescător), poziţia în care se găseşte în şir o valoare x citită de la tastatură. Dacă valoarea
nu se găseşte în şir, să se afişeze un mesaj de informare.
Algoritmul de căutare secvenţială într-un vector are ordinul de complexitate O(n). Pornind
de la faptul că vectorul este un vector particular (este ordonat strict crescător sau strict
descrescător), se poate folosi metoda divide et impera. Pentru un vector ordonat strict
crescător, paşii algoritmului sunt:
PAS1. Se divizează vectorul v în doi subvectori, prin divizarea mulţimii indicilor [s,d] (unde
s este indicele primului element, iar d indicele ultimului element) în două submulţimi
disjuncte, [s,m] şi [m+1,d], unde m este indicele din mijlocul intervalului: m=(s+d)/2.
PAS2. Dacă elementul situat pe poziţia din mijloc (v[m]) are valoarea x, atunci proble-
ma este rezolvată şi poziţia este m; altfel, dacă elementul din mijlocul vectorului
este mai mic decât valoarea x, atunci căutarea se face printre elementele cu
indicii în mulţimea [s,m]; altfel, căutarea se face printre elementele cu indicii din
mulţimea [m+1,d]. Pasul 2 se execută până când se găseşte elementul sau până
când vectorul nu mai poate fi împărţit în subvectori.
#include<iostream.h>
int v[100],n,x;
void divizeaza(int s,int d,int &m) {m=(s+d)/2;}
void cauta(int s,int d,int &z)
{int m;
if (d>s){divizeaza(s,d,m);
if (v[m]==x) z=m;
else if (x>v[m]) cauta(m+1,d,z);
else cauta(s,m,z);}}
void main()
{int i,z=0; cout<<"n= ";cin>>n; cout<<"x= ";cin>>x;
for(i=1;i<=n;i++) {cout<<"v["<<i<<"]="; cin>>v[i];}
cauta(1,n,z);
if(z==0) cout<<"nu exista elementul "<<x<<" in vector";
else cout<<"exista pe pozitia "<<z;}
Informatică 59

1. 5. Metoda greedy
1.5.1. Descrierea metodei greedy
Metoda greedy se poate folosi pentru problemele în care, dându-se o mulţime finită A,
trebuie determinată o mulţime SA care să îndeplinească anumite condiţii. Metoda furni-
zează o singură soluţie, reprezentată prin elementele mulţimii S. Ca şi în cazul metodei
backtracking, soluţia problemei este dată de un vector S = {x1, x2, …, xn} ale cărui elemente
aparţin însă unei singure mulţimi A. Spre deosebire de metoda backtracking, metoda Greedy
nu găseşte decât o singură soluţie şi, în general, această soluţie este soluţia optimă.

Scop: identificarea problemelor în care soluţia optimă este o submulţime inclusă într-o
mulţime dată, care trebuie să îndeplinească anumite condiţii.
Enunţul problemei 1: Să se repartizeze optim o resursă (de exemplu, o sală de specta-
cole, o sală de conferinţe, o sală de sport) mai multor activităţi (spectacole, prezentări de
produse, respectiv meciuri) care concurează pentru a obţine resursa respectivă.

Metoda greedy construieşte soluţia prin selectarea, dintr-o mulţime de elemente,


a elementelor care îndeplinesc o anumită condiţie. Pentru ca elementele
care se selectează să aparţină soluţiei optime, la pasul k se alege candidatul
optim pentru elementul xk al soluţiei.

Implementarea metodei greedy


Pentru implementarea metodei greedy se vor folosi două subprograme:
 subprogramul sort() – care ordonează mulţimea A după criteriul candidatului optim;
 subprogramul greedy() – care implementează metoda propriu-zisă.
În exemplele următoare, datele de intrare vor fi citite dintr-un fişier text.
Exemplul 1. Problema planificării optime a activităţilor.
Se folosesc următoarele date şi structuri de date:
 Pentru numărul de elemente ale celor două mulţimi se folosesc variabilele n – numărul
de activităţi (numărul de elemente ale mulţimii A) şi m – numărul de activităţi selectate
pentru a forma soluţia problemei (numărul de elemente ale mulţimii S).
 Pentru mulţimea activităţilor (mulţimea A) se foloseşte vectorul a, ale cărui elemente
sunt de tip înregistrare activitate – care conţine trei câmpuri, x şi y pentru ora la
care începe activitatea, respectiv ora la care se termină activitatea – şi k pentru numărul
activităţii (este folosit ca un identificator al activităţii).
 Soluţia (mulţimea S) va fi memorată în vectorul s. În fiecare element al vectorului s se
memorează indicele elementului selectat din vectorul a, după ce a fost sortat după
criteriul candidatului optim.
Se folosesc următoarele subprograme:
 Subprogramul citeste() – pentru citirea datelor din fişierul text. În fişierul text, pe
primul rând este scris numărul de activităţi n, iar pe următoarele n rânduri – perechi de
numere întregi, separate prin spaţiu, care reprezintă ora de început şi ora de sfârşit ale
unei activităţi. Fiecărei activităţi i se atribuie ca identificator numărul de ordine de la citi-
rea din fişier. În urma execuţiei acestui subprogram, se creează vectorul activităţilor – a.
 Subprogramul sort() – sortează vectorul a crescător după timpul de terminare a
activităţilor (câmpul y).
 Subprogramul greedy() – implementează strategia greedy pentru această problemă.
 Subprogramul afiseaza() – afişează soluţia problemei folosind informaţiile din
vectorul s – indicele fiecărei activităţi selectate.
Strategia greedy este implementată astfel:
PAS1. Se iniţializează vectorul s cu indicele primului element din vectorul a (se selec-
tează, ca primă activitate, activitatea care are ora de terminare cea mai mică).
PAS2. Pentru următoarele n-1 elemente ale vectorului a, execută
PAS3. Dacă ora la care începe activitatea din elementul curent al vectorului a
este mai mare sau egală cu ora la care se termină ultima activitate adău-
gată la soluţie (în vectorul s), atunci activitatea este adăugată la soluţie.
Mulţimea activităţilor
Activitatea 1 2 3 4 5 6 7 8
Ora de începere 9 12 8 10 16 14 20 19
Ora de terminare 11 13 10 12 18 16 22 21
Mulţimea activităţilor – după ce a fost sortată
Activitatea 3 1 4 2 6 5 8 7
Ora de începere 8 9 10 12 14 16 19 20
Ora de terminare 10 11 12 13 16 18 21 22
Soluţia problemei
Activitatea 3 4 2 6 5 8
Ora de începere 8 10 12 14 16 19
Ora de terminare 10 12 13 16 18 21
Programul este:
#include<fstream.h>
struct activitate {int x,y,k;};
activitate a[20]; int n,m,s[20];
fstream f("greedy1.txt",ios::in);
void citeste()
{int i; f>>n;
for(i=1;i<=n;i++) {f>>a[i].x>>a[i].y; a[i].k=i;} f.close();}
void sort()
{int i,j; activitate aux;
for (i=1;i<n;i++)
for (j=i+1;j<=n;j++)
if (a[i].y>a[j].y) {aux=a[i]; a[i]=a[j]; a[j]=aux;}}
void greedy()
{int i,j; s[1]=1; j=1;
for (i=2;i<=n;i++)
if (a[i].x>=a[s[j]].y) {j++; s[j]=i;}
m=j;}
void afiseaza()
{cout<<"Planificarea activitatilor: "<<endl;
for (int i=1;i<=m;i++)
cout<<"Activitatea "<< a[s[i]].k<<" incepe la ora ";
cout<<a[s[i]].x <<" si se termina la ora "<<a[s[i]].y<<endl;}
void main()
{citeste(); sort(); greedy(); afiseaza();}

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