Podeli pa vladaj

Lekcija 6 od 7 · Napredne tehnike

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

Pre nego što počneš: potrebno predznanje – rekurzija, merge sort.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u C++-u.

Tri koraka

KorakŠta radiMerge sortBinarna pretraga
Podeliproblem na manje deloveniz na dve polovineinterval na dve polovine
Rešisvaki deo (rekurzivno)sortiraj obe polovinenastavi samo u jednoj
Spojirešenja delovaspajanje sortiranih nizova, O(n)ništa

Složenost zavisi od broja delova, njihove veličine i cene spajanja:

RekurzijaSloženostPrimer
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.

Primer: brzo stepenovanje

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.

Primer: najveći zbir uzastopnog dela

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.

Gde se još koristi

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: Lako

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 2

Izlaz

9
ZADATAK 02Težina: Lako

Koliko puta se javlja x?

Dat je sortiran niz i broj x. Ispiši koliko puta se x javlja, u O(log n).

Primer 1

Ulaz

8
1 2 2 2 3 5 5 8
2

Izlaz

3

Primer 2

Ulaz

3
1 2 3
4

Izlaz

0
Česte greške i saveti
  • Delovi treba da budu približno jednaki – inače se gubi logaritamska dubina.
  • Rezultat podproblema koji se koristi dva puta izračunaj jednom i sačuvaj.
  • Pazi na bazni slučaj (interval od jednog elementa) da rekurzija ne bi bila beskonačna.

Proveri znanje

Otvori kviz

Podeli pa vladaj

1. 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?