Probleme rezolvate pe categorii
Metoda divide et impera
Înainte de probleme Tiparul problemelor rezolvate prin metoda divide et impera
Metoda divide et impera este o metodă de programare care rezolvă o problemă prin descompunerea ei în subprobleme independente, de același tip cu problema inițială și de dimensiuni mai mici. Subproblemele se rezolvă separat, iar din soluțiile lor se obține soluția problemei inițiale.
Tiparul rezolvării
Rezolvarea are următoarele etape:
- descompunerea: problema inițială se împarte în două sau mai multe subprobleme de același tip, care prelucrează date disjuncte;
- rezolvarea subproblemelor: fiecare subproblemă se descompune la rândul ei, până se ajunge la o subproblemă care admite rezolvare imediată, numită caz de bază;
- combinarea soluțiilor: din soluțiile subproblemelor se obține soluția subproblemei din care au provenit, până la soluția problemei inițiale.
La problemele cu un șir x[1..n], o subproblemă este prelucrarea unei secvențe x[p..u]. Ea se
împarte la mijloc, în x[p..m] și x[m+1..u], cu m = (p + u) / 2. Secvența cu un singur element,
p = u, este de obicei cazul de bază.
| Problema | Descompunerea | Cazul de bază | Combinarea |
|---|---|---|---|
| minimul unui șir | la mijloc | un element: minimul este chiar el | cel mai mic dintre cele două minime |
| suma elementelor | la mijloc | un element: suma este chiar el | suma celor două sume |
| căutarea binară | la mijloc, se păstrează o singură jumătate | secvența vidă sau elementul din mijloc egal cu valoarea căutată | nu este necesară |
| turnurile din Hanoi | n − 1 discuri, discul cel mai mare, din nou n − 1 discuri | un singur disc, mutat direct | ordinea mutărilor |
| sortarea prin interclasare | la mijloc | un element, deja ordonat | interclasarea celor două jumătăți ordonate |
| sortarea rapidă | în jurul pivotului, așezat pe poziția lui din șirul ordonat | o secvență cu cel mult un element | nu este necesară |
Programul-șablon
Subprogramul recursiv de mai jos are toate părțile tiparului. Ca exemplu, el determină cea mai mare valoare dintr-un șir.
Problema 1 · Exercițiu Minimul unui șir prin divide et impera
Ce cere problema
Șirul se împarte în două subșiruri. Minimul șirului este cel mai mic dintre minimele celor două subșiruri:
| Subșirul | Minimul |
|---|---|
| x[1..3] = (6, 2, 1) | 1 |
| x[4..5] = (7, −3) | −3 |
| x[1..5], tot șirul | cel mai mic dintre 1 și −3, adică −3 |
Fiecare subșir se împarte la rândul lui, până la subșiruri cu un singur element. Minimul unui subșir cu un singur element este chiar acel element.
Programul și descrierea metodei
În stânga sunt subprogramele și programul principal. În dreapta este metoda de rezolvare, descrisă în limbaj natural, cu pașii așezați pe niveluri după structura programului.
#include <iostream>
#include <fstream>
using namespace std;
int x[101], n;
void citire() {
ifstream f("minim.in");
f >> n;
for (int i = 1; i <= n; i++)
f >> x[i];
f.close();
}
Subprogramul citire()
ifstream f("minim.in")deschide fișierul pentru citire- se citesc n și cele n valori ale șirului, în
x[1], …,x[n] f.close()închide fișierul
/* Minimul subsirului x[p..u]. */
int minim(int p, int u) {
if (p == u) // un singur element
return x[p];
int m = (p + u) / 2;
int min1 = minim(p, m);
int min2 = minim(m + 1, u);
if (min1 < min2)
return min1;
return min2;
}
Subprogramul minim(p, u)
- dacă subșirul are un singur element, adică p = u
- problema admite rezolvare imediată: se returnează
x[p]
- problema admite rezolvare imediată: se returnează
- altfel
- m este indicele din mijlocul intervalului [p, u]
- min1 primește minimul subșirului
x[p..m], prin autoapel - min2 primește minimul subșirului
x[m+1..u], prin autoapel - se returnează cea mai mică dintre valorile min1 și min2
int main() {
citire();
cout << minim(1, n);
return 0;
}
Programul principal
- se citesc datele din fișier
- se afișează minimul întregului șir, dat de apelul
minim(1, n)
Problema 2 · Exercițiu Suma elementelor unui vector prin divide et impera
Ce cere problema
Mulțimea indicilor [1, n] se divide în două submulțimi disjuncte, [s, m] și [m + 1, d], unde m este indicele din mijlocul intervalului [s, d]. Suma vectorului este suma celor două sume parțiale:
| Intervalul | Elementele | Suma |
|---|---|---|
| [1, 3] | 4, −1, 7 | 10 |
| [4, 6] | 3, −5, 2 | 0 |
| [1, 6] | tot vectorul | 10 + 0 = 10 |
Descompunerea continuă până când fiecare submulțime conține un singur element. Suma unui singur element este chiar valoarea lui.
Programul și descrierea metodei
În stânga sunt subprogramul și programul principal. În dreapta este metoda de rezolvare, descrisă în limbaj natural, cu pașii așezați pe niveluri după structura programului.
Problema 3 · Exercițiu Căutarea binară prin divide et impera
Ce cere problema
Șirul este ordonat, deci valoarea x se compară cu elementul din mijloc:
- dacă este egală cu el, poziția a fost găsită;
- dacă este mai mică, x nu poate fi decât în jumătatea din stânga;
- dacă este mai mare, x nu poate fi decât în jumătatea din dreapta.
Pentru x = 17 și șirul din exemplu:
| Poziția | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| Valoarea | −4 | 5 | 8 | 11 | 17 | 23 | 42 |
Elementul din mijloc este 11, mai mic decât 17, deci căutarea continuă numai pe pozițiile de la 5 la 7. Spre deosebire de celelalte probleme rezolvate prin divide et impera, se rezolvă o singură subproblemă, iar soluția ei este chiar soluția problemei inițiale.
Programul și descrierea metodei
În stânga sunt subprogramul și programul principal. În dreapta este metoda de rezolvare, descrisă în limbaj natural, cu pașii așezați pe niveluri după structura programului.
#include <iostream>
#include <fstream>
using namespace std;
int v[101], n, x;
/* Pozitia lui x in v[st..dr],
sau 0 daca x nu se afla acolo. */
int cauta(int st, int dr) {
if (st > dr) // subsir vid: x nu exista
return 0;
int mij = (st + dr) / 2;
if (v[mij] == x)
return mij;
if (x < v[mij])
return cauta(st, mij - 1); // jumatatea din stanga
return cauta(mij + 1, dr); // jumatatea din dreapta
}
Subprogramul cauta(st, dr)
- dacă st > dr, subșirul este vid
- x nu se află în șir: se returnează 0
- mij este indicele din mijlocul intervalului [st, dr]
- dacă
v[mij]este egal cu x- se returnează mij
- altfel, dacă x este mai mic decât
v[mij]- se returnează rezultatul căutării în
v[st..mij−1], prin autoapel
- se returnează rezultatul căutării în
- altfel
- se returnează rezultatul căutării în
v[mij+1..dr], prin autoapel
- se returnează rezultatul căutării în
Pozițiile încep de la 1, deci valoarea 0 nu poate fi o poziție și arată că x lipsește.
int main() {
cin >> n >> x;
for (int i = 1; i <= n; i++)
cin >> v[i];
ofstream g("cautare.out");
int poz = cauta(1, n);
if (poz == 0)
g << "nu exista";
else
g << poz;
g.close();
return 0;
}
Programul principal
- se citesc n, x și cele n elemente ale șirului
ofstream g("cautare.out")deschide fișierul pentru scriere- poz primește rezultatul apelului
cauta(1, n), pentru tot șirul - dacă poz este 0
- se scrie în fișier mesajul
nu exista
- se scrie în fișier mesajul
- altfel
- se scrie în fișier poz
g.close()închide fișierul
Problema 4 · Exercițiu Problema turnurilor din Hanoi
Ce cere problema
Discul cel mai mare poate trece de pe tija T1 pe tija T2 numai când celelalte n − 1 discuri se află pe tija T3. De aceea mutarea celor n discuri se descompune astfel:
| Pasul | Ce se mută | De pe | Pe | Tija intermediară |
|---|---|---|---|---|
| 1 | cele n − 1 discuri de deasupra | T1 | T3 | T2 |
| 2 | discul cel mai mare | T1 | T2 | — |
| 3 | cele n − 1 discuri | T3 | T2 | T1 |
Pașii 1 și 3 sunt probleme de același tip cu problema inițială, cu un disc mai puțin. Fiecare dintre ele se descompune după aceiași trei pași, până la un singur disc, care se mută direct.
Programul și descrierea metodei
În stânga sunt subprogramul și programul principal. În dreapta este metoda de rezolvare, descrisă în limbaj natural, cu pașii așezați pe niveluri după structura programului.
#include <iostream>
using namespace std;
int n;
/* Muta n discuri de pe tija s pe tija d, folosind tija m. */
void hanoi(int n, int s, int d, int m) {
if (n == 1)
cout << s << " -> " << d << endl;
else {
hanoi(n - 1, s, m, d); // n-1 discuri, pe m
cout << s << " -> " << d << endl; // cel mai mare
hanoi(n - 1, m, d, s); // n-1 discuri, pe d
}
}
Subprogramul hanoi(n, s, d, m)
- parametrii tijelor
- s este tija sursă
- d este tija destinație
- m este tija intermediară
- dacă n = 1
- problema admite rezolvare imediată: discul se mută de pe s pe d
- altfel
- cele n − 1 discuri de deasupra se mută de pe s pe m, cu d ca tijă intermediară, prin autoapel
- discul cel mai mare se mută de pe s pe d
- cele n − 1 discuri se mută de pe m pe d, cu s ca tijă intermediară, prin autoapel
int main() {
cin >> n;
hanoi(n, 1, 2, 3);
return 0;
}
Programul principal
- se citește n
- se mută cele n discuri de pe tija 1 pe tija 2, cu tija 3 ca tijă intermediară
Problema 5 · Exercițiu Sortarea prin interclasare
Ce cere problema
Șirul se împarte în două subșiruri de lungimi egale sau care diferă cu cel mult o unitate. Fiecare subșir se ordonează prin aceeași metodă, iar cele două subșiruri ordonate se interclasează:
| Etapa | Subșirurile |
|---|---|
| descompunerea | 38 27 43 3 și 9 82 10 |
| descompunerea | 38 27, 43 3, 9 82 și 10 |
| descompunerea | 38, 27, 43, 3, 9, 82 și 10 |
| interclasarea | 27 38, 3 43, 9 82 și 10 |
| interclasarea | 3 27 38 43 și 9 10 82 |
| interclasarea | 3 9 10 27 38 43 82 |
Un subșir cu un singur element este deja ordonat: acesta este cazul de bază. Combinarea soluțiilor este interclasarea a două subșiruri ordonate crescător.
Programul și descrierea metodei
În stânga sunt subprogramele și programul principal. În dreapta este metoda de rezolvare, descrisă în limbaj natural, cu pașii așezați pe niveluri după structura programului.
#include <iostream>
using namespace std;
int v[101], a[101], n;
/* Interclaseaza v[st..mij] si v[mij+1..dr],
ordonate crescator. */
void interclasare(int st, int mij, int dr) {
int i = st, j = mij + 1, k = 0;
while (i <= mij && j <= dr)
if (v[i] <= v[j])
a[++k] = v[i++];
else
a[++k] = v[j++];
while (i <= mij) // ce a ramas in stanga
a[++k] = v[i++];
while (j <= dr) // ce a ramas in dreapta
a[++k] = v[j++];
for (i = st, k = 1; i <= dr; i++, k++)
v[i] = a[k]; // inapoi in v
}
Subprogramul interclasare(st, mij, dr)
- i parcurge subșirul din stânga,
v[st..mij], iar j subșirul din dreapta,v[mij+1..dr] - cât timp ambele subșiruri mai au elemente
- cel mai mic dintre
v[i]șiv[j]se adaugă în vectorul auxiliar a, iar indicele lui înaintează a[++k] = v[i++]mărește întâi k, punev[i]pe poziția k și apoi mărește i
- cel mai mic dintre
- elementele rămase într-unul dintre subșiruri se adaugă la sfârșitul lui a
- elementele din a se copiază înapoi în v, pe pozițiile de la st la dr
void mergesort(int st, int dr) {
if (st < dr) {
int mij = (st + dr) / 2;
mergesort(st, mij);
mergesort(mij + 1, dr);
interclasare(st, mij, dr);
}
}
Subprogramul mergesort(st, dr)
- dacă subșirul
v[st..dr]are cel puțin două elemente- mij este indicele din mijlocul intervalului
- se ordonează
v[st..mij], prin autoapel - se ordonează
v[mij+1..dr], prin autoapel - se interclasează cele două subșiruri ordonate
- un subșir cu un singur element este deja ordonat: subprogramul nu face nimic
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> v[i];
mergesort(1, n);
for (int i = 1; i <= n; i++)
cout << v[i] << " ";
return 0;
}
Programul principal
- se citesc n și cele n elemente ale șirului
- se ordonează tot șirul, prin apelul
mergesort(1, n) - se afișează șirul ordonat
Problema 6 · Exercițiu Sortarea rapidă
Ce cere problema
Primul element al șirului, numit pivot, se mută pe poziția pe care trebuie să se găsească în șirul ordonat. Elementele se rearanjează astfel:
- toate elementele mai mici decât pivotul ajung înaintea lui;
- toate elementele mai mari decât pivotul ajung după el.
Pentru șirul din exemplu, pivotul este 40:
| Momentul | Șirul |
|---|---|
| Înainte | 40 21 8 17 51 34 |
| După rearanjare | 34 21 8 17 40 51 |
| Rămân de ordonat | 34 21 8 17, în stânga pivotului, și 51, în dreapta lui |
Problema inițială se descompune în două subprobleme: ordonarea subșirului din stânga pivotului și ordonarea subșirului din dreapta lui. Descompunerea continuă până la subșiruri cu cel mult un element. Combinarea soluțiilor nu cere nicio operație: subșirurile ordonate sunt deja la locul lor.
Programul și descrierea metodei
În stânga sunt subprogramele și programul principal. În dreapta este metoda de rezolvare, descrisă în limbaj natural, cu pașii așezați pe niveluri după structura programului.
#include <iostream>
using namespace std;
int v[101], n;
void schimba(int& a, int& b) {
int aux = a;
a = b;
b = aux;
}
Subprogramul schimba(a, b)
- interschimbă valorile variabilelor a și b, cu ajutorul variabilei aux
- a și b se transmit prin referință,
int&, ca schimbarea să se vadă după apel
/* Aseaza pivotul v[st] pe pozitia lui din sirul ordonat
si furnizeaza acest loc prin parametrul mij. */
void pozitie(int st, int dr, int& mij) {
int i = st, j = dr, pi = 0, pj = 1;
while (i < j) {
if (v[i] > v[j]) {
schimba(v[i], v[j]);
schimba(pi, pj); // se misca celalalt capat
}
i = i + pi;
j = j - pj;
}
mij = i;
}
Subprogramul pozitie(st, dr, mij)
- i pornește de la st, iar j de la dr
- pi și pj arată care indice se mișcă: la început pi = 0 și pj = 1, deci se mișcă numai j
- cât timp i < j
- dacă
v[i] > v[j], cele două elemente sunt în ordine greșită- se interschimbă
v[i]șiv[j] - se interschimbă pi și pj, deci de acum se mișcă celălalt indice
- se interschimbă
- i crește cu pi, iar j scade cu pj
- dacă
- pivotul se află mereu pe poziția indicelui care nu se mișcă
- când i și j se întâlnesc, pivotul este pe poziția lui din șirul ordonat: mij primește i
void quicksort(int st, int dr) {
int mij;
if (st < dr) {
pozitie(st, dr, mij);
quicksort(st, mij - 1);
quicksort(mij + 1, dr);
}
}
Subprogramul quicksort(st, dr)
- dacă subșirul
v[st..dr]are cel puțin două elemente- pivotul se așază pe poziția lui din șirul ordonat, mij
- se ordonează subșirul din stânga pivotului,
v[st..mij−1], prin autoapel - se ordonează subșirul din dreapta pivotului,
v[mij+1..dr], prin autoapel
- un subșir cu cel mult un element este deja ordonat
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> v[i];
quicksort(1, n);
for (int i = 1; i <= n; i++)
cout << v[i] << " ";
return 0;
}
Programul principal
- se citesc n și cele n elemente ale șirului
- se ordonează tot șirul, prin apelul
quicksort(1, n) - se afișează șirul ordonat
Materialul acesta se citește pe educamp.ro și nu se tipărește.
S-a încheiat minutul de citit liber.
Vezi problemele rezolvate ca și cursant
Lista categoriilor, cu problemele date la examen în fiecare, se citește gratuit și integral. Pentru a citi rezolvările, te înscrii la cursul de pregătire.
Te înscrii, alegi în cont când începe cursul și plătești prima lună prin transfer bancar. Pentru a vedea mai întâi ce probleme s-au dat, poți reveni la lista categoriilor.
Acces la suportul de curs
25 RON / lună, fără ședințe
Ce cuprinde:
- Acces la toate lecțiile disponibile
- Suport de curs pentru tematica științifică și metodică
- Exemple, explicații și exerciții
- Acces la materialele noi adăugate pe platformă
- Posibilitatea de a adresa întrebări