Maksimum podeli pa vladaj
Napiši rekurzivnu funkciju koja nalazi najveći element niza tako što ga deli na dve polovine.
Primer
Ulaz
6
3 9 -1 7 9 2Izlaz
9Veliki problem se podeli na manje probleme iste vrste, oni se reše (obično rekurzivno), a zatim se njihova rešenja spoje. Na ovoj ideji su zasnovani binarna pretraga, sortiranje objedinjavanjem, brzo sortiranje i brzo stepenovanje.
| Korak | Šta radi | Merge sort | Binarna pretraga |
|---|---|---|---|
| Podeli | problem na manje delove | niz na dve polovine | interval na dve polovine |
| Reši | svaki deo (rekurzivno) | sortiraj obe polovine | nastavi samo u jednoj |
| Spoji | rešenja delova | spajanje sortiranih nizova, O(n) | ništa |
Složenost zavisi od broja delova, njihove veličine i cene spajanja:
| Rekurzija | Složenost | Primer |
|---|---|---|
| T(n) = T(n/2) + O(1) | O(log n) | binarna pretraga, brzo stepenovanje |
| T(n) = 2T(n/2) + O(n) | O(n log n) | merge sort, broj inverzija |
| T(n) = 2T(n/2) + O(1) | O(n) | maksimum niza rekurzivno |
| T(n) = T(n − 1) + O(n) | O(n²) | loše podeljen problem (quick sort u najgorem slučaju) |
Dobitak dolazi od toga što se problem deli na jednake delove: tada je nivoa rekurzije samo log₂ n.
aⁿ = (an/2)² za parno n i a · an−1 za neparno. Svaki korak prepolovi izložilac – O(log n) množenja umesto n.
#include <iostream>
using namespace std;
const long long MOD = 1000000007;
long long stepen(long long a, long long n) {
if (n == 0) return 1; // a^0 = 1
long long p = stepen(a, n / 2); // reši problem pola veličine (jednom!)
p = p * p % MOD; // spoji
if (n % 2 == 1) p = p * a % MOD; // neparan izložilac
return p;
}
int main() {
long long a, n;
cin >> a >> n;
cout << stepen(a % MOD, n) << endl;
return 0;
}
Važno je da se stepen(a, n / 2) pozove jednom i sačuva; dva poziva bi vratila složenost na O(n). Detaljnije u lekciji Algoritam brzog stepenovanja.
Najbolji deo niza je ili ceo u levoj polovini, ili ceo u desnoj, ili prelazi preko sredine. Prva dva slučaja rešava rekurzija, a treći – najbolji sufiks leve i najbolji prefiks desne polovine – u O(n). Ukupno O(n log n).
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
vector<long long> a;
long long najbolji(int l, int d) {
if (l == d) return a[l]; // jedan element
int m = (l + d) / 2;
long long levo = najbolji(l, m), desno = najbolji(m + 1, d);
// deo koji prelazi sredinu: najbolji sufiks levo + najbolji prefiks desno
long long s = 0, bestL = LLONG_MIN;
for (int i = m; i >= l; i--) { s += a[i]; bestL = max(bestL, s); }
s = 0;
long long bestD = LLONG_MIN;
for (int i = m + 1; i <= d; i++) { s += a[i]; bestD = max(bestD, s); }
return max({levo, desno, bestL + bestD});
}
int main() {
int n;
cin >> n;
a.resize(n);
for (auto &x : a) cin >> x;
cout << najbolji(0, n - 1) << endl;
return 0;
}
Za ovaj problem postoji i rešenje O(n) – Kadaneov algoritam, ali „podeli pa vladaj“ verzija pokazuje opšti obrazac: šta prelazi preko granice delova, to se računa pri spajanju.
Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.
Napiši rekurzivnu funkciju koja nalazi najveći element niza tako što ga deli na dve polovine.
6
3 9 -1 7 9 29Dat je sortiran niz i broj x. Ispiši koliko puta se x javlja, u O(log n).
8
1 2 2 2 3 5 5 8
233
1 2 3
401. Koji su koraci tehnike podeli pa vladaj?
2. Koja je složenost ako je T(n) = 2T(n/2) + O(n)?
3. Zašto je važno da se u brzom stepenovanju stepen(a, n/2) pozove samo jednom?
Metodologija podeli pa vladaj (eng. divide and conquer) predstavlja jednu od najvažnijih paradigma u savremenim algoritmima. Ideja je da se veliki problem podeli na manje podproblemske jedinice, zatim se svaki od njih reši nezavisno, a potom se rešenja kombinuju u konačno rešenje.
Ova tehnika se najviše koristi kada je moguće:
Neki od najpoznatijih algoritama koji koriste ovu paradigmu su: Merge Sort, Quick Sort, Binary Search, Strassenovo množenje matrica, FFT i mnogi drugi.
Algoritam se sastoji od tri osnovna koraka:
Ovi koraci se često prikazuju formulom:
T(n) = a · T(n/b) + f(n)
Gde:
a = broj podproblemab = faktor smanjenja veličine problema u svakom korakuf(n) = trošak spajanja rešenja podproblemaOvakve relacije rešavamo Master Teoremom (kasnije posebna lekcija).
Binarna pretraga je najjednostavniji primer primene D&C pristupa. Ako je niz sortiran, reklišemo polovinu niza u jednom koraku.
Pravila:
int binarySearch(int a[], int l, int r, int x) {
if (l > r) return -1;
int mid = (l + r) / 2;
if (a[mid] == x) return mid;
if (x < a[mid])
return binarySearch(a, l, mid - 1, x);
else
return binarySearch(a, mid + 1, r, x);
}
Vremenska složenost: O(log n) Prostorna složenost: O(log n) zbog rekurzije
Merge Sort je jedan od najpoznatijih algoritama koji ilustruje moć ove paradigme. Algoritam sortira tako što:
void mergeSort(vector &a) {
if (a.size() <= 1) return;
int mid = a.size() / 2;
vector left(a.begin(), a.begin() + mid);
vector right(a.begin() + mid, a.end());
mergeSort(left);
mergeSort(right);
int i = 0, j = 0, k = 0;
while (i < (int)left.size() && j < (int)right.size()) {
if (left[i] < right[j]) a[k++] = left[i++];
else a[k++] = right[j++];
}
while (i < (int)left.size()) a[k++] = left[i++];
while (j < (int)right.size()) a[k++] = right[j++];
}
Vremenska složenost: O(n log n)
Prostorna složenost: O(n)
Ako je algoritam opisan relacijom:
T(n) = aT(n/b) + f(n)
onda koristimo sledeća pravila:
f(n) manja od n^(log_b(a)) → vreme je O(n^(log_b(a)))f(n) jednaka → O(n^(log_b(a)) · log n)f(n) veća → O(f(n))→ Detaljna lekcija o Master Teoremu će biti dostupna posebno.
Probaj da rešiš sledeće zadatke koristeći D&C pristup:
Napiši funkciju koja nalazi maksimalni element u nizu koristeći rekurziju i pristup podeli pa vladaj.
Implementirati algoritam za pronalaženje najveće sume podniza (Maximum Subarray) koristeći D&C pristup.
Pronađi koliko puta se element x pojavljuje u sortiranom nizu koristeći binarnu pretragu (varijanta: nađi krajnje indekse).
Paradigma Podeli pa vladaj je temelj mnogih naprednih algoritama. Korišćenjem jasne strukture podeli → reši → spoji postiže se efikasnost, posebno u slučajevima kada je moguća logaritamska redukcija problema.
Preporučena naredna lekcija: □ Gramzivi algoritmi (Greedy)