Čas 2.1, 2.2, 2.3 - efikasnost i složenost algori-...

60
Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tama Vrste složenosti i načini njene analize Pored svojstva ispravnosti programa, veoma je važno i pitanje koliko program za- hteva vremena (ili izvršenih instrukcija) i memorije za svoje izvršavanje. Često nije dovoljno imati informaciju o tome koliko se neki program izvršava za neke konkretne ulazne vrednosti, već je potrebno imati neku opštiju procenu za proizvoljne ulazne vrednosti. Štaviše, potrebno je imati i opšti način za opi- sivanje i poređenje efikasnosti (ili složenosti ) različitih algoritama. Obično se razmatraju: • vremenska složenost algoritma; • prostorna (memorijska) složenost algoritma. Vremenska i prostorna složenost mogu se razmatrati • u terminima konkretnog vremena/prostora utrošenog za neku konkretnu ulaznu veličinu; • u terminima asimptotskog ponašanja vremena/prostora kada veličina ulaza raste. Vreme izvršavanja programa može biti procenjeno ili izmereno za neke konkretne veličine ulazne vrednosti i neko konkretno izvršavanje. Veličina ulazne vrednosti može biti broj ulaznih elemenata koje treba obraditi, sam ulazni broj koji treba obraditi, broj bitova potrebnih za zapisivanje ulaza koji treba obraditi, itd. Uvek je potrebno eksplicitno navesti u odnosu na koju veličinu se razmatra složenost. No, vreme izvršavanja programa može biti opisano opštije, u vidu funkcije koja zavisi od ulaznih argumenata. Često se algoritmi ne izvršavaju isto za sve ulaze istih veličina, pa je potrebno naći način za opisivanje i poređenje efikasnosti različitih algoritama. Analiza najgoreg slučaja zasniva procenu složenosti algoritma na najgorem slučaju (na slučaju za koji se algoritam najduže izvršava — u analizi vre- menske složenosti, ili na slučaju za koji algoritam koristi najviše memorije — u analizi prostorne složenosti). Ta procena može da bude varljiva, ali predstavlja dobar opšti način za poređenje efikasnosti algoritama. • U nekim situacijama moguće je izvršiti analizu prosečnog slučaja i izraču- nati prosečno vreme izvršavanja algoritma, ali da bi se to uradilo, potrebno je precizno poznavati prostor dopuštenih ulaznih vrednosti i verovatnoću da se svaka dopuštena ulazna vrednost pojavi na ulazu programa. U sluča- jevima kada je bitna garancija efikasnosti svakog pojedinačnog izvršavanja programa procena prosečnog slučaja može biti varljiva. 1

Upload: others

Post on 10-Jan-2020

4 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori-tama

Vrste složenosti i načini njene analize

Pored svojstva ispravnosti programa, veoma je važno i pitanje koliko program za-hteva vremena (ili izvršenih instrukcija) i memorije za svoje izvršavanje. Čestonije dovoljno imati informaciju o tome koliko se neki program izvršava za nekekonkretne ulazne vrednosti, već je potrebno imati neku opštiju procenu zaproizvoljne ulazne vrednosti. Štaviše, potrebno je imati i opšti način za opi-sivanje i poređenje efikasnosti (ili složenosti) različitih algoritama. Obično serazmatraju:

• vremenska složenost algoritma;• prostorna (memorijska) složenost algoritma.

Vremenska i prostorna složenost mogu se razmatrati• u terminima konkretnog vremena/prostora utrošenog za neku konkretnu

ulaznu veličinu;• u terminima asimptotskog ponašanja vremena/prostora kada veličina

ulaza raste.Vreme izvršavanja programa može biti procenjeno ili izmereno za neke konkretneveličine ulazne vrednosti i neko konkretno izvršavanje. Veličina ulazne vrednostimože biti broj ulaznih elemenata koje treba obraditi, sam ulazni broj koji trebaobraditi, broj bitova potrebnih za zapisivanje ulaza koji treba obraditi, itd. Uvekje potrebno eksplicitno navesti u odnosu na koju veličinu se razmatra složenost.No, vreme izvršavanja programa može biti opisano opštije, u vidu funkcije kojazavisi od ulaznih argumenata.Često se algoritmi ne izvršavaju isto za sve ulaze istih veličina, pa je potrebnonaći način za opisivanje i poređenje efikasnosti različitih algoritama.

• Analiza najgoreg slučaja zasniva procenu složenosti algoritma na najgoremslučaju (na slučaju za koji se algoritam najduže izvršava — u analizi vre-menske složenosti, ili na slučaju za koji algoritam koristi najviše memorije

— u analizi prostorne složenosti). Ta procena može da bude varljiva, alipredstavlja dobar opšti način za poređenje efikasnosti algoritama.

• U nekim situacijama moguće je izvršiti analizu prosečnog slučaja i izraču-nati prosečno vreme izvršavanja algoritma, ali da bi se to uradilo, potrebnoje precizno poznavati prostor dopuštenih ulaznih vrednosti i verovatnoćuda se svaka dopuštena ulazna vrednost pojavi na ulazu programa. U sluča-jevima kada je bitna garancija efikasnosti svakog pojedinačnog izvršavanjaprograma procena prosečnog slučaja može biti varljiva.

1

Page 2: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

• Analiza najboljeg slučaja, naravno, nikada nema smisla.Nekada se analiza vrši tako da se proceni ukupno vreme potrebno da se izvršiodređen broj srodnih operacija. Taj oblik analize naziva se amortizovana analizai u tim situacijama nam nije bitna raspodela vremena na pojedinačne operacije,već samo zbirno vreme izvršavanja svih operacija.U nastavku će, ako nije rečeno drugačije, biti podrazumevana analiza najgoregslučaja.Složenost se obično procenjuje na osnovu izvornog koda programa. Savremenikompilatori izvršavaju različite napredne optimizacije i mašinski kôd koji seizvršava može biti prilično drugačiji od izvornog koda programa (na primer,kompilator može skupu operaciju množenja zameniti efikasnijim bitovskim op-eracijama, može naredbu koja se više puta izvršava u petlji izmestiti van petljei slično). Detalji koji se u izvornom kodu ne vide, poput pitanja da li seneki podatak nalazi u keš-memoriji ili je potrebno pristupati RAM-u, takođemogu veoma značajno da utiču na stvarno vreme izvršavanja programa. Savre-meni procesori podržavaju protočnu obradu i paralelno izvršavanje instrukcija,što takođe čini stvarno ponašanje programa drugačijim od klasičnog, sekvenci-jalnog modela koji se najčešće podrazumeva prilikom analize algoritama. Dakle,stvarno vreme izvršavanja programa zavisi od karakteristika konkretnog raču-nara na kom se program izvršava, ali i od karakteristika programskog prevodioca,pa i operativnog sistema na kom se program izvršava. Stvarno vreme izvršavanjazavisi i od konstanti sakrivenih u asimptotskim oznakama, međutim, asimptot-sko ponašanje obično prilično dobro određuje njegov red veličine (da li su upitanju mikrosekunde, milisekunde, sekunde, minuti, sati, dani, godine). Ako(pojednostavljeno) pretpostavimo da se svaka instrukcija na računaru izvršavaza jednu nanosekundu (10−9𝑠), a da broj instrukcija zavisi od veličine ulaza 𝑛na osnovu funkcije 𝑓(𝑛), tada je vreme potrebno da se algoritam izvrši dat usledećim tabelama.Algoritmi čija je složenost odozgo ograničena polinomijalnim funkcijama sma-traju se efikasnim.

𝑛/𝑓(𝑛) log 𝑛 √𝑛 𝑛 𝑛 log 𝑛 𝑛2 𝑛3

10 0,003 𝜇𝑠 0,003 𝜇𝑠 0,01 𝜇𝑠 0,033 𝜇𝑠 0,1 𝜇𝑠 1 𝜇𝑠100 0,007 𝜇𝑠 0, 010 𝜇𝑠 0,1 𝜇𝑠 0,644 𝜇𝑠 10 𝜇𝑠 1 𝑚𝑠1,000 0,010 𝜇𝑠 0, 032 𝜇𝑠 1,0 𝜇𝑠 9,966 𝜇𝑠 1 𝑚𝑠 1 𝑠10,000 0,013 𝜇𝑠 0, 1 𝜇𝑠 10 𝜇𝑠 130 𝜇𝑠 0,1 𝑠 16,7 𝑚𝑖𝑛100,000 0,017 𝜇𝑠 0, 316 𝜇𝑠 100 𝜇𝑠 1,67 𝑚𝑠 10 𝑠 11,57 𝑑𝑎𝑛1,000,000 0,020 𝜇𝑠 1 𝜇𝑠 1 𝑚𝑠 19,93 𝑚𝑠 16,7 𝑚𝑖𝑛 31,7 𝑔𝑜𝑑10,000,000 0,023 𝜇𝑠 3,16 𝜇𝑠 10 𝑚𝑠 0,23 𝑠 1,16 𝑑𝑎𝑛 3 × 105 𝑔𝑜𝑑100,000,000 0,027 𝜇𝑠 10 𝜇𝑠 0,1 𝑠 2,66 𝑠 115,7 𝑑𝑎𝑛1,000,000,000 0,030 𝜇𝑠 31,62 𝜇𝑠 1 𝑠 29,9 𝑠 31,7 𝑔𝑜𝑑

2

Page 3: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Algoritmi čija je složenost ograničena odozdo eksponencijalnom ili faktorijel-skom funkcijom se smatraju neefikasnim.

𝑛/𝑓(𝑛) 2𝑛 𝑛!10 1 𝜇𝑠 3,63 𝑚𝑠20 1 𝑚𝑠 77,1 𝑔𝑜𝑑30 1 𝑠 8, 4 × 1015 𝑔𝑜𝑑40 18,3 𝑚𝑖𝑛50 13 𝑑𝑎𝑛100 4 × 1013 𝑔𝑜𝑑

Možemo postaviti i pitanje koja se dimenzija ulaza se otprilike može obraditi zaodređeno vreme. Odgovor je dat u narednoj tabeli.

𝑡 𝑛 𝑛 log 𝑛 𝑛2 𝑛3 2𝑛 𝑛!1𝑚𝑠 106 63, 000 1, 000 100 20 910𝑚𝑠 10 ⋅ 106 530, 000 3, 200 215 23 10100𝑚𝑠 100 ⋅ 106 4, 5 ⋅ 106 10, 000 465 27 111𝑠 109 40 ⋅ 106 32, 000 1, 000 30 121𝑚𝑖𝑛 60 ⋅ 109 1, 9 ⋅ 109 245, 000 3, 900 36 14

Gornja granica složenosti se obično izražava korišćenjem 𝑂-notacije koju ste većizučavali u uvodnim kursevima programiranja. Podsetimo se.Definicija: Ako postoje pozitivna realna konstanta 𝑐 i prirodan broj 𝑛0 takvida za funkcije 𝑓 i 𝑔 nad prirodnim brojevima važi 𝑓(𝑛) ≤ 𝑐 ⋅𝑔(𝑛) za sve prirodnebrojeve 𝑛 veće od 𝑛0 onda pišemo 𝑓(𝑛) = 𝑂(𝑔(𝑛)) i čitamo „𝑓 je veliko ,o‘ od𝑔“.U nekim slučajevima koristimo i oznaku Θ koja nam ne daje samo gornju granicu,već precizno opisuje asimptotsko ponašanje.Definicija: Ako postoje pozitivne realne konstante 𝑐1 i 𝑐2 i prirodan broj 𝑛0takvi da za funkcije 𝑓 i 𝑔 nad prirodnim brojevima važi 𝑐1⋅𝑔(𝑛) ≤ 𝑓(𝑛) ≤ 𝑐2⋅𝑔(𝑛)za sve prirodne brojeve 𝑛 veće od 𝑛0, onda pišemo 𝑓(𝑛) = Θ(𝑔(𝑛)) i čitamo „𝑓je veliko ‘teta’ od 𝑔“.Navedimo karakteristike ovih osnovnih klasa složenosti.

• 𝑂(log 𝑛) - izuzetno efikasno, npr. binarna pretraga;• 𝑂(√𝑛) - “logaritam za one sa jeftinijim ulaznicama” - nemamo najbolja

mesta, ali ipak možemo da gledamo utakicu, npr. ispitivanje da li je brojprost, faktorizacija;

• 𝑂(𝑛) - optimalno, kada je za rešenje potrebno pogledati ceo ulaz, npr.minimum/maksimum;

3

Page 4: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

• 𝑂(𝑛 log 𝑛) - “linearni algoritam za one sa jeftinijim ulaznicama”, algoritmizasnovani na dekompoziciji, sortiranju, korišćenju struktura podataka salogaritamskim vremenom pristupa, npr. sortiranje objedinjavanjem;

• 𝑂(𝑛2) - ugnežđene petlje, npr. sortiranje selekcijom;• 𝑂(𝑛3) - višestruko ugnežđene petlje, npr. množenje matrica;• 𝑂(2𝑛) - ispitivanje svih podskupova;• 𝑂(𝑛! ) - ispitivanje svih permutacija.

Sumiranje

Tokom analize algoritama često imamo potrebu da izračunamo određene kon-ačne sume. Sa njima ste se sretali u srednjoj školi i kursevima diskretne matem-atike. Rezimirajmo ih kroz nekoliko najznačajnijih primera.

Aritmetički niz

Gausu se pripisuje da je još kao dete izračunao da je

1 + 2 + … + 𝑛 =𝑛

∑𝑘=0

𝑘 = 𝑛(𝑛 + 1)2 .

Zaista, u ovom zbiru se krije 𝑛/2 parova čiji je zbir 𝑛 + 1 (ovo, naravno, važisamo kada je 𝑛 paran broj, ali nam daje odličnu intuiciju koja nam pomaže daovu formulu lako zapamtimo). Preciznije, ako označimo taj zbir sa 𝑆 onda je2𝑆 = 𝑆 + 𝑆 = (1 + 2 + … + 𝑛) + (𝑛 + (𝑛 − 1) + … + 1) = 𝑛 ⋅ (𝑛 + 1).Nekada slika govori više od reči.n

++++*+++**++ n+1***+****

Na osnovu prethodnog jednostavno se izvodi da je zbir prvih 𝑛 članova arit-metičkog niza čiji je prvi član 𝑎, a razlika između svaka dva susedna članajednaka 𝑟 jednaka

𝑎 + (𝑎 + 𝑟) + (𝑎 + 2𝑟) + … + (𝑎 + (𝑛 − 1) ⋅ 𝑟) =𝑛−1∑𝑘=0

(𝑎 + 𝑘 ⋅ 𝑟) = 𝑛 ⋅ 𝑎 + 𝑟𝑛(𝑛 − 1)2 .

Intuicija nam opet govori da se ovde krije 𝑛2 parova čiji je zbir 𝑎0 + 𝑎𝑛−1, što

opet dovodi do formule 𝑛2 (2𝑎 + (𝑛 − 1) ⋅ 𝑟).

4

Page 5: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Geometrijski niz i red

Izvedimo formulu za zbir prvih 𝑛 članova geometrijskog niza kome je prvi član𝑎 a količnik između svaka dva člana 𝑞 ≠ 1. Obeležimo traženu sumu sa 𝑆.

𝑆 = 𝑎 + 𝑎 ⋅ 𝑞1 + 𝑎 ⋅ 𝑞2 + … + 𝑎 ⋅ 𝑞𝑛−2 + 𝑎 ⋅ 𝑞𝑛−1 =𝑛−1∑𝑘=0

𝑎 ⋅ 𝑞𝑘

Ako levu i desnu stranu prethodne jednakosti pomnožimo sa 1 − 𝑞 dobijamojednakost:

𝑆⋅(1−𝑞) = 𝑎⋅(1−𝑞)+𝑎⋅𝑞⋅(1−𝑞)+𝑎⋅𝑞2⋅(1−𝑞)+…+𝑎⋅𝑞𝑛−2⋅(1−𝑞)+𝑎⋅𝑞𝑛−1⋅(1−𝑞)

Izvršimo množenja na desnoj strani jednakosti:

𝑆⋅(1−𝑞) = 𝑎−𝑎⋅𝑞+𝑎⋅𝑞−𝑎⋅𝑞2+𝑎⋅𝑞2−𝑎⋅𝑞3+…+𝑎⋅𝑞𝑛−2−𝑎⋅𝑞𝑛−1+𝑎⋅𝑞𝑛−1−𝑎⋅𝑞𝑛

Sređivanjem poslednjeg izraza dobijamo 𝑆 ⋅ (1 − 𝑞) = 𝑎 − 𝑎 ⋅ 𝑞𝑛. Prema tome,pošto je 𝑞 ≠ 1, važi

𝑆 = 𝑎 ⋅ 1 − 𝑞𝑛

1 − 𝑞 .

Za |𝑞| < 1 geometrijski red konvergira i suma mu je 𝑎1−𝑞 .

Nama će najčešće biti korisni slučajevi 𝑞 = 2 i 𝑞 = 1/2.Na osnovu prethodne formule, za 𝑎 = 1 i 𝑞 = 2, važi da je 1+2+…+2𝑛−1 = 2𝑛−1. Ova formula ima interesantno tumačenje. Suma sa leve strane predstavljaukupan broj čvorova na prvih 𝑛 nivoa potpunog binarnog drveta, dok je izraz sadesne strane za jedan manji od broja čvorova na narednom nivou 𝑛 + 1. Dakle,na svakom narednom nivou binarnog drveta ima jedan čvor više nego što ječvorova na svim prethodnim nivoima drveta.

Slika 1: Broj čvorova na najnižem nivou binarnog drveta za jedan je veći odukupnog broja čvorova na prethdnim nivoima

Za 𝑎 = 1 i 𝑞 = 1/2 dobijamo da je

1 + 1/2 + … + (1/2)𝑛−1 = 1 − (1/2)𝑛

1 − 1/2 = 2 − (1/2)𝑛−1.

5

Page 6: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Sa porastom 𝑛 ova vrednost se približava vrednosti 2 (svakako je njomeograničena odozgo).Opet slika govori više od reči.

Slika 2: Zbir geometrijskog reda za 𝑎 = 1/2, 𝑞 = 1/2

Stepene sume

Prikažimo kako možemo izračunati sumu kvadrata prvih 𝑛 prirodnih brojeva.Važi da je

(𝑘 + 1)3 − 𝑘3 = 𝑘3 + 3𝑘2 + 3𝑘 + 1 − 𝑘3 = 3𝑘2 + 3𝑘 + 1.

Zato je

23 − 13 = 3 ⋅ 12 + 3 ⋅ 1 + 133 − 23 = 3 ⋅ 22 + 3 ⋅ 2 + 1

…(𝑛 + 1)3 − 𝑛3 = 3 ⋅ 𝑛2 + 3 ⋅ 𝑛 + 1

Sabiranjem prethodnih jednakosti dobijamo

(𝑛 + 1)3 − 1 = 3 ⋅ (12 + … + 𝑛2) + 3 ⋅ (1 + … + 𝑛) + (1 + … + 1)

tj.

3 ⋅𝑛

∑𝑘=1

𝑘2 = (𝑛 + 1)3 − 1 − 3𝑛

∑𝑘=1

𝑘 −𝑛

∑𝑘=1

1.

Na osnovu ranije izvedenih formula za zbir aritmetičkog niza, sledi da je𝑛

∑𝑘=1

𝑘2 = 13 ⋅ (𝑛3 + 3𝑛2 + 3𝑛 − 3𝑛(𝑛 + 1)

2 − 𝑛) = 𝑛 ⋅ (2𝑛 + 1) ⋅ (𝑛 + 1)6

Dakle, suma kvadrata prvih 𝑛 prirodnih brojeva se asimptotski ponaša kao 𝑛33 .

6

Page 7: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Potpuno analogno, sumiranjem izraza (𝑘 + 1)4 − 𝑘4 od 1 do 𝑛 i primenom dosada izvedenih formula može se pokazati da je

13 + 23 + … + 𝑛3 =𝑛

∑𝑘=1

𝑘3 = (𝑛(𝑛 + 1))2

4 .

Ovo tvrđenje, poznato kao Nikomahova teorema pokazuje da je zbir prvih 𝑛kubova jednak kvadratu zbira prvih 𝑛 prirodnih brojeva i asimptotski se ponašakao 𝑛4

4 .Pošto će nas u analizi algoritama najčešće zanimati samo asimptotsko ponašanjefunkcija, najvažnije je zapamtiti da se suma prvih 𝑛 𝑝-tih stepena asimptotskiponaša kao 𝑛𝑝+1

𝑝+1 .

Primena diferencijalnog i integralnog računa u izračunavanju i ocenisuma

Za izračunavanje i ocenu suma mogu se koristiti i izvodi i integrali. Primetimoda je neodređeni integral funkcije 𝑥𝑝 funkcija 𝑥𝑝+1

𝑝+1 , a da se zbir prvih 𝑛 𝑝-tihstepena asimptotski ponaša upravo kao 𝑛𝑝+1

𝑝+1 . To nije slučajnost. Razmotrimomonotono rastuću funkciju 𝑓 (takva je funkcija 𝑓(𝑥) = 𝑥𝑝) na domenu 𝑥 ≥ 0.Suma ∑𝑛−1

𝑘=0 𝑓(𝑘) se vizuelno može predstaviti kao površina 𝑛 pravougaonika(svakome je širina 1, a visina 𝑘-tog je 𝑓(𝑘)). Na slici je prikazana suma prvih 25potpunih kvadrata. Sa slike je prilično očigledno da je ta suma (zbir površinapravougaonika) veoma bliska površini ispod krive 𝑓(𝑥) = 𝑥2 koja se može izraču-nati (tj. čije se asimptotsko ponašanje može proceniti) primenom određenihintegrala.

Slika 3: Procena sume određenim integralom

Ilustrujmo ovo i malo preciznije. Površina ispod krive 𝑓(𝑥) za 𝑘 ≤ 𝑥 ≤ 𝑘 + 1jednaka je određenom integralu ∫𝑘+1

𝑘 𝑓(𝑥) 𝑑𝑥. Pošto je funkcija rastuća, ta

7

Page 8: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

površina je veća od površine pravougaonika čija je visina 𝑓(𝑘) i širina 1, amanja od površine pravougaonika čija je visina 𝑓(𝑘 + 1) i širina 1 tj. važi

𝑓(𝑘) ≤ ∫𝑘+1

𝑘𝑓(𝑥) 𝑑𝑥 ≤ 𝑓(𝑘 + 1).

Zato je𝑛−1∑𝑘=0

𝑓(𝑘) ≤ ∫𝑛

0𝑓(𝑥) 𝑑𝑥 ≤

𝑛−1∑𝑘=0

𝑓(𝑘 + 1).

Gornju granicu sume možemo dobiti iz prve nejednakosti. Pošto je

𝑛−1∑𝑘=0

𝑓(𝑘 + 1) =𝑛−1∑𝑘=0

𝑓(𝑘) − 𝑓(0) + 𝑓(𝑛),

iz druge nejednakosti sledi i donja granica.

∫𝑛

0𝑓(𝑥) 𝑑𝑥 + 𝑓(0) − 𝑓(𝑛) ≤

𝑛−1∑𝑘=0

𝑓(𝑘) ≤ ∫𝑛

0𝑓(𝑥) 𝑑𝑥.

Slučaj kada je 𝑓(𝑥) monotono opadajuća funkcija za 𝑥 ≥ 0 se obrađuje analogno(samo je potrebno umesto ≤ koristiti ≥).Na primer, ponašanje sume

𝑛−1∑𝑘=0

𝑘𝑎𝑘 = 𝑎 + 2𝑎2 + 3𝑎3 + … + (𝑛 − 1)𝑎𝑛−1,

možemo proceniti izračunavanjem određenog integrala

∫𝑛

0𝑥𝑎𝑥 𝑑𝑥.

On se jednostavno može izračunati parcijalnom integracijom za 𝑢 = 𝑥 (pa je𝑑𝑢 = 𝑑𝑥) i 𝑑𝑣 = 𝑎𝑥 𝑑𝑥, odakle je 𝑣 = 𝑎𝑥

ln 𝑎 . Zato je

∫𝑛

0𝑥𝑎𝑥 𝑑𝑥 = ∫

𝑥

0𝑢 𝑑𝑣 = 𝑢𝑣|𝑛0 −∫

𝑛

0𝑣 𝑑𝑢 = 𝑛𝑎𝑛

ln 𝑎 −∫𝑛0 𝑎𝑥 𝑑𝑥

ln 𝑎 = 𝑛𝑎𝑛

ln 𝑎 −(𝑎𝑛 − 1)ln2 𝑎 ,

pa se ova funkcija asimptotski ponaša kao 𝑛𝑎𝑛.Ovu sumu je moguće izračunati i egzaktno, primenom diferenciranja. Naime,važi da je

𝑛−1∑𝑘=0

𝑥𝑘 = 1 + 𝑥 + … + 𝑥𝑛−1 = 𝑥𝑛 − 1𝑥 − 1 .

8

Page 9: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Diferenciranjem obe strane po 𝑥 dobijamo

1 + 2𝑥 + 3𝑥2 + … + (𝑛 − 1)𝑥𝑛−2 = 𝑛𝑥𝑛−1(𝑥 − 1) − (𝑥𝑛 − 1)(𝑥 − 1)2 .

Množenjem sa 𝑥 dobijamo

𝑛−1∑𝑘=0

𝑘𝑥𝑘 = 𝑥 + 2𝑥2 + 3𝑥3 + … + (𝑛 − 1)𝑥𝑛−1 = 𝑥𝑛𝑥𝑛−1(𝑥 − 1) − (𝑥𝑛 − 1)(𝑥 − 1)2 .

Na primer, za 𝑥 = 2 dobijamo da je ∑𝑛−1𝑘=0 𝑘2𝑘 = (𝑛 − 2) ⋅ 2𝑛 + 2.

Rekurentne jednačine

Složenost rekurzivnih funkcija se često može opisati rekurentnim jednačinama.Rešenje rekurentne jednačine je funkcija 𝑇 (𝑛) i za rešenje ćemo reći da je uzatvorenom obliku ako je izraženo kao elementarna funkcija po 𝑛 (i ne uključujesa desne strane ponovno referisanje na funkciju 𝑇 ). Često ćemo se zadovoljiti daumesto potpuno preciznog rešenja znamo samo njegovo asimptotsko ponašanje.Podsetimo se nekoliko najčešćih rekurentnih jednačina.U prvoj grupi se problem svodi na problem dimenzije koja je tačno za jedanmanja od dimenzije polaznog problema.

• Jednačina: 𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑂(1), 𝑇 (0) = 𝑂(1). Primer: Traženjeminimuma niza. Rešenje: 𝑂(𝑛).

• Jednačina: 𝑇 (𝑛) = 𝑇 (𝑛−1)+𝑂(log 𝑛), 𝑇 (0) = 𝑂(1). Primer: Formiranjebalansiranog binarnog drveta. Rešenje: 𝑂(𝑛 log 𝑛).

• Jednačina: 𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑂(𝑛), 𝑇 (0) = 𝑂(1). Primer: Sortiranjeselekcijom. Rešenje: 𝑂(𝑛2).

U drugoj grupi se problem svodi na dva (ili više) problema čija je dimenzijaza jedan ili dva manja od dimenzije polaznog problema. To obično dovodi doeksponencijalne složenosti.

• Jednačina: 𝑇 (𝑛) = 2𝑇 (𝑛 − 1) + 𝑂(1), 𝑇 (0) = 𝑂(1). Primer: Hanojskekule. Rešenje: 𝑂(2𝑛)

• Jednačina: 𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑇 (𝑛 − 2) + 𝑂(1), 𝑇 (0) = 𝑂(1). Primer:Fibonačijevi brojevi. Rešenje: 𝑂(2𝑛)

U narednoj grupi se problem svodi na jedan (ili više) potproblema koji su znača-jno manje dimenzije od polaznog (obično su bar duplo manji). Ovo dovodi dopolinomijalne složenosti, pa često i do veoma efikasnih rešenja.

• Jednačina: 𝑇 (𝑛) = 𝑇 (𝑛/2) + 𝑂(1), 𝑇 (0) = 𝑂(1). Primer: Binarna pre-traga sortiranog niza. Rešenje: 𝑂(log 𝑛).

• Jednačina: 𝑇 (𝑛) = 𝑇 (𝑛/2) + 𝑂(𝑛), 𝑇 (0) = 𝑂(1). Primer: Pronalaženjemedijane (središnjeg elementa) niza. Rešenje: 𝑂(𝑛).

9

Page 10: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

• Jednačina: 𝑇 (𝑛) = 2𝑇 (𝑛/2) + 𝑂(1), 𝑇 (0) = 𝑂(1). Primer: Obilazakpotpunog binarnog drveta. Rešenje: 𝑂(𝑛).

• Jednačina: 𝑇 (𝑛) = 2𝑇 (𝑛/2) + 𝑂(𝑛), 𝑇 (0) = 𝑂(1). Primer: Sortiranjeobjedinjavanjem. Rešenje: 𝑂(𝑛 log 𝑛).

Ako su granice u samim jednačinama egzaktne, skoro u svim prethodno nabro-janim jednačinama dato rešenje nije samo gornje ograničenje, već je asimptotskiegzaktno. Na primer, rešenje jednačine 𝑇 (𝑛) = 2𝑇 (𝑛/2) + Θ(𝑛), 𝑇 (1) = Θ(1)ima rešenje 𝑇 (𝑛) = Θ(𝑛 log 𝑛). Izuzetak je primer Fibonačijevog niza gde pon-ašanje jeste eksponencijalno, ali osnova nije 2, već zlatni presek (1 +

√5)/2.

Potpuno formalno i precizno izvođenje i dokazivanje asimptotskog ponašanjarešenja ovih jednačina neće biti u direktnom fokusu ovog kursa, jer je obrađivanou sklopu kurseva diskretnih struktura i programiranja 2. Mnogo važnije je stećineku intuiciju zašto su rešenja baš takva kakva jesu (uz određenu dozu rezerve,jer ovakve grube aproksimacije nekada mogu dovesti do greške). Jedan načinda se to uradi je da se krene sa “odmotavanjem” rekurzije i da se vidi do čegase dolazi.Na primer, kod jednačine 𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑂(1) i 𝑇 (0) = 𝑂(1), nakon odmo-tavanja dobijamo da važi

𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑂(1)= 𝑇 (𝑛 − 2) + 𝑂(1) + 𝑂(1)= 𝑇 (𝑛 − 3) + 𝑂(1) + 𝑂(1) + 𝑂(1)= …= 𝑇 (0) + 𝑛 ⋅ 𝑂(1)= 𝑂(1) + 𝑛 ⋅ 𝑂(1)= 𝑂(𝑛).

Slika 4: Drvo poziva u slučaju 𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑂(1), 𝑇 (0) = 𝑂(1) za 𝑛 = 4.Pravougaonik označava dimenziju ulaza, a elipsa količinu posla koji se obavljau tom čvoru.

Kod jednačine 𝑇 (𝑛) = 𝑇 (𝑛−1)+𝑂(𝑛), 𝑇 (0) = 𝑂(1) slično dobijamo 𝑛 sabirakakoji su svi 𝑂(𝑛) tako da je ukupna suma 𝑂(𝑛2). Postavlja se pitanje da li je ova

10

Page 11: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

granica egzaktna tj. da li je moguće da je složenost manja od izvedenog gornjegograničenja. Pretpostavimo da je 𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑐𝑛 i da je 𝑇 (0) = 𝑂(1).Tada je

𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑐𝑛= 𝑇 (𝑛 − 2) + 𝑐(𝑛 − 1) + 𝑐𝑛= …= 𝑇 (0) + 𝑐(1 + … + 𝑛)= 𝑂(1) + 𝑐𝑛(𝑛 + 1)/2,

tako da je 𝑇 (𝑛) = Θ(𝑛2).

Slika 5: Drvo poziva u slučaju 𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑂(𝑛), 𝑇 (0) = 𝑂(1) za 𝑛 = 4

Pokažimo još i šta se dešava sa jednačinom 𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑐 log 𝑛, 𝑇 (0) =𝑂(1). Odmotavanjem dobijamo

𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑐 log 𝑛= 𝑇 (𝑛 − 2) + 𝑐 log (𝑛 − 1) + 𝑐 log(𝑛)= …= 𝑂(1) + 𝑐(log 1 + … + log 𝑛).

Pošto je logaritam rastuća funkcija, svaki od 𝑛 članova ovog zbira ograničen jeodozgo vrednošću log 𝑛. Zato je zbir 𝑂(𝑛 log 𝑛). Dokažimo da ovo ograničenjenije previše grubo. Važi da je

log 1 + log 2 + … + log 𝑛 ≥ log (𝑛/2) + log (𝑛/2 + 1) + … + log 𝑛,

jer je prvih 𝑛/2 članova koji su iz sume izostavljeni sigurno nenegativni. Poštoje logaritam rastuća funkcija (za osnovu veću od 1), svi sabirci u ovom zbiru suveći ili jednaki log (𝑛/2), pa je

log (𝑛/2) + log (𝑛/2 + 1) + … + log 𝑛 ≥ log (𝑛/2) + log (𝑛/2) + … + log (𝑛/2).

Zbir na desnoj strani ima 𝑛/2 istih sabiraka i jednak je (𝑛/2) ⋅ log (𝑛/2). Stogaje početni zbir logaritama ograničen i odozdo i odozgo funkcijama koje su

11

Page 12: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Θ(𝑛 log 𝑛), pa je i sam Θ(𝑛 log 𝑛). Još jedan način da se ovo pokaže koji se čestosreće u literaturi je sledeći. Zbir logaritama, jednak je logaritmu proizvoda, pazapravo ovde računamo vrednost log 1 ⋅ … ⋅ 𝑛 = log 𝑛!. Po Stirlingovoj formuli𝑛! se ponaša kao

√2𝜋𝑛( 𝑛𝑒 )𝑛. Zato se log 𝑛! ponaša kao 𝑛 log 𝑛 − 𝑛 + 𝑂(log 𝑛),

pa je ukupan zbir Θ(𝑛 log 𝑛).Možemo uočiti neke pravilnosti. Kod svih jednačina oblika 𝑇 (𝑛) = 𝑇 (𝑛 − 1) +𝑓(𝑛), 𝑇 (0) = 𝑐, nakon odmotavanja dobijamo da je 𝑇 (𝑛) = 𝑐+𝑓(1)+𝑓(2)+…+𝑓(𝑛), tako da se određivanje asimptotskog ponašanja svodi na sumiranje. Zbir𝑛 sabiraka reda 1 + 2 + … + 𝑛 ima vrednost 𝑛(𝑛 + 1)/2, koja je reda Θ(𝑛2). Toje samo duplo manje od vrednosti zbira 𝑛+…+𝑛, koji se sastoji od 𝑛 sabiraka iima vrednost 𝑛2. Zbir 𝑛 sabiraka log 1 + … + log 𝑛 ima vrednost koja se ponašakao 𝑛 log 𝑛 − 𝑛, što je asimptotski isto kao vrednost zbira log 𝑛 + … + log 𝑛koji ima 𝑛 sabiraka i vrednost 𝑛 log 𝑛. Slično, zbir 12 + … + 𝑛2 ima vrednost𝑛(𝑛+1)(2𝑛+1)/6, što je Θ(𝑛3) i samo je oko tri puta manje od zbira 𝑛2 +… 𝑛2

koji ima 𝑛 sabiraka i vrednost 𝑛3. Iako ovakve generalizacije prete da buduneprecizne, sa malom dozom rezerve se može proceniti da algoritmi u kojima se𝑛 puta primenjuje neka operacija složenosti Θ(𝑓(𝑘)) imaju složenost Θ(𝑛⋅𝑓(𝑛)),čak i kada se operacija u svakom koraku primenjuje nad podacima koji su se za𝑂(1) povećali u odnosu na prethodni korak i samo u krajnjoj instanci imamoΘ(𝑛) podataka.Odmotavanjem jednačine 𝑇 (𝑛) = 2𝑇 (𝑛 − 1) + 𝑂(1), 𝑇 (0) = 𝑂(1) dobijamo

𝑇 (𝑛) = 2𝑇 (𝑛 − 1) + 𝑂(1)= 2(2𝑇 (𝑛 − 2) + 𝑂(1)) + 𝑂(1) = 4𝑇 (𝑛 − 2) + 2𝑂(1) + 𝑂(1)= 4(2𝑇 (𝑛 − 3) + 𝑂(1)) + 2𝑂(1) + 𝑂(1)= 8𝑇 (𝑛 − 3) + 4𝑂(1) + 2𝑂(1) + 𝑂(1)= …= 2𝑛𝑇 (0) + (2𝑛−1 + … + 2 + 1) ⋅ 𝑂(1)= 2𝑛 ⋅ 𝑂(1) + (2𝑛 − 1) ⋅ 𝑂(1) = 𝑂(2𝑛).

Dakle, iako se u svakom rekurzivnom pozivu radi malo posla, rekurzivnih pozivaima eksponencijalno mnogo, što dovodi do izrazito neefikasnog algoritma.Funkcije koje zadovoljavaju jednačinu 𝑇 (𝑛) = 2𝑇 (𝑛 − 1) + 𝑂(𝑛), 𝑇 (0) = 𝑂(1)takođe pokazuju eksponencijalnu složenost.Odmotavanjem jednačine 𝑇 (𝑛) = 2𝑇 (𝑛 − 1) + 𝑐 ⋅ 𝑛, 𝑇 (0) = 𝑂(1) dobijamo

𝑇 (𝑛) = 2𝑇 (𝑛 − 1) + 𝑐 ⋅ 𝑛= 2(2𝑇 (𝑛 − 2) + 𝑐 ⋅ (𝑛 − 1)) + 𝑐 ⋅ 𝑛 = 4𝑇 (𝑛 − 2) + 𝑐 ⋅ (2(𝑛 − 1) + 𝑛)= 4(2𝑇 (𝑛 − 3) + 𝑐 ⋅ (𝑛 − 2)) + 𝑐 ⋅ (2(𝑛 − 1) + 𝑛)= 8𝑇 (𝑛 − 3) + 𝑐 ⋅ (4(𝑛 − 2) + 2(𝑛 − 1) + 𝑛)= …= 2𝑛𝑇 (0) + 𝑐(2𝑛−1 ⋅ 1 + 2𝑛−2 ⋅ 2 + … + 2(𝑛 − 1) + 𝑛) ⋅ 𝑂(1)= 2𝑛 ⋅ 𝑂(1) + Σ𝑛

𝑘=02𝑘(𝑛 − 𝑘).

12

Page 13: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Slika 6: Drvo poziva u slučaju 𝑇 (𝑛) = 2𝑇 (𝑛 − 1) + 𝑂(1), 𝑇 (0) = 𝑂(1) za 𝑛 = 5

Slika 7: Drvo poziva u slučaju 𝑇 (𝑛) = 2𝑇 (𝑛 − 1) + 𝑂(𝑛), 𝑇 (0) = 𝑂(1) za 𝑛 = 5

13

Page 14: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Korišćenjem ranije izvedenih formula možemo jednostavno izračunati i sumu𝑛

∑𝑘=0

2𝑘(𝑛 − 𝑘) = 𝑛 + 2(𝑛 − 1) + 22(𝑛 − 2) + … + 2𝑛−1 ⋅ 1.

Važi da je𝑛

∑𝑘=0

2𝑘(𝑛 − 𝑘) = 𝑛𝑛

∑𝑘=0

2𝑘 −𝑛

∑𝑘=0

𝑘2𝑘

= 𝑛(2𝑛+1 − 1) − ((𝑛 − 1)2𝑛+1 + 2)= 2𝑛+1 − 𝑛 − 2

Zato je 𝑇 (𝑛) = 2𝑛 ⋅ 𝑂(1) + 2𝑛+1 − 𝑛 − 2. Dakle, i u ovom slučaju funkcijapokazuje eksponencijalno ponašanje 𝑂(2𝑛).

Master teorema

Jednačine zasnovane na dekompoziciji problema na nekoliko manjih potprob-lema koje su oblika 𝑇 (𝑛) = 𝑎𝑇 (𝑛/𝑏) + 𝑂(𝑛𝑐), 𝑇 (0) = 𝑂(1) se rešavaju naosnovu master teoreme.Teorema: Rešenje rekurentne relacije 𝑇 (𝑛) = 𝑎𝑇 (𝑛/𝑏) + 𝑐𝑛𝑘, gde su 𝑎 i 𝑏celobrojne konstante takve da važi 𝑎 ≥ 1 i 𝑏 ≥ 1, i 𝑐 i 𝑘 su pozitivne realnekonstante je

𝑇 (𝑛) =⎧{⎨{⎩

Θ(𝑛log𝑏 𝑎) , ako je log𝑏 𝑎 > 𝑘Θ(𝑛𝑘 log 𝑛) , ako je log𝑏 𝑎 = 𝑘Θ(𝑛𝑘) , ako je log𝑏 𝑎 < 𝑘

Nećemo davati dokaz ove teoreme, ali pokušajmo opet da damo neko intuitivnojasno objašnjenje.U prvom slučaju se dobija drvo rekurzivnih poziva čiji broj čvorova dominiraposlom koji se radi u svakom čvoru. Razmotrimo, na primer, jednačinu 𝑇 (𝑛) =2 ⋅ 𝑇 (𝑛/2) + 𝑂(1), 𝑇 (1) = 𝑂(1). Drvo će sadržati 𝑂(𝑛) čvorova, a u svakomčvoru će se vršiti posao koji zahteva 𝑂(1) operacija. Odmotavanjem rekurentnejednačine, dobijamo

𝑇 (𝑛) = 2 ⋅ 𝑇 (𝑛/2) + 𝑂(1)= 4 ⋅ 𝑇 (𝑛/4) + 2 ⋅ 𝑂(1) + 𝑂(1)= 8 ⋅ 𝑇 (𝑛/8) + 4 ⋅ 𝑂(1) + 2 ⋅ 𝑂(1) + 𝑂(1)= 2𝑘 ⋅ 𝑇 (𝑛/2𝑘) + (2𝑘−1 + … + 2 + 1) ⋅ 𝑂(1).

Ako je 𝑛 = 2𝑘 dobijamo da je 𝑛/2𝑘 = 1, pa pošto je na osnovu formule za zbirgeometrijskog niza 2𝑘−1 + … + 2 + 1 = 2𝑘 − 1, složenost je Θ(𝑛). I kada 𝑛

14

Page 15: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Slika 8: Drvo poziva u slučaju 𝑇 (𝑛) = 2𝑇 (𝑛/2) + 𝑂(1), 𝑇 (1) = 𝑂(1) za 𝑛 = 8

nije stepen dvojke, dobija se isto asimptotsko ponašanje (što se može dokazatiograničavanjem odozgo i odozdo stepenima dvojke).U drugom slučaju su broj čvorova i posao koji se radi na neki način uravnoteženi.Razmotrimo, na primer, jednačinu 𝑇 (𝑛) = 2⋅𝑇 (𝑛/2)+𝑐⋅𝑛, 𝑇 (1) = 𝑂(1) i ponovopokušajmo da je odmotamo.

𝑇 (𝑛) = 2 ⋅ 𝑇 (𝑛/2) + 𝑐 ⋅ 𝑛= 2 ⋅ (2 ⋅ 𝑇 (𝑛/4) + 𝑐 ⋅ 𝑛/2) + 𝑐 ⋅ 𝑛= 4𝑇 (𝑛/4) + 𝑐 ⋅ 𝑛 + 𝑐 ⋅ 𝑛= 4(2𝑇 (𝑛/8) + 𝑐 ⋅ 𝑛/4) + 2 ⋅ 𝑐 ⋅ 𝑛= 8𝑇 (𝑛/8) + 3 ⋅ 𝑐 ⋅ 𝑛= …= 2𝑘 ⋅ 𝑇 (𝑛/2𝑘) + 𝑘 ⋅ 𝑐 ⋅ 𝑛.

Ako je 𝑛 = 2𝑘 posle 𝑘 = log2 𝑛 koraka 𝑛/2𝑘 će dostići vrednost 1 tako da ćezbir biti reda veličine 𝑛 ⋅ 𝑂(1) + log2 𝑛 ⋅ 𝑐 ⋅ 𝑛 = Θ(𝑛 log 𝑛). Isto važi i kada 𝑛 nijestepen dvojke.

Slika 9: Drvo poziva u slučaju 𝑇 (𝑛) = 2𝑇 (𝑛/2) + 𝑂(𝑛), 𝑇 (1) = 𝑂(1) za 𝑛 = 8

U trećem slučaju posao koji se radi u čvorovima dominira brojem čvorova. Raz-motrimo jednačinu 𝑇 (𝑛) = 𝑇 (𝑛/2) + 𝑐𝑛, 𝑇 (1) = 𝑂(1). Njenim odmotavanjem

15

Page 16: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

dobijamo da je

𝑇 (𝑛) = 𝑇 (𝑛/2) + 𝑐𝑛= 𝑇 (𝑛/4) + 𝑐𝑛/2 + 𝑐𝑛= 𝑇 (𝑛/8) + 𝑐𝑛/4 + 𝑐𝑛/2 + 𝑐𝑛= …= 𝑇 (𝑛/2𝑘) + 𝑐𝑛(1/2𝑘−1 + … + 1/2 + 1).

Ponovo, ako je 𝑛 = 2𝑘, tada je prvi član jednak 𝑂(1) i pošto je na osnovu formuleza zbir geometrijskog niza 1/2𝑘−1 + … + 1/2 + 1 = (1 − (1/2)𝑘)/(1 − (1/2)) =2 − 2/𝑛 zbir je jednak 𝑂(1) + 𝑐𝑛(2 − 2/𝑛) = Θ(𝑛).

Slika 10: Drvo poziva u slučaju 𝑇 (𝑛) = 𝑇 (𝑛/2) + 𝑂(𝑛), 𝑇 (1) = 𝑂(1) za 𝑛 = 8

Prokomentarišimo da se u nekim problemima dobijaju jednačine koje nisu bašu svakom rekurzivnom pozivu identične ovim navedenim. Na primer, prilikomanalize algoritma QuickSort, ako je pivot tačno na sredini niza, važi da je 𝑇 (𝑛) =2𝑇 (𝑛/2) + 𝑂(𝑛) i 𝑇 (1) = 𝑂(1). Kada bi se to stalno događalo, rešenje bibilo 𝑇 (𝑛) = 𝑂(𝑛 log 𝑛), međutim, verovatnoća da se to dogodi je strašno mala,jer u većini slučajeva pivot ne deli niz na dva dela potpuno iste dimenzije izato treba biti obazriv. Ako bi se desilo da pivot stalno završavao na jednomkraju niza, jednačina bi bila 𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑂(𝑛), 𝑇 (1) = 𝑂(1), što bidovelo do složenosti 𝑂(𝑛2), što i jeste složenost najgoreg slučaja. Analizomkoju ćemo prikazati kasnije se može utvrditi da je prosečna složenost 𝑂(𝑛 log 𝑛)tj. da iako pivot nije stalno na sredini, da je u dovoljnom procenu slučajevanegde blizu nje (recimo između 25% i 75% dužine niza). Slična analiza važii za problem pronalaženja medijane. Međutim, postoje i drugačiji slučajevi.Prilikom obilaska binarnog drveta, balansiranost nema uticaja. Naime, ako jedrvo potpuno, tada je jednačina 𝑇 (𝑛) = 2𝑇 (𝑛/2) + 𝑂(1), 𝑇 (1) = 𝑂(1), čije jerešenje 𝑂(𝑛). Međutim, čak i kada je drvo izdegenerisano u listu, jednačina je𝑇 (𝑛) = 𝑇 (𝑛 − 1) + 𝑂(1), 𝑇 (1) = 𝑂(1), čije je rešenje opet 𝑂(𝑛). Kakav god daje odnos broja čvorova u levom i desnom poddrvetu rešenje će biti 𝑂(𝑛). To semože opisati jednačinom 𝑇 (𝑛) = 𝑇 (𝑘) + 𝑇 (𝑛 − 𝑘 − 1) + 𝑂(1), 𝑇 (1) = 𝑂(1), za0 ≤ 𝑘 ≤ 𝑛 − 1, čije će rešenje biti 𝑂(𝑛), bez obzira na to kakvo se 𝑘 pojavljujeu raznim rekurzivnim pozivima.

16

Page 17: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Primeri analize složenosti najgoreg slučaja

Euklidov algoritam Problem: Proceniti složenost Euklidovog algoritma zaodređivanje NZD dva pozitivna prirodna broja.Razmotrimo prvo originalnu varijantu algoritma, u kojoj se koristi oduzimanje.

int nzd(int a, int b) {if (a > b)

return nzd(a-b, b);else if (b > a)

return nzd(a, b-a)else

return a;}

Zaustavljanje se obezbeđuje time što pri svakom rekurzivnom pozivu jedan odelemenata opada (jer su oba broja pozitivna i uvek se manji oduzima od većeg).Do kraja stiže u najviše 𝑎 + 𝑏 koraka (a često i manje). Najgori slučaj nastupaako je jedan od dva broja jednak 1, a drugi dosta veći od njega, jer se onda tajedinica oduzima (npr. ako je 𝑏 = 1, do kraja se stiže u 𝑎 − 1 koraka). Dakle,ukupna složenost je linearna tj. 𝑂(𝑎 + 𝑏).Razmotrimo sada varijantu zasnovanu na celobrojnom deljenju.

int nzd(int a, int b) {return b == 0 ? a : nzd(b, a % b);

}

Primetimo da se posle najviše jednog koraka osigurava da je 𝑎 > 𝑏 (jerse u svakom koraku par (𝑎, 𝑏) menja parom (𝑏, 𝑎 mod 𝑏), a uvek važi da je𝑎 mod 𝑏 < 𝑏). Posle bilo koje dve iteracije se od para (𝑎, 𝑏) dolazi do para(𝑎 mod 𝑏, 𝑏 mod (𝑎 mod 𝑏)) (naravno, pod pretpostavkom da je 𝑏 ≠ 0 i da je𝑎 mod 𝑏 ≠ 0). Dokažimo da je 𝑎 mod 𝑏 < 𝑎/2. Ako je 𝑏 ≤ 𝑎/2, tada je𝑎 mod 𝑏 < 𝑏 ≤ 𝑎/2. U suprotnom, za 𝑏 > 𝑎/2 važi da je 𝑎 mod 𝑏 = 𝑎 − 𝑏 < 𝑎/2.Zato se prvi argument posle svaka dva koraka smanji bar dvostruko. Dovrednosti 1 prvi argument stigne u logaritamskom broju koraka u odnosu naveći od polazna dva broja i tada drugi broj sigurno dostiže nulu (jer je strogomanji od prvog) i postupak se završava. Dakle, složenost je logaritamska uodnosu na veći od dva broja, što može da se zapiše i kao 𝑂(log(𝑎 + 𝑏)).Dakle, varijanta zasnovana na celobrojnom deljenju je neuporedivo efikasnija odvarijante zasnovana na oduzimanju.

QuickSort particionisanje Problem: Proceniti složenost narednog algo-ritma particionisanja niza dužine 𝑛 ≥ 1.

int m = 1, v = n-1;while (m <= v) {

17

Page 18: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

while (m <= v && a[m] <= a[0])m++;

while (m <= v && a[v] > a[0])v--;

if (m < v)swap(a[m], a[v]);

}swap(a[0], a[m-1]);

Jedna od najčešćih zabluda je da se složenost algoritama procenjuje samo naosnovu broja ugnežđenih petlji. Pošto se u ovom zadatku javlja petlja u petlji,neko bi naivno mogao da kaže da je složenost u ovom slučaju 𝑂(𝑛2), međutimto nije tačno. U ovom algoritmu se koriste dva pokazivača 𝑚 i 𝑣 koja praktičnokreću sa dva kraja niza i približavaju se jedan drugome, sve dok se ne susretnu.Jasno je, dakle, da je najveći broj koraka koji je moguće napraviti ograničendužinom niza tj. da je ukupna složenost 𝑂(𝑛). Ovaj algoritam spada u gruputzv. algoritama zasnovanih na tehnici dva pokazivača (engl. two pointer), kojise prepoznaju po tome što se dva indeksa niza (tzv. pokazivača) pomeraju krozniz, ali stalno u istom smeru (nijedan pokazivač se ne vraća nikada unazad).U svakom koraku petlje pomera se bar jedan od pokazivača. Bez obzira da liiteracija prestaje kada pokazivači stignu do kraja niza ili dok se ne susretnu,vreme izvršavanja takvih algoritama je linearno tj. 𝑂(𝑛), gde je 𝑛 dužina niza.

Analiza prosečne složenosti

Analiza prosečne složenosti algoritma QuickSort Upečatljivo svojstvoalgoritma Quicksort je efikasnost u praksi nasuprot kvadratnoj složenosti na-jgoreg slučaja. Ovo zapažanje sugeriše da su najgori slučajevi retki i da jeprosečna složenost ovog algoritma osetno povoljnija.Pretpostavimo da je jednaka verovatnoća da će proizvoljni element biti izabranza pivot i da je jednaka verovatnoća da pivot nakon particionisanja završi nabilo kojoj poziciji od 0 do 𝑛 − 1. Ako brojimo samo upoređivanja (broj zamenaje manji ili jednak broju upoređivanja), složenost particionisanja je 𝑛 − 1. Tadaprosečna složenost zadovoljava narednu rekurentnu jednačinu.

𝑇 (𝑛) = 𝑛 − 1 + 1𝑛

𝑛−1∑𝑖=0

(𝑇 (𝑖) + 𝑇 (𝑛 − 𝑖 − 1))

Prvi sabirak se kreće od 𝑇 (0) do 𝑇 (𝑛 − 1), a drugi od 𝑇 (𝑛 − 1) do 𝑇 (0), takoda se svako 𝑇 (𝑖) javlja tačno dva puta. Zato za 𝑛 ≥ 1 važi

𝑇 (𝑛) = 𝑛 − 1 + 2𝑛

𝑛−1∑𝑖=0

𝑇 (𝑖).

18

Page 19: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Ovo je takozvana jednačina sa potpunom istorijom, jer se vrednost 𝑇 (𝑛) izraču-nava preko svih prethodnih vrednosti 𝑇 (𝑖). Jedan način da se istorija eliminišeje da se posmatraju razlike između susednih članova niza čime se dobija jed-načina koja opisuje vezu između dva susedna člana. U ovom slučaju se svaki odsabiraka 𝑇 (𝑖) deli sa 𝑛, te pre oduzimanja svaki od uzastopnih članova trebapomnožiti sa odgovarajućim faktorom. Tako se dobija

𝑛𝑇 (𝑛) − (𝑛 − 1)𝑇 (𝑛 − 1) = (𝑛(𝑛 − 1) + 2𝑛−1∑𝑖=0

𝑇 (𝑖)) −

((𝑛 − 1)(𝑛 − 2) + 2𝑛−2∑𝑖=0

𝑇 (𝑖))

= 2(𝑛 − 1) + 2𝑇 (𝑛 − 1)

Zato je𝑇 (𝑛) = 2(𝑛 − 1)

𝑛 + 𝑛 + 1𝑛 𝑇 (𝑛 − 1)

Iako je ova jednačina linearna rekurentna jednačina koja povezuje samo dvauzastopna člana niza, ona nije sa konstantnim koeficijentima i potrebno je maloinventivnosti da bismo je rešili. Deljenje sa 𝑛 + 1 nam daje pogodniji oblik.

𝑇 (𝑛)𝑛 + 1 = 𝑇 (𝑛 − 1)

𝑛 + 2(𝑛 − 1)𝑛(𝑛 + 1)

Naime, sada se vidi da su dva člana koja uključuju nepoznatu funkciju 𝑇 (𝑛) istogoblika, pa jednačinu možemo jednostavno odmotati i korišćenjem činjenice daje 𝑇 (0) = 0 dobiti

𝑇 (𝑛)𝑛 + 1 = 2(𝑛 − 1)

𝑛(𝑛 + 1) + 2(𝑛 − 2)(𝑛 − 1)𝑛 + … + 2 ⋅ (1 − 1)

1(1 + 1) = 2𝑛

∑𝑖=1

𝑖 − 1𝑖(𝑖 + 1)

Centralno pitanje postaje kako izračunati zbir𝑛

∑𝑖=1

𝑖 − 1𝑖(𝑖 + 1)

Tome pomaže razdvajanje sabiraka na parcijalne razlomke. Iz jednačine𝑖 − 1

𝑖(𝑖 + 1) = 𝐴𝑖 + 𝐵

𝑖 + 1sledi da je 𝐴𝑖 + 𝐴 + 𝐵𝑖 = 𝑖 − 1, pa je 𝐴 = −1, 𝐵 = 2 i važi

𝑖 − 1𝑖(𝑖 + 1) = 2

𝑖 + 1 − 1𝑖

Zato je𝑛

∑𝑖=1

𝑖 − 1𝑖(𝑖 + 1) = 2

1 + 1 − 11 + 2

2 + 1 − 12 + 2

3 + 1 − 13 + … + 2

𝑛 + 1 − 1𝑛

19

Page 20: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Možemo grupisati razlomke sa istim imeniocem i dobiti𝑛

∑𝑖=1

𝑖 − 1𝑖(𝑖 + 1) = 1

2 + … + 1𝑛 − 1 + 2

𝑛 + 1

Zato je𝑇 (𝑛) = 2(𝑛 + 1) ⋅ (1 + 1

2 + … + 1𝑛) − 4𝑛

Znamo da se harmonijski zbir 1+1/2+…+1/𝑛 asimptotski ponaša kao log 𝑛+𝛾,gde je 𝛾 Ojler-Maskeronijeva konstanta 𝛾 ≈ 0, 57722 i zato je

𝑇 (𝑛) = Θ (2(𝑛 + 1)(log 𝑛 + 𝛾) − 4𝑛) = Θ(𝑛 log 𝑛).

Amortizovana analiza složenosti

U nekim situacijama se izvesne operacije ponavljaju puno puta tokom izvrša-vanja programa. U mnogim situacijama možemo da dopustimo da pojedinačnoizvršavanje neke operacije traje i malo duže, ako smo sigurni da više izvršavanjate operacije u zbiru neće trajati predugo (ako postoji dovoljno izvršavanja teoperacije koja će trajati kratko). Analiza ukupne dužine trajanja većeg brojaoperacija naziva se amortizovana analiza složenosti. Amortizovana cena izvrša-vanja 𝑛 operacija podrazumeva količnik njihove ukupne dužine izvršavanja ibroja 𝑛. Ilustrujmo je na jednom primeru.Jedna od najčešće upotrebljavanih struktura podataka je dinamički niz. Raz-motrimo koliko je potrebno vremena da se u njega smesti 𝑛 elemenata. Kadau nizu nema dovoljno prostora da se smesti naredni element, niz se dinamičkirealocira. U najgorem slučaju ovo podrazumeva kopiranje starog sadržaja nizana novu lokaciju, što je operacija složenosti 𝑂(𝑚), gde je 𝑚 broj elemenataupisanih u niz. Postavlja se pitanje kako prilikom realokacija određivati brojelemenata proširenog niza.Jedna strategija može biti aritmetička i ona podrazumeva da se krenuvši odnekog inicijalnog kapaciteta niza 0 prilikom svake realokacije veličina nizapoveća za neki broj 𝑘 (ništa se suštinski ne bi promenilo u analizi i da jeinicijalni kapacitet neki broj 𝑚0). Izbrojmo koliko je puta potrebno izvršitiupis elementa u niz (ta operacija je obično najsporija), pri čemu tu računamoupise novih elemenata i upise nastale tokom pomeranja postojećih elemenatatokom realokacije. U prvom koraku primene aritmetičke strategije alociramo𝑘 elemenata i zatim u 𝑘 narednih koraka vršimo upis po jednog elementa.Onda vršimo realokaciju na veličinu 2𝑘 i pritom prepisujemo prvih 𝑘 elemenataniza. Nakon toga upisujemo narednih 𝑘 elemenata, a onda prilikom realokacijeprepisujemo 2𝑘 elemenata. Sličan postupak se nastavlja sve dok se ne upišeelement 𝑛. Dakle, broj operacija je onda jednak 𝑘 + 𝑘 + 𝑘 + 2𝑘 + 𝑘 + 3𝑘 + ….Da bi se smestilo 𝑛 elemenata, realokaciju je potrebno vršiti oko 𝑛/𝑘 puta, paće ukupan broj operacija biti otprilike jednak

20

Page 21: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

𝑛𝑘 𝑘 + 𝑘 ⋅ (1 + 2 + … + 𝑛

𝑘 ) = 𝑛 + 𝑘𝑛𝑘 ( 𝑛

𝑘 + 1)2 = 𝑛2

2𝑘 + 3𝑛2

Dakle, ukupan broj upisa asimptotski je jednak 𝑛22𝑘 tj. 𝑂(𝑛2) i stoga je amor-

tizovana cena jedne operacije asimptotski jednaka 𝑛2𝑘 , što je 𝑂(𝑛), doduše, za

dosta malu vrednost konstante uz 𝑛 (što je veće 𝑘, to je realokacija manje, paje cena operacije manja, ali se cena plaća kroz veće zauzeće memorije i manjupopunjenost alociranog prostora).Druga strategija može biti geometrijska i ona podrazumeva da se svaki putveličina niza poveća 𝑞 puta za neki faktor 𝑞 > 1. Pretpostavimo da je početnaveličina niza 𝑚0. Dakle, nakon početne alokacije možemo da upišemo 𝑚0 ele-menata. Nakon toga se vrši prva realokacija u kojoj se veličina niza povećava na𝑞𝑚0 elemenata i pri čemu se prepisuje 𝑚0 elemenata. Nakon toga se vrši upisnarednih 𝑞𝑚0 − 𝑚0 elemenata. U narednoj realokaciji veličina niza se povećavana 𝑞2𝑚0 i pritom se prepisuje 𝑞𝑚0 elemenata. Nakon toga se upisuje preostalih𝑞2𝑚0 − 𝑞𝑚0 elemanata. Postupak se dalje nastavlja po istom principu. Dakle,ukupan broj upisa u niz jednak je

𝑚0 + 𝑚0 + (𝑞𝑚0 − 𝑚0) + 𝑞𝑚0 + (𝑞2𝑚0 − 𝑞𝑚0) + … = 𝑚0 + 𝑞𝑚0 + 𝑞2𝑚0 + …

Posle 𝑟 realokacija ukupan broj upisa jednak je

𝑚0(1 + … + 𝑞𝑟) = 𝑚0𝑞𝑟+1 − 1

𝑞 − 1 .

Ako pretpostavimo da je ceo niz popunjen posle 𝑟 realokacija, tj. da je 𝑛 = 𝑚0𝑞𝑟,onda je ukupan broj operacija potrebnih za popunjavanje niza jednak

𝑚0𝑞 𝑛𝑚0

− 1𝑞 − 1 = 𝑞𝑛 − 𝑚0

𝑞 − 1 .

Asimptotski je, dakle, ukupna cena izvođenja svih operacija jednaka 𝑂(𝑛), aamortizovana cena izvođenja jedne operacije dodavanja u ovakav niz je 𝑂(1).Konstantan faktor jednak je 𝑞

𝑞−1 i on je sve manji kako 𝑞 raste. Zaista, sapovećanjem 𝑞 vrši se sve manje realokacija, ali se cena plaća većim angažovanjemmemorije tj. manjom popunjenošću niza.Amortizovana analiza složenosti nam pokazuje da sa stanovišta vremena izvrša-vanja geometrijska strategija realokacije daje značajno bolje rezultate nego ar-itmetička.

21

Page 22: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Neki načini poboljšanja složenosti

U nastavku ćemo prikazati kako se neki problemi mogu rešiti različitim algo-ritmima i analiziraćemo njihovu složenost (slično ste već u kursu P2 radili naprimerima algoritama sortiranja i pretrage). Ujedno ćemo videti kako se ra-zličite algoritamske tehnike o kojima će detaljno biti reči u nastavku ovog kursa(neke od njih ste već sreli u kursu P2) primenjuju da bi se dobio algoritam boljesloženosti.Neki važni saveti koji nas dovode do algoritama manje složenosti mogu bitisledeći:

• Nemoj terati računar da vrši dugotrajna izračunavanja koja se moguizvršiti i “peške”, primenom matematike.

• Nemoj terati računar da više puta izračunava jedno te isto.• Nemoj terati računar da izračunava stvari koje nisu potrebne za dobijanje

konačnog rešenja problema.• Nemoj terati računar da ispituje slučajeve za koje unapred možeš zaključiti

da ne mogu biti traženo rešenje problema.• Ako je to moguće, pripremi podatke tako da se kasnije mogu efikasnije

obraditi.• Koristi efikasnije strukture podataka.• …

Zamena iteracije eksplicitnom formulom

Često se neki traženi rezultati mogu izvesti “na papiru”, korišćenjem adekvatnogmatematičkog aparata. Iako računari mogu prilično brzo izvršiti veliki brojračunskih operacija i iako nas algoritmi zasnovani na gruboj sili mogu poštedetinapredne matematičke analize, kada nam je važna efikasnost i kada su očekivaneulazne veličine velike, ipak je poželjno razmotriti mogućnost da se primenommatematičkog aparata izvede gotova formula koja će zameniti neki iterativnipostupak. Prikažimo ovo kroz nekoliko primera.

Zbir prvih n prirodnih brojeva Problem: Definisati efikasan algoritamkoji izračunava zbir 1 + 2 + … + 𝑛. Proceniti mu složenost.Jedna mogućnost je da se zadatak reši u složenosti 𝑂(𝑛), tako što se primenipetlja i algoritam sabiranja serije elemenata.

int zbir = 0;for (int i = 1; i <= n; i++)

zbir += i;

22

Page 23: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Iako je ovo rešenje zadovoljavajuće za male vrednosti 𝑛, i iako ono ne zahtevanikakav matematički aparat, za veće vrednosti 𝑛 ovo rešenje je neefikasno. Dorešenja se može doći i u složenosti 𝑂(1) korišćenje eksplicitne, Gausove formule.

unsigned long long zbir = n*(n+1)/2;

Prilikom implementacije treba voditi računa i o prekoračenju i malo bolja im-plementacija prvo deli, pa onda množi.

unsigned long long zbir =n % 2 == 0 ? (n/2)*(n+1) : ((n+1)/2)*n;

Prvi algoritam prvih milijardu prirodnih brojeva sabere za oko 0, 438 sekundi,a drugi za oko 0, 004 sekunde (u to je uključeno i vreme pokretanja programa,ispisa, završetka programa, tj. celokupno vreme izvršavanja prijavljeno pomoćufunkcije time).

Nedostajući broj Problem: Dat je niz od 𝑛 elemenata koji sadrži različitebrojeve iz skupa 0, 1, 2, … , 𝑛 (tačno jedan broj nedostaje). Odredi koji brojnedostaje.Jedno rešenje može biti zasnovano na linearnoj pretrazi svih kandidata. Za svebrojeve od 0 do 𝑛 proveravamo da li su sadržani u nizu. Linearna pretraganiza od 𝑛 elemenata u najgorem slučaju zahteva 𝑂(𝑛) koraka, pa pošto se traži𝑛 + 1 element, složenost je 𝑂(𝑛2). Može se primetiti i da nije moguće da se usvakom koraku linearne pretrage dogodi najgori slučaj. Najgori slučaj se dešavakada se element ne nalazi u nizu, a svi elementi koji se traže (sem jednog) suprisutni. Zaista, kada tražimo element koji se nalazi na poziciji 𝑖 ta pretraga ćese završiti u 𝑖 koraka. Preciznije, element na poziciji 1 će se pronaći u jednomkoraku, element na poziciji 2 u dva koraka, element na poziciji 3 u tri koraka itako dalje. Ukupan broj koraka je zato 1 + 2 + … + 𝑛, što je opet 𝑂(𝑛2).Bolje rešenje može biti zasnovano na sortiranju elemenata (koje se može uraditiu vremenu 𝑂(𝑛 log 𝑛)) i zatim linearnoj proveri svake pozicije da li je 𝑎𝑖 = 𝑖.Prva koja nije ukazuje na nedostajući broj. Tu poziciju možemo identifiko-vati i binarnom pretragom, ali algoritmom i dalje dominira složenost sortiranja𝑂(𝑛 log 𝑛). Kasnije ćemo videti da zahvaljujući specifičnosti raspona brojeva sor-tiranje možemo uraditi i u vremenu 𝑂(𝑛), međutim postoji veoma jednostavan,potpuno matematički postupak koji nam rešenje daje u toj složenosti.Zbir svih elemenata iz skupa {0, 1, 2, … , 𝑛} je 0 + 1 + 2 + … + 𝑛 = 𝑛(𝑛+1)

2 . Toje zbir elemenata koji se nalaze u nizu i nedostajućeg elementa. Nedostajućielement je, dakle, jednak, razlici između 𝑛(𝑛+1)

2 i zbira svih elemenata niza kojilako možemo izračunati u vremenu 𝑂(𝑛).#include <iostream>using namespace std;

int main() {

23

Page 24: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

int n;int zbir = 0;for (int i = 0; i < n; i++) {

int x;cin >> x;zbir += x;

}cout << n*(n+1) / 2 - zbir << endl;return 0;

}

Posmatrajmo sledeće uopštenje ovog problema.Problem: Dat je niz od 𝑛 − 1 elemenata koji sadrži različite brojeve iz skupa0, 1, 2, … , 𝑛 (tačno dva broja nedostaju). Odredi koji su brojevi nedostajući.Problem rešavamo slično kao u prethodnom slučaju, jedino što pored zbira el-emenata koristimo i zbir kvadrata. Neka su nedostajući elementi 𝑥 i 𝑦, a nekaje zbir postojećih elemenata u nizu 𝑧1, a zbir njihovih kvadrata 𝑧2. Važi daje 𝑧1 + 𝑥 + 𝑦 = 0 + 1 + 2 + … + 𝑛 = 𝑛(𝑛+1)

2 , a da je 𝑧2 + 𝑥2 + 𝑦2 = 02 + 12 +22 + … + 𝑛2 = 𝑛(𝑛+1)(2𝑛+1)

6 . Zato znamo da je 𝑥 + 𝑦 = 𝑛(𝑛+1)2 − 𝑧1 = 𝑐1,

a da je 𝑥2 + 𝑦2 = 𝑛(𝑛+1)(2𝑛+1)6 − 𝑧2 = 𝑐2, za neke konstante 𝑐1 i 𝑐2. Zato

je (𝑥 + 𝑦)2 − (𝑥2 + 𝑦2) = 2𝑥𝑦 = 𝑐21 − 𝑐2. Pošto je 𝑦 = 𝑐1 − 𝑥, važi da je

2𝑥(𝑐1 − 𝑥) = 𝑐21 − 𝑐2, pa je 2𝑥2 − 2𝑐1𝑥 + 𝑐2

1 − 𝑐2 = 0. Zato je

𝑥 = 2𝑐1 + √4𝑐21 − 8(𝑐2

1 − 𝑐2)4 = 𝑐1 + √2𝑐2 − 𝑐2

12 ,

𝑦 = 𝑐1 − 𝑥 = 𝑐1 − √2𝑐2 − 𝑐21

2 .

#include <iostream>#include <cmath>using namespace std;

int main() {int n;cin >> n;int z1 = 0, z2 = 0;for (int i = 0; i < n-1; i++) {

int x;cin >> x;z1 += x; z2 += x*x;

}int c1 = n*(n+1)/2 - z1;int c2 = n*(n+1)*(2*n+1)/6 - z2;int x = (c1 + sqrt(2*c2 - c1*c1)) / 2;int y = c1 - x;cout << x << " " << y << endl;

24

Page 25: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

return 0;}

Za tri nedostajuća elementa mogli bismo upotrebiti i zbir kubova. Moguće ječak uopštenje na 𝑘 nedostajućih elemenata, ali matematika bi postajala sveteža i teža (ključna tehnika je rešavanje sistema jednačina u kojima figuriše zbirkubova, zbir kvadrata i običan zbir, za koji se može pokazati da se uvek možeeksplicitno rešiti). Složenost bi sve vreme bila jednaka 𝑂(𝑛), mada bi konstantapostajala sve veća i veća. Kasnije ćemo konstruisati direktno, algoritamskorešenje ovog zadatka, bez previše upotrebe matematike.

Broj deljivih u intervalu Problem: Definisati efikasan algoritam kojiodređuje koliko je brojeva u intervalu [𝑎, 𝑏], 0 ≤ 𝑎 ≤ 𝑏 deljivo datim brojem 𝑘.Proceniti mu složenost.Naivan način da se ovaj zadatak reši je da se primeni linearni prolaz kroz interval,da se proveri deljivost svakog elementa i brojanje onih koji zadovoljavaju traženiuslov.

int broj = 0;for (int x = a; x <= b; x++)

if (x % k == 0)broj++;

cout << broj << endl;

Složenost ovog algoritma, jasno je 𝑂(𝑏 − 𝑎).Međutim, zadatak možemo rešiti i u složenosti 𝑂(1).Da bi broj 𝑥 bio deljiv brojem 𝑘 potrebno je da postoji neko 𝑛 tako da je 𝑥 = 𝑛⋅𝑘.Pošto 𝑥 mora biti u intervalu [𝑎, 𝑏], mora da važi da je 𝑎 ≤ 𝑛 ⋅ 𝑘 i 𝑛 ⋅ 𝑘 ≤ 𝑏.Najmanje 𝑛 koje zadovoljava prvu nejednačinu jednako je 𝑛𝑙 = ⌈ 𝑎

𝑘 ⌉. Najveće𝑛 koje zadovoljava drugu nejednačinu jednako je 𝑛𝑑 = ⌊ 𝑏

𝑘 ⌋. Bilo koji broj izintervala [𝑛𝑙, 𝑛𝑑] zadovoljava obe nejednakosti i predstavlja količnik nekog brojaiz intervala [𝑎, 𝑏] brojem 𝑘. Slično, bilo koji broj iz intervala [𝑎, 𝑏] deljiv brojem𝑘 daje neki količnik iz intervala [𝑛𝑙, 𝑛𝑑]. Zato je traženi broj brojeva iz intervala[𝑎, 𝑏] koji su deljivi brojem 𝑘 jednak broju brojeva u intervalu [𝑛𝑙, 𝑛𝑑] a to je𝑛𝑑 − 𝑛𝑙 + 1 ako je 𝑛𝑑 ≥ 𝑛𝑙, tj. 0 ako je taj interval prazan tj. ako je 𝑛𝑑 < 𝑛𝑙.

int nl = a % k == 0 ? a/k : a/k + 1; // ceil(a/k)int nd = b/k; // floor(b/k)int broj = nd >= nl ? nd-nl+1 : 0;

Još jedan elegantan način da se ovaj zadatak reši u složenosti 𝑂(1) je da se brojdeljivih brojeva u intervalu [𝑎, 𝑏] predstavi kao razlika između broja deljivih uintervalu [0, 𝑏] i broja deljivih u intervalu [0, 𝑎 − 1] (specijalni slučaj je kada je𝑎 = 0 i kada samo treba izračunati broj deljivih u intervalu [0, 𝑏]). Dakle brojelemenata u proizvoljnom segmentu razlažemo na razliku broja elemenata u dva

25

Page 26: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

prefiksa). Broj deljivih u bilo kom intervalu oblika [0, 𝑛] možemo jednostavnoodrediti određivanjem najvećeg broja u tom intervalu deljivog sa 𝑘, tj. njegovogkoličnika sa 𝑘. To je broj ⌊ 𝑛

𝑘 ⌋ + 1. Najmanji broj deljiv sa 𝑘 u svakom takvomprefiksu je 0.

broj = a > 0 ? b / k - (a - 1) / k : (b / k) + 1;

Za 𝑎 = 0, 𝑏 = 109 i 𝑘 = 43 prvi algoritam rezultat vraća za oko 2, 602 sekundi,a drugi i treći za oko 0, 004 sekunde.Uštedu vremena smo dobili tako što smo u potpunosti eliminisali pretragu.

Eliminisanje identičnih izračunavanja

Jedna od najvažnijih stvari prilikom pravljenja efikasnih algoritama je izbega-vanje izračunavanja istih stvari više puta. Prikažimo ovu tehniku kroz nekolikoprimera.

Niske u C-u Problem: Definisati funkciju koja ispisuje sve karaktere nisketerminisane nulom.Jedno moguće rešenje može biti sledeće.

void ispis(char s[]) {for (int i = 0; i < strlen(s); i++)

printf("%c", s[i]);}

Iako je naizgled ovo rešenje linearne složenosti (jer sadrži samo jednu petlju), unjemu se 𝑛 puta poziva funkcija strlen (gde je 𝑛 dužina niske s), pa je zatosloženost ovog algoritma 𝑂(𝑛2). Ovakvu pojavu nazivamo skrivenom složenošćufunkcije.Problem sa ovim rešenjem je to što se identično izračunavanje dužine niske snepotrebno vrši 𝑛 puta. Jedno moguće rešenje je zasnovano na memoizaciji tj.keširanju – jednom izračunatu vrednost ćemo upamtiti i kasnije je samo čitati.

void ispis(char s[]) {int n = strlen(s);for (int i = 0; i < n; i++)

printf("%c", s[i]);}

Rešenje može biti zasnovano i na drugim tehnikama (izračunavanje dužine niskese u potpunosti može zaobići proverom da li se na tekućoj poziciji nalazi termi-nalna nula).

26

Page 27: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

void ispis(char s[]) {for (int i = 0; s[i] != '\0'; i++)

printf("%c", s[i]);}

Fibonačijevi brojevi Problem: Fibonačijev niz 0, 1, 1, 2, 3, 5, 8, 13, …, jetakav da je zbir njegova dva susedna elementa uvek daje element koji slediiza njih. Napisati program koji izračunava njegov član na poziciji 𝑛.Na osnovu definicije niza 𝐹0 = 0, 𝐹1 = 1, 𝐹𝑛 = 𝐹𝑛−1 + 𝐹𝑛−2 za 𝑛 ≥ 2,rekurzivno rešenje je moguće implementirati veoma jednostavno.

int fib(int n) {if (n == 0) return 0;if (n == 1) return 1;return fib(n-1) + fib(n-2);

}

Jednačina koja određuje složenost ove funkcije (na primer, u broju sabiranja) je𝑇 (𝑛) = 𝑇 (𝑛−1)+𝑇 (𝑛−2)+1, 𝑇 (1) = 𝑇 (0) = 0. Njeno rešenje je eksponecijalno ifunkcija je izrazito neefikasna. Problem je u tome što se iste vrednosti računajuviše puta. Na primer, da bi se izračunao 𝐹5 vrše se rekurzivni pozivi kojiizračunavaju 𝐹4 i 𝐹3. Tokom izračunavanja 𝐹4 računa se i 𝐹3, da bi se onda taizračunata vrednost zanemarila i računala iz početka. Vrednosti 𝐹2, 𝐹1 i 𝐹0 setokom izračunavanja 𝐹5 izračunavaju više puta.U slučajevima preklapajućih rekurzivnih poziva, kao što je ovaj, može se pri-meniti tehnika dinamičkog programiranja o kojoj će mnogo više reči biti kasnije.Osnovna ideja je da se rezultati rekurzivnih poziva pamte, da se svaki put preulaska u rekurziju proveri da li je taj poziv već izvršavan i ako jeste, da se vratinjegova već izračunata vrednost. Ova tehnika se naziva memoizacija.

int fib(int n, vector<int>& memo) {if (memo[n] != -1)

return memo[n];if (n == 0) return memo[0] = 0;if (n == 1) return memo[1] = 1;return memo[n] = fib(n-1, memo) + fib(n-2, memo);

}

int fib(int n) {vector<int> memo(n+1, -1);return fib(n, memo);

}

Već ovom transformacijom složenost je svedena na 𝑂(𝑛).Moguće je u potpunosti eliminisati rekurziju i niz popunjavati redom.

27

Page 28: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

int fib(int n) {vector<int> memo(n+1);memo[0] = 0;memo[1] = 1;for (int i = 2; i <= n; i++)

memo[i] = memo[i-1] + memo[i-2];return memo[n];

}

Na kraju, moguće je primetiti i da se u svakom trenutku koriste samo dvaprethodna elementa niza, pa je ceo niz moguće eliminisati i tako dobiti programčija je vremska složenost 𝑂(𝑛), a memorijska 𝑂(1).int fib(int n) {

if (n == 0) return 0;int fpp = 0, fp = 1;for (int i = 2; i <= n; i++) {

int f = fp + fpp;fpp = fp;fp = f;

}return fp;

}

Na osnovu prve definicije vrednost 𝐹40 se izvršava oko 0, 484 sekunde, a naosnovu svih ostalih za oko 0, 005 sekundi.U kasnijem bavljenju dinamičkim programiranjem, videćemo da se sličan nizkoraka može sprovesti za poboljšanje efikasnosti u mnogim situacijama u kojimase rekurzivni pozivi preklapaju, tj. kada se isti rekurzivni pozivi vrše veći brojputa, čime se narušava efikasnost.Napomenimo i da se eksplicitnim rešavanjem rekurentne jednačine za Fibionači-jeve brojeve dobija Bineova formula koja pokazuje da je 𝐹𝑛 = 𝜙𝑛−(1−𝜙)𝑛

√5 , gde

je 𝜙 = 1+√

52 , vrednost takozvanog zlatnog preseka.

Najmanja tačna perioda niza Nekada je čak moguće problem razložiti namanje potprobleme koji su potpuno identični i onda rešiti samo jedan od njih.Razmotrimo sledeći interesantan primer.Problem: Dat je niz 𝑎 dužine 2𝑘. Broj 𝑝 je tačna perioda niza 𝑎 ako seniz 𝑎 može dobiti ponavljanjem podniza prvih 𝑝 elemenata niza, bez dodatnihelemenata. Na primer, dužina najkraće tačne periode niza 1 2 3 4 1 2 34 je 4, dok je za niz 1 2 3 1 2 3 1 2 dužina najkraće tačne periode 8).Definisati funkciju koja efikasno određuje dužinu 𝑝 najmanje tačne periode togniza i proceniti joj složenost.Jasno je da dužina periode mora da deli dužinu niza. Pošto je dužina niza 2𝑘,dužina periode mora biti stepen broja 2.

28

Page 29: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Jedan pristup rešavanju zadatka je da se isprobaju redom sve moguće dužineperioda od 1 do dužine niza, koji su stepeni dvojke i da se prijavi prvi broj zakoji se ustanovi da predstavlja dužinu periode. Ako je niz periodičan, tada je𝑎0 = 𝑎𝑝 = 𝑎2𝑝 = …, 𝑎1 = 𝑎𝑝+1 = 𝑎2𝑝+1 = … itd. Dakle, za svaku poziciju 𝑖mora da važi da je 𝑎𝑖 = 𝑎𝑖 mod 𝑝. Na osnovu toga možemo jednostavno napravitifunkciju koja proverava da li je 𝑝 perioda niza (za 𝑝 koje deli 𝑛).

int jePerioda(int a[], int n, int p) {for (int i = p; i < n; i++)

if (a[i] != a[i % p])return false;

return true;}

int minPerioda(int a[], int n) {for (int p = 1; p <= n; p *= 2)

if (jePerioda(a, n, p))return p;

return n;}

Ocenimo složenost ovog algoritma. Složenost najgoreg slučaja funkcijejePerioda je 𝑂(𝑛) (međutim, često se može desiti da će se na razliku naićiranije, tako da je za očekivati da je složenost prosečnog slučaja bolja). Složenostnajgoreg slučaja funkcije minPerioda nastupa kada niz nije tačno-periodičan,tj. kada se petlja u toj funkciji izvrši do kraja. Pošto se tokom izvršavanjapetlje promenljiva p stalno uvećava duplo, kraj petlje se dostiže u log 𝑛 koraka.Dakle, složenost najgoreg slučaja ovog rešenja je 𝑂(𝑛 log 𝑛).U prethodnom algoritmu se vrši linearna pretraga niza mogućih eksponenata[0, 𝑘]. Jedno moguće poboljšanje je da se umesto linearne koristi binarna pre-traga tog niza.Razmotrimo sada i drugi, malo efikasniji način da se problem reši. Ključniuvid je to da ako je 𝑝 perioda niza, tada je i 2𝑝 takođe perioda niza. Dužinaniza 𝑛 je sigurno perioda niza. Ako prva polovina niza nije jednaka drugoj,tada je najmanja perioda dužina niza (jer niz nije 𝑛/2 periodičan, pa ne možebiti periodičan ni za 𝑛/4, 𝑛/8, itd.). Ako se ustanovi da je prva polovina nizajednaka drugoj, tada je perioda sigurno i 𝑛/2, što je dužina prve polovine niza.Određivanje najkraće periode celog niza se tada svodi na određivanje najkraćeperiode prve polovine niza (pošto je druga polovina niza jednaka prvoj, nju netreba posebno analizirati). Ovim smo dobili induktivno rekurzivnu konstrukcijukoja se završava kada je tekuća dužina niza 1 (njegova najmanja perioda je 1)ili kada se naiđe na niz čija prva polovina nije jednaka drugoj.Možemo napraviti rekurzivnu implementaciju.

int minPeriod(int a[], int n) {if (n == 1)

return 1;

29

Page 30: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

for (int i = 0; i < n / 2; i++)if (a[i] != a[n/2 + i])

return n;return minPeriod(a, n/2);

}

Pošto je rekurzija repna, veoma jednostavno je se možemo osloboditi.

int minPeriod(int a[], int n) {while (n > 1) {

for (int i = 0; i < n / 2; i++)if (a[i] != a[n/2 + i])

return n;n /= 2;

}return 1;

}

Dokaz korektnosti je prilično jednostavan, ali je analiza složenosti interesantnija.Rekurzivna implementacija zadovoljava rekurentnu jednačinu 𝑇 (𝑛) = 𝑇 (𝑛/2) +𝑂(𝑛), čije je rešenje 𝑂(𝑛). To je jasno na osnovu master teoreme, ali lakose može videti i direktno. Naime, na prvom nivou se uradi 𝑛/2 poređenjaunutar unutrašnje petlje, u drugom 𝑛/4, u trećem 𝑛/8, a u poslednjem preizlaska iz rekurzije jedno poređenje (kada je niz dvočlan). Ukupno se uradidakle 𝑛/2+𝑛/4+…+1 koraka, što je 2𝑘−1 +2𝑘−2 +…+1, odnosno, na osnovuformule za zbir geometrijskog niza jednako (2𝑘 − 1)/(2 − 1) tj. 𝑛 − 1.I u ovom primeru smo demonstrirali da iako se u algoritmu javljaju ugnježenepetlje, to ne povlači automatski složenost 𝑂(𝑛2). Analizu je stoga potrebnosprovoditi veoma pažljivo.Čak i kod najgoreg slučaja, razlika između složenosti 𝑂(𝑛) i 𝑂(𝑛 log 𝑛) nije prev-elika, ali ipak može biti osetna. Niz koji se sastoji od 221−1 nule i jedne jedinice,koji očigledno nije periodičan, prvi algoritam obradi za oko 0, 202 sekunde, adrugi za oko 0, 027 sekundi, što je oko 10 puta brže - razlika je dosta manjanego u prethodnim primerima, ali ipak je nezanemariva. Sa druge strane, zaniz iste dužine u kojem se periodično ponavljaju brojevi od 1 do 1024 prvi al-goritam rezultat vraća za oko 0, 050 sekundi, a drugi za oko 0, 030 sekunde, štoje još manja razlika (generalno, što je perioda kraća, prvi algoritam će je bržedetektovati, pri čemu čak iako je perioda dugačka, ali u okviru jedne periodenema puno samosličnosti, prvi algoritam će raditi prilično brzo).

Inkrementalnost

Još jedna tehnika u kojoj se izbegava vršenje istih izračunavanja iznova je inkre-mentalnost. Ilustrujmo je kroz nekoliko interesantnih primera.

30

Page 31: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Svi faktorijeli Problem: Napisati program koji ispisuje vrednosti svih fak-torijela od 1 do 𝑛.Naivan način je da se implementira funkcija koja izračunava vrednost faktorijelai da se ona u petlji poziva.

#include <iostream>

using namespace std;

int faktorijel(int k) {int p = 1;for (int i = 2; i <= k; i++)

p *= i;return p;

}

int main() {int n;cin >> n;for(int k = 1; k <= n; k++)

cout << faktorijel(k) << endl;return 0;

}

Za izračunavanje vrednosti 𝑘! potrebno je 𝑘−1 množenja, pa je za izračunavanjevrednosti svih faktorijela do 𝑛 potrebno 1 + 2 + … + (𝑛 − 1) = (𝑛−1)𝑛

2 množenja,što je 𝑂(𝑛2).Mnogo bolje rešenje je da se primeti da je 𝑘! = 𝑘 ⋅ (𝑘 − 1)!. Zato faktorijele netreba izračunavati jedan po jedan već svaki sledeći treba izračunati na osnovuprethodnog. Faktorijeli zapravo čine rekurentno definisanu seriju 0! = 1 i 𝑘! =𝑘 ⋅ (𝑘 − 1)!, za 𝑘 > 0.

#include <iostream>

using namespace std;

int main() {int n;cin >> n;int p = 1;for(int k = 1; k <= n; k++) {

p *= k;cout << p << endl;

}return 0;

}

Pošto se svaki naredni od prethodnog dobija samo jednim množenjem, ukupanbroj množenja je 𝑛 − 1 pa je složenost izračunavanja svih faktorijela 𝑂(𝑛).

31

Page 32: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Za algoritme kod kojih se neka vrednost ne izračunava stalno iz početka većse efikasno rekonstruiše na osnovu vrednosti poznatih u prethodnom korakukažemo da su inkrementalni. Primetimo da se inkrementalnost veoma dobrouklapa u opšti mehanizam induktivno-rekurzivne konstrukcije, jer smo i u ovomslučaju na osnovu poznavanja rešenja za problem manje dimenzije uspeli direk-tno da odredimo rešenje za problem za jedan veće dimenzije. Inkrementalnostje tesno vezana za tehniku ojačavanja induktivne hipoteze (koju ćemo detaljnijeilustrovati kasnije). Naime, invarijanta petlje u funkciji main u prvom programuje da su ispisani svi faktorijeli zaključno sa 𝑘 − 1, dok je invarijanta petlje ufunkciji main u drugom programu jača - ispisani su svi faktorijeli zaključno sa𝑘, dok je 𝑝 = (𝑘 − 1)!.Inkrementalnost je strašno važna tehnika za snižavanje vremenske složenostialgoritama i u nastavku ćemo je intenzivno koristiti.

Sumiranje redova Problem: Definisati efikasnu proceduru koja izračunavasumu prvih 𝑛 članova reda

∞∑𝑘=0

𝑥𝑘

𝑘! ≈ 𝑒𝑥

Naivno rešenje bi nezavisno izračunavalo svaki član ovog reda, pozivajućifunkcije za izračunavanje stepena i faktorijela. Osim što je sporo, to bi dovelodo potencijalnog prekoračenja, zbog brzog rasta stepene i faktorijelne funkcije.

double exp2(double x, int n) {double zbir = 0.0;for (int k = 0; k < n; k++)

zbir += pow(x, k) / fact(k);return zbir;

}

Mnogo bolje rešenje se dobija primenom inkrementalnosti, ako se primeti da senaredni član reda može dobiti na osnovu prethodnog množenjem sa 𝑥 i deljenjemsa 𝑘.

double exp(double x, int n) {double zbir = 0.0;double clan = 1.0;for (int k = 1; k <= n; k++) {

zbir += clan;clan *= x / k;

}return zbir;

}

Važna invarijanta ove petlje je to da promenljiva clan ima vrednost 𝑥𝑘−1 (𝑘−1)!.Zaista, na početku se promenljive k i clan inicijalizuju jedinicom. Invarijanta

32

Page 33: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

se održava time što se k uvećava za 1, dok se pre tog uvećanja clan množivrednošću 𝑥/𝑘.

Maksimalni zbir segmenta Problem: Definisati efikasnu funkciju kojapronalazi najveći mogući zbir segmenta (podniza uzastopnih elemenata) datogniza celih brojeva. Proceniti joj složenost.Naglasimo da podniz može biti i prazan niz. Tako da je, u slučaju kada seniz sastoji samo od negativnih elemenata, segment sa najvećim zbirom upravoprazan segment.Najdirektniji mogući način da se zadatak reši je da se izračuna zbir svih segme-nata.

// tekuca vrednost maksimalnog zbiraint max_zbir = 0;// proveravamo sve segmente odredjene pozicijama [i, j]for (int i = 0; i < n; i++) {

for (int j = i; j < n; j++) {// izracunavamo sumu segmenta [i, j]int z = 0;for (int k = i; k <= j; k++)

z += a[k];// ako je dobijena suma veca od tekuce, azuriramo jeif (z > max_zbir)

max_zbir = z;}

}

cout << max_zbir << endl;

Korektnost prethodnog algoritma je veoma jednostavno dokazati. Maksimalnizbir je inicijalizovan na nulu (što je zbir praznog segmenta). Unutrašnja petljaračuna zbir segmenta određenog indeksima [𝑖, 𝑗], dok dve spoljašnje petlje nabra-jaju redom sve takve neprazne segmente, pa će svi segmenti biti obrađeni. Kadagod se naiđe na segment čiji je zbir veći od tekućeg maksimalnog zbira, on seažurira, tako da će na kraju promenljiva max_zbir sadržati maksimalnu vred-nost zbirova svih segmenata. Ključni argument za korektnost algoritama kojedo rešenja dolaze nabrajanjem i proverom svih mogućih kandidata za rešenje jeto da su iscrpne tj. da se prilikom provere ni jedan od mogućih kandidata nepreskače. Kasnije ćemo videti da se efikasnost ovog tipa procedura često možepoboljšati time što se za neke kandidate eksplicitno matematički pokaže da nemogu da sadrže rešenje, pa se onda prilikom efektivne pretrage mogu preskočiti.Pokušajte za vežbu da formulišete i precizne invarijante petlji i da formalnodokažete korektnost prethodnog algoritma. Ključni argument za korektnost jeto da se tokom pretrage eksplicitno proveravaju baš svi segmenti niza.Analizirajmo složenost ovog algoritma u terminima broja operacija sabiranja

33

Page 34: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

elemenata segmenata. Unutrašnja petlja se izvršava 𝑗 − 𝑖 + 1 puta (toliko putase vrši operacija sabiranja), što odgovara dužini segmenta. Spoljne petlje kojenabrajaju sve segmente nabrajaju jedan segment dužine 𝑛, dva segmenta dužine𝑛 − 1, tri segmenta dužine 𝑛 − 2, … i 𝑛 segmenata dužine 1. Znači da je brojkoraka jednak 1 ⋅ 𝑛 + 2 ⋅ (𝑛 − 1) + 3 ⋅ (𝑛 − 2) + … + (𝑛 − 1) ⋅ 2 + 𝑛 ⋅ 1. Dakle,segmenata dužine 𝑘 ima 𝑛 − 𝑘 + 1, pa važi da je prethodni zbir jednak

𝑛∑𝑘=1

𝑘(𝑛−𝑘+1) = (𝑛+1)⋅𝑛

∑𝑘=0

𝑘−𝑛

∑𝑘=0

𝑘2 = (𝑛+1)⋅ 𝑛(𝑛 + 1)2 − 𝑛(𝑛 + 1)(2𝑛 + 1)

6

Ovo je reda veličine 𝑛32 − 2𝑛3

6 = 𝑛36 , pa je ovaj algoritam veoma naivan i

složenosti je 𝑂(𝑛3).Prilično je očigledno da se složenost može smanjiti ako se primeti da se u većinislučajeva naredni segment dobija od prethodnog tako što se prethodni segmentproširi za jedan element zdesna. Umesto da zbir proširenog segmenta svaki putračunamo iznova, možemo iskoristiti to što već znamo zbir prethodnog segmentai njega možemo samo uvećati za novododati element.

// tekuca vrednost maksimalnog zbiraint max_zbir = 0;// proveravamo sve segmente koji pocinju na poziciji [i]for (int i = 0; i < n; i++) {

// sumu racunamo inkrementalnoint z = 0;// kraj segmenta ce biti na poziciji [j]for (int j = i; j < n; j++) {

// u sumu uracunavamo element a[j]z += a[j];// ako je dobijena suma veca od maksimalne, azuriramo jeif (z > max_zbir)

max_zbir = z;}

}

cout << max_zbir << endl;

I u ovom slučaju proveravaju se eksplicitno baš svi segmenti, što je osnovniargument korektnosti procedure. Jedina razlika je što se za svaki novi segmentzbir efikasnije računa, tako da je u dokazu korektnosti potrebno obrazložitizašto će uvećanjem promenljive z za a[j] ona sadržati zbir svih elemenata nizau segmentu pozicija [𝑖, 𝑗] (što nije teško dokazati indukcijom, tj. kao invarijantupetlje).Procenimo složenost ove implementacije. Za svako 𝑖 unutrašnja petlja seizvršava 𝑛 − 𝑖 puta i u svakom koraku se vrši jedno sabiranje. Dakle, u prvomkoraku spoljne petlje imamo 𝑛 sabiranja, u drugom 𝑛 − 1 sabiranja, da bi se

34

Page 35: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

u poslednjem koraku vršilo samo jedno sabiranje. Ukupan broj sabiranja je𝑛 + (𝑛 − 1) + … + 2 + 1 = 𝑛(𝑛+1)

2 , pa smo korišćenjem inkrementalnosti svelialgoritam sa složenosti 𝑂(𝑛3) na složenost 𝑂(𝑛2) (videćemo uskoro da seprimenom naprednijih tehnika mogu dobiti rešenja koja su efikasnija od ovoga).Prvi algoritam, složenosti 𝑂(𝑛3) nasumično generisan niz sa 10 hiljada eleme-nata između -3 i 3 obrađuje za 27, 051 sekundi, inkrementalna optimizacija toskraćuje na 0, 074 sekunde.U ovom primeru se prirodno dolazi do inkrementalnog rešenja i neko bi mogaopomisliti da li je uopšte realno da neko ko ima iole iskustva u programiranjukrene od prvog rešenja (koje radi u potpunosti “grubom silom”). Međutim,ako programski jezik nudi ugrađenu funkcionalnost pomoću koje se lako sumirasegment niza, nije isključeno da se dobije rešenje koje je suštinski ekvivalentnotom navedenom. Na primer, u jeziku Python, moguće je formulisati sledećerešenje.

maks_zbir = 0for i in range(n):

for j in range(i+1, n):if sum(a[i:j]) > maks_zbir:

maks_zbir = sum(a[i:j])

Pozivom funkcije sum(a[i:j]) vrši se sabiranje dela niza iz intervala pozicija[𝑖, 𝑗), međutim, za to je potrebno linearno vreme u odnosu na dužinu tog seg-menta, pa je ukupno vreme ovog algoritma 𝑂(𝑛3) iako na prvi pogled ima samodve ugnežđene petlje. Ovakvu pojavu nazivamo skrivena složenost. Dodatniproblem ovde je to što se funkcija sum poziva dva puta, čime se krši principizbegavanja identičnih izračunavanja.

Određivanje broja rastućih segmenata datog niza brojeva Problem:Dat je niz celih brojeva 𝑎. Definisati efikasan algoritam kojim se određuje kolikou tom nizu postoji rastućih segmenata dužine bar 2 i proceniti mu složenost.Segment [𝑖, 𝑗] (0 ≤ 𝑖 < 𝑗 < 𝑛) je rastući ako za njegove elemente važi 𝑎𝑖 <𝑎𝑖+1 < … < 𝑎𝑗.Ovaj zadatak ponovo zahteva analizu segmenata niza, pa su moguća rešenjaslična prethodnom zadatku u kom smo tražili segment maksimalnog zbira.Navodimo ga da bismo još jednom istakli značaj inkrementalnosti.Najdirektnije rešenje je rešenje grubom silom i zasniva se na tome da se za svakisegment u nizu eksplicitno proveri da li je rastući. Provera da li je segmentrastući može se zasnovati na linearnoj pretrazi za dva susedna elementa u kojimaje 𝑎𝑘 ≤ 𝑎𝑘−1 - ako takva dva elementa ne postoje, niz je rastući.

// ukupan broj rastucih segmenataint brojRastucih = 0;// proveravamo sve segmente odredjene pozicijama [i, j]// posto je minimalna duzina segmenta 2,

35

Page 36: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

// vrednost i ne sme ici dalje od n-2, a j krece od i+1for (int i = 0; i < n - 1; i++) {

for (int j = i + 1; j < n; j++) {// provera da li je segment a[i], a[i+1], ..., a[j] rastucibool rastuci = true;for(int k = i + 1; k <= j && rastuci; k++)

if (a[k] <= a[k-1])rastuci = false;

// ako jeste, uvecavamo broj rastucih serijaif (rastuci)

brojRastucih++;}

}

Ovo rešenje je veoma neefikasno. Postoji 𝑂(𝑛2) segmenata, a monotonostsvakog se proverava algoritmom linearne složenosti, pa je ukupna složenost na-jgoreg slučaja 𝑂(𝑛3). Precizna analiza složenosti je zapravo veoma slična onoju prethodnom zadatku (ponovo se dobija veoma slična suma).Naravno, do rešenja možemo efikasnije doći ako primenimo svojstvo inkremen-talnosti tj. činjenicu da ako znamo da je segment 𝑎𝑖, … 𝑎𝑗 rastući, tada jesegment 𝑎𝑖, … , 𝑎𝑗, 𝑎𝑗+1 rastući ako i samo ako je 𝑎𝑗 < 𝑎𝑗+1. Dodatno, ako tajsegment nije rastući, znamo da rastući ne može biti ni jedan dalji segment kojipočinje na poziciji 𝑖, tako da unutrašnju petlju možemo prekinuti čim se naiđena dva uzastopna elementa koji nisu u rastućem redosledu.

int brojRastucih = 0;for (int i = 0; i < n - 1; i++)

for (int j = i + 1; j < n && a[j] > a[j-1]; j++)brojRastucih++;

Složenost ovog algoritma je 𝑂(𝑛2), a uskoro ćemo videti da se korišćenjem efikas-nijih tehnika mogu dobiti rešenja koja su efikasnija od ovoga.

Postavljanje rutera Problem: Duž jedne ulice su ravnomerno raspoređenezgrade (rastojanje između svake dve susedne je jednako). Za svaku zgradu jepoznat broj korisnika koje novi dobavljač interneta treba da poveže. Odred-iti u koju od zgrada treba postaviti ruter tako da bi ukupna dužina optičkihkablova kojim se svaki od korisnika povezuje sa ruterom bila minimalna (raču-nati samo dužinu kablova od zgrade do zgrade i zanemariti dužine unutarzgrada). Na primer, ako je broj korisnika po zgradama jednak 3, 5, 1, 6, 2, 4,ruter treba postaviti u četvrtu zgradu sleva i dužina kablova je tada jednaka3 ⋅ 3 + 2 ⋅ 5 + 1 ⋅ 1 + 1 ⋅ 2 + 2 ⋅ 4 = 30.Naivno rešenje bi podrazumevalo da se izračuna dužina kablova za svaku mogućupoziciju rutera i da se odabere najmanji. Da bismo izračunali dužinu kablova,ako je ruter u zgradi na poziciji 𝑘, računamo zapravo zbir

36

Page 37: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

𝑛−1∑𝑖=0

|𝑘 − 𝑖| ⋅ 𝑎𝑖,

gde je 𝑎𝑖 broj korisnika u zgradi 𝑖. Tu težinsku sumu možemo izračunati uvremenu 𝑂(𝑛), pa pošto se ispituje 𝑛 pozicija, algoritam bi bio složenosti 𝑂(𝑛2).Mnogo bolje rešenje i linearni algoritam možemo dobiti ako primenimo principinkrementalnosti. Razmotrimo kako se dužina kablova menja kada se ruterpomera sa zgrade 𝑘 na zgradu 𝑘 + 1. Ako je ruter na zgradi 𝑘 tada je dužinakablova jednaka

𝑑𝑘 =𝑘−1∑𝑖=0

(𝑘 − 𝑖) ⋅ 𝑎𝑖 +𝑛−1∑

𝑖=𝑘+1(𝑖 − 𝑘) ⋅ 𝑎𝑖.

Ako je ruter na zgradi 𝑘 + 1, tada je dužina kablova jednaka

𝑑𝑘+1 =𝑘

∑𝑖=0

(𝑘 + 1 − 𝑖) ⋅ 𝑎𝑖 +𝑛−1∑

𝑖=𝑘+2(𝑖 − 𝑘 − 1) ⋅ 𝑎𝑖.

Razlika između te dve sume jednaka je

𝑑𝑘+1 − 𝑑𝑘 = (𝑘

∑𝑖=0

(𝑘 + 1 − 𝑖) ⋅ 𝑎𝑖 −𝑘−1∑𝑖=0

(𝑘 − 𝑖) ⋅ 𝑎𝑖) +

(𝑛−1∑

𝑖=𝑘+2(𝑖 − 𝑘 − 1) ⋅ 𝑎𝑖 −

𝑛−1∑

𝑖=𝑘+1(𝑖 − 𝑘) ⋅ 𝑎𝑖)

= (𝑘−1∑𝑖=0

((𝑘 + 1 − 𝑖) − (𝑘 − 𝑖)) ⋅ 𝑎𝑖) + 𝑎𝑘 − 𝑎𝑘+1+

(𝑛−1∑

𝑖=𝑘+2((𝑖 − 𝑘 − 1) − (𝑖 − 𝑘)) ⋅ 𝑎𝑖)

=𝑘−1∑𝑖=0

𝑎𝑖 + 𝑎𝑘 − 𝑎𝑘−1 −𝑛−1∑

𝑖=𝑘+2𝑎𝑖

=𝑘

∑𝑖=0

𝑎𝑖 −𝑛−1∑

𝑖=𝑘+1𝑎𝑖

Dužinu kablova za ruter u zgradi 𝑘 + 1 dobijamo od dužine kablova za ruteru zgradi 𝑘 tako što tu dužinu uvećamo za ukupan broj stanara zaključno sazgradom 𝑘 i umanjimo je za ukupan broj stanara počevši od zgrade 𝑘 + 1. To jezapravo intuitivno prilično jasno i bez prethodnog komplikovanog matematičkog

37

Page 38: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

izvođenja. Pomeranjem rutera za dužinu jedne zgrade nadesno, svakom stanarukoji živi zaključno do zgrade 𝑘 dužina kabla se povećala za jedno rastojanjeizmeđu zgrada, a svim stanarima od zgrade 𝑘+1 nadesno se ta dužina smanjujeza jedno rastojanje između zgrada. Ovaj primer lepo pokazuje kako se intuicijai formalno matematičko izvođenje prepliću - kada možemo do rešenja doći intu-itivnim putem, ono je obično veoma elegantno i jednostavno. Sa druge strane,ako ne vidimo odmah rešenje problema, matematički aparat nam može pomoćida do njega dođemo.Ukupne brojeve stanara pre i posle date zgrade možemo takođe računati inkre-mentalno (pri prelasku na narednu zgradu, prvi broj se uvećava, a drugi uman-juje za broj stanara tekuće zgrade).Dakle, u programu možemo da pamtimo tri stvari: dužinu kablova 𝑑𝑘 ako jeruter na poziciji 𝑘, ukupan broj stanara 𝑝𝑟𝑒𝑘 pre zgrade 𝑘 i ukupan broj stanara𝑝𝑜𝑠𝑙𝑒𝑘 od zgrade 𝑘 do kraja. Na početku, kada je 𝑘 = 0, prvi broj 𝑑0 moramoeksplicitno izračunati kao ∑𝑛−1

𝑖=1 𝑖⋅𝑎𝑖 (za to nam je potrebno vreme 𝑂(𝑛)), drugibroj treba inicijalizovati na nulu 𝑝𝑟𝑒0 = 0, a treći na ukupan broj svih stanara𝑝𝑜𝑠𝑙𝑒𝑘 = ∑𝑛−1

𝑖=0 𝑎𝑖 (i za to nam je potrebno vreme 𝑂(𝑛)). Zatim za svako 𝑘od 1 do 𝑛 − 1 računamo 𝑝𝑟𝑒𝑘 = 𝑝𝑟𝑒𝑘−1 + 𝑎𝑘−1, 𝑝𝑜𝑠𝑙𝑒𝑘 = 𝑝𝑜𝑠𝑙𝑒𝑘−1 − 𝑎𝑘−1 i𝑑𝑘 = 𝑑𝑘−1 + 𝑝𝑟𝑒𝑘 + 𝑝𝑜𝑠𝑙𝑒𝑘.

// krećemo od zgrade 0// ukupna dužina kablova ako je ruter u tekućoj zgradilong long duzina_kablova = 0;for (int i = 0; i < n; i++)duzina_kablova += stanara[i] * i;

// broj stanara pre tekuće zgradelong long stanara_pre = 0;// broj stanara od tekuće zgrade do krajalong long stanara_posle = 0;for (int i = 0; i < n; i++)stanar_posle += stanara[i];

// minimalna duzina kablovalong long min_duzina_kablova = duzina_kablova;

// obrađujemo sve zgrade od 1 do n-1for (int k = 1; k < n; k++) {

// ažuriramo brojeve stanarastanara_pre += stanara[k-1];stanara_posle -= stanara[k-1];// ažuriramo duzduzina_kablova += stanara_pre - stanara_posle;if (duzina_kablova < min_duzina_kablova)

min_duzina_kablova = duzina_kablova;}

cout << min_duzina_kablova << endl;

38

Page 39: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Prikažimo rad ovog algoritma na jednom primeru. Neka u zgradama redomživi 3, 5, 2, 9, 7 i 6 stanara. U narednoj tabeli prikazano je kretanje vrednostipromenljivih.

stanara stanara_pre stanara_posle duzina_kablova

0 32 943 3 29 685 8 24 522 10 22 409 19 13 467 26 6 666

Ukupno vreme izvršavanja ovog algoritma je linearno (izvršavaju se tri petlje,svaka složenosti 𝑂(𝑛)). Recimo i da nismo morali održavati broj stanara odzgrade 𝑘 do kraja, već smo ga mogli svaki put izračunavati kao razliku izmeđuukupnog broja stanara i broja stanara pre zgrade 𝑘 (to praktično ne bi uticalona vreme izvršavanja). Takođe, može se primetiti da dužina kablova do jednogtrenutka opada, a da onda počinje da raste (pokušajte to da dokažete). U prvomtrenutku kada broj stanara pre pretekne broj stanara posle možemo prekinutipretragu (ovo neće uticati na asimptotsko vreme, ali može malo smanjiti kon-stantni faktor). Ovaj vid optimizacije predstavlja odsecanje dela pretrage, moženekada i značajno uticati na smanjivanje složenosti, međutim zahteva obazrivosti dokaz da se među mogućnostima čije se ispitivanje preskače ne nalazi optimalnorešenje (o svemu ovome će biti malo više reči kasnije).

Najveći težinski zbir rotacija niza Problem: Dat je niz 𝑎 celih brojevadužine 𝑛. Dozvoljena je operacija cikličnog pomeranja tj. rotacije niza ulevoza proizvoljan broj mesta. Napisati program kojim se određuje najmanji brojpomeranja ulevo tako da težinski zbir

𝑛−1∑𝑖=0

𝑖 ⋅ 𝑎𝑖 = 0 ⋅ 𝑎0 + 1 ⋅ 𝑎1 + 2 ⋅ 𝑎2 + 3 ⋅ 𝑎3 + … + (𝑛 − 1) ⋅ 𝑎𝑛−1

u transformisanom nizu ima najveću vrednost.I ovaj primer navodimo kako bismo ilustrovali tehniku inkrementalnosti i njenznačaj.Zadatak možemo rešiti tako što 𝑛 − 1 put niz efektivno rotiramo za po jednomesto ulevo izračunavajući svaki put traženi težinski zbir iznova, tražeći maksi-mum i poziciju maksimuma tako dobijenih težinskih zbirova. Rotaciju možemorealizovali bibliotečkom funkcijom za rotiranje (složenost joj je 𝑂(𝑛)) ili jed-nostavno sami isprogramirati (pomoću jedne pomoćne promenljive i pomeranja

39

Page 40: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

svakog elementa za jedno mesto ulevo). Pošto broj operacija potreban za nezav-isno izračunavanje svakog težinskog zbira linearno zavisi od dužine niza 𝑛, ovajpristup dovodi do kvadratne složenosti algoritma tj. do složenosti 𝑂(𝑛2).

long long tezinskiZbir(const vector<int>& a) {long long zbir = 0;for (int i = 0; i < a.size(); i++)

zbir += i*a[i];return zbir;

}

int brojRotacijaZaMaksimalniZbir(const vector<int>& a) {// maksimum inicijalizujemo na težinski zbir pocetnog nizaint maksBrojRotacija = 0;long long maksTezinskiZbir = tezinskiZbir(a);// n-1 puta ponavljamofor (int i = 1; i < n; i++) {

// rotiramo niz za jedno mesto ulevorotate(begin(a), next(begin(a)), next(begin(a), n));// izračunavamo novi težinski zbirlong long zbir = tezinskiZbir(a);// ažuriramo maksimum ako je to potrebnoif (zbir > maksTezinskiZbir) {

maksBrojRotacija = i;maksTezinskiZbir = zbir;

}}return maksBrojRotacija;

}

Na nasumično generisanom nizu od oko 20 hiljada elemenata, ova funkcija mak-simum nalazi za oko 1, 349 sekundi.Umesto efektivne rotacije svih elemenata niza (što može biti neefikasno zbogpuno pomeranja elemenata niza), efekat obilaska niza koji je rotiran za 𝑘 mestaulevo možemo postići tako što obilazak krećemo od pozicije 𝑘, a zatim u petljikoja ima 𝑛 iteracija uvećavamo brojač za 1, ali po modulu 𝑛 (kada brojač postane𝑛 vrednost mu se vraća na nulu što možemo postići bilo eksplicitnim ispitivan-jem vrednosti nakon svakog uvećanja brojača, bilo izračunavanjem ostatka prideljenju sa 𝑛, što može dosta usporiti program). Dobitak na brzini može bitiznačajan, ali složenost i dalje ostaje 𝑂(𝑛2). Funkcija implementirana na ovajnačin, sa deljenjem po modulu 𝑛 radi oko 1, 062 sekunde a sa ispitivanjemvraćanjem brojača na nulu kada dostigne 𝑛 oko 0, 490 sekundi.Još jedna tehnika kojom možemo izbeći rotaciju je da alociramo dvostruko višememorije, da elemente originalnog niza smestimo dva puta, jednom iza drugogi da zatim efekat rotacije za 𝑘 mesta postižemo tako što obilazimo 𝑛 elemenatatako proširenog niza počevši od pozicije 𝑘. Ovim dobijamo na brzini, ali gubimona zauzeću memorije. I ovaj put dobitak na brzini može biti značajan, ali

40

Page 41: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

složenost i dalje ostaje 𝑂(𝑛2). Naredni program na tekućem test-primeru radioko 0, 427 sekundi.

// izracunava tezinski zbir n elemenata niza a od pozicije ilong long tezinskiZbir(const vector<int>& a, int i, int n) {

long long zbir = 0;for (int j = 0; j < n; j++)

zbir += a[i + j] * j;return zbir;

}

int brojRotacijaZaMaksimalniZbir(const vector<int>& a) {// ponavljamo elemente niza dva putaint n = a.size();a.resize(2*n);copy(begin(a), next(begin(a), n), next(begin(a), n));

// maksimum inicijalizujemo na težinski zbir pocetnog nizaint maksBrojRotacija = 0;long long maksTezinskiZbir = tezinskiZbir(a, 0, n);// računamo težinski zbir koji kreće od svake pozicije od i do n-1for (int i = 1; i < n; i++) {

// računamo težinski zbirlong long zbir = tezinskiZbir(a, i, n);// ako je potrebno ažuriramo podatke o maksumumuif (zbir > maksTezinskiZbir) {

maksTezinskiZbir = zbir;maksBrojRotacija = i;

}}

// brišemo višak elemenata nizaa.resize(n);

return maksBrojRotacija;}

Problem je moguće rešiti i efikasnije, ako se oslonimo na inkrementalni pristup.Možemo uočiti da je u traženom zbiru posle pomeranja ulevo svaki element niza,izuzev prvog elementa 𝑎0, jedan put manje uključen nego pre pomeranja, a prvielement je uključen 𝑛 − 1 put. Obeležimo sa 𝑧𝑖 traženi težinski zbir dobijenprilikom pomeranja polaznog niza ulevo 𝑖 puta, a sa 𝑧 zbir svih elemenata niza.Prema tome važe sledeće jednakosti:

𝑧0 = 0 ⋅ 𝑎0 + 1 ⋅ 𝑎1 + 2 ⋅ 𝑎2 + ... + (𝑛 − 2) ⋅ 𝑎𝑛−2 + (𝑛 − 1) ⋅ 𝑎𝑛−1𝑧1 = 0 ⋅ 𝑎1 + 1 ⋅ 𝑎2 + 2 ⋅ 𝑎3 + ... + (𝑛 − 2) ⋅ 𝑎𝑛−1 + (𝑛 − 1) ⋅ 𝑎0𝑧2 = 0 ⋅ 𝑎2 + 1 ⋅ 𝑎3 + 2 ⋅ 𝑎4 + ... + (𝑛 − 2) ⋅ 𝑎0 + (𝑛 − 1) ⋅ 𝑎1

…𝑧𝑛−1 = 0 ⋅ 𝑎𝑛−1 + 1 ⋅ 𝑎0 + 2 ⋅ 𝑎1 + ... + (𝑛 − 2) ⋅ 𝑎𝑛−3 + (𝑛 − 1) ⋅ 𝑎𝑛−2

41

Page 42: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

𝑧 = 𝑎0 + 𝑎1 + ... + 𝑎𝑛−2 + 𝑎𝑛−1

Primetimo da važi

𝑧0 − 𝑧1 = 𝑎1 + 𝑎2 + … + 𝑎𝑛−1 − (𝑛 − 1) ⋅ 𝑎0 = 𝑧 − 𝑛 ⋅ 𝑎0𝑧1 − 𝑧2 = 𝑎2 + 𝑎3 + … + 𝑎0 − (𝑛 − 1) ⋅ 𝑎1 = 𝑧 − 𝑛 ⋅ 𝑎1

…𝑧𝑛−2 − 𝑧𝑛−1 = 𝑎𝑛−1 + 𝑎0 + … + 𝑎𝑛−3 − (𝑛 − 1) ⋅ 𝑎𝑛−2 = 𝑧 − 𝑛 ⋅ 𝑎𝑛−2

itd.Prema tome važi da je 𝑧𝑖−1 − 𝑧𝑖 = 𝑧 − 𝑛 ⋅ 𝑎𝑖−1, tj.

𝑧𝑖 = 𝑧𝑖−1 − 𝑧 + 𝑛 ⋅ 𝑎𝑖−1.

Dakle, traženi težinski zbir posle pomeranja za jedno mesto ulevo možemo jed-nostavno izračunati bez pomeranja niza na osnovu poznatog zbira niza prepomeranja i zbira svih elemenata niza.Implementaciju možemo izvršiti na sledeći način. Izračunamo za polazni niztraženi težinski zbir 𝑧0 = ∑𝑛−1

𝑖=0 𝑖 ⋅ 𝑎𝑖 i zbir svih elemenata niza 𝑧 = ∑𝑛−1𝑖=0 𝑎𝑖.

Od svih zbirova treba izračunati najveći i odrediti posle koliko rotacija se tajnajveći zbir postiže, što, naravno, radimo uobičajenim algoritmom određivanjapozicije maksimuma serije elemenata. Prvi izračunati zbir proglasimo najvećim,a broj pomeranja postavimo na 0. Zatim računamo tražene zbirove za nizovedobijene pomeranjem niza za jedno mesto ulevo 𝑖 puta, i to redom za 𝑖 od 1do 𝑛 − 1. Traženi zbir 𝑧𝑖 računamo tako što prethodni zbir 𝑧𝑖−1 umanjimo zazbir svih elemenata 𝑧 i uvećamo za 𝑛 ⋅ 𝑎𝑖−1. Proveravamo da li je dobijeni zbirveći od do sada najvećeg nađenog zbira i ako jeste korigujemo najveći zbir i brojpomeranja.Vreme potrebno za izračunavanje početnih zbirova 𝑧0 i 𝑧 linearno zavisi oddužine niza 𝑛, dok je za izračunavanje svakog od narednih 𝑛−1 zbirova dovoljankonstantan broj operacija, tako da je ukupna vremenska složenost algoritmalinearna tj. 𝑂(𝑛). Na tekućem test-primeru naredni program radi oko 0, 012sekundi, što je neuporedivo efikasnije od svih prethodnih rešenja.

int brojRotacijaZaMaksimalniZbir(const vector<int>& a) {// izračunavamo zbir i težinski zbir početnog nizaint tezinskiZbir = 0;int zbir = 0;for (int i = 0; i < n; i++) {

tezinskiZbir += i*a[i];zbir += a[i];

}// najveci do sada vidjeni tezinski zbir

42

Page 43: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

int maksTezinskiZbir = tezinskiZbir;// broj rotacija kojima se on postižeint maksBrojRotacija = 0;for (int i = 1; i < n; i++) {

// ažuriramo težinski zbirtezinskiZbir = tezinskiZbir - zbir + n * a[i-1];// ažuriramo maksimum ako je to potrebnoif (tezinskiZbir > maksTezinskiZbir) {maksTezinskiZbir = tezinskiZbir;maksBrojRotacija = i;

}}return maksBrojRotacija;

}

Izbegavanje nepotrebnih izračunavanja

Algoritam bi trebalo da izračuna samo one stvari koje su mu neophodne da bivratio traženi rezultat. Višak posla obično dovodi do loše efikasnosti algoritma.Ilustrujmo ovo kroz nekoliko primera.

Drugi po veličini element u nizu Problem: Data je lista poena studenatanakon prijemog. Koliko poena je imao drugi student na rang listi?Naivno rešenje podrazumeva da se niz sortira opadajuće, pa da se pročita ele-ment na drugoj poziciji.

int drugiNaListi(int a[], int n) {sort(a, a+n, greater<int>());return a[1];

}

Složenost ovog rešenja dolazi od složenosti sortiranja i iznosi 𝑂(𝑛 log 𝑛).Razmislite koliko se nepotrebno vremena troši nizu od 1000 elemenata da seustanovi poredak 998 elemenata iza prva dva. Jedan od načina da se složenostsmanji na 𝑂(𝑛) je da se primene samo prve dve runde algoritma sortiranjaselekcijom.

int drugiNaListi(int a[], int n) {for (int i = 0; i < 2; i++) {

int max_p = ifor (int j = i + 1; j < n; j++)

if (a[j] > a[max_p])max_p = j;

swap(a[i], a[max_p]);}return a[1];

}

43

Page 44: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Iako se u implementaciji javljaju dve ugnežđene petlje, složenost ovog algoritmaje 𝑂(𝑛), jer se spoljna petlja izvršava samo dva puta (ukupan broj poređenja je𝑛 + (𝑛 − 1)).Ipak, najbolje rešenje koristi specijalizovani algoritam.

int drugiNaListi(int a[], int n) {// prvi i drugi maksimum inicijalizujemo na -1int prviMax, drugiMax;prviMax = drugiMax = -1;

for(int i = 0; i < n; i++) {// ako je potrebno ažuriramo vrednost maksimumaif (a[i] > prviMax) {drugiMax = prviMax;prviMax = a[i];

} else if (a[i] > drugiMax) {drugiMax = a[i];

}}

// vraćamo vrednost drugog maksimumareturn drugiMax;

}

Uopštenjem ovog problema (nalaženjem 𝑘-tog po veličini elementa bavićemo sekasnije).

Morzeov niz Problem: Niz

1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, … ,

koji se sastoji od nula i jedinica, gradi se na sledeći način: prvi element je1; drugi se dobija logičkom negacijom prvog ! 1 = 0, treći i četvrti logičkomnegacijom prethodna dva ! 1 = 0, ! 0 = 1, peti, šesti, sedmi i osmi logičkomnegacijom prva četiri - dobija se 0, 1, 1, 0 itd. Dakle, krenuvši od jednočlanogsegmenta 1, svakom početnom segmentu koji je dužine 2𝑘 (𝑘 uzima vrednosti0, 1, 2, …) dopisuje se segment iste dužine dobijen logičkom negacijom svih el-emenata početnog segmenta. Definisati funkciju koja za dato 𝑛 određuje 𝑛-tičlan niza (brojanje kreće od 1).Direktan način da se zadatak reši je da se efektivno popuni niz sve do pozicije𝑛 i da se pročita element na mestu 𝑛.Jedan način da se to uradi je da se upotrebi ugnježdena petlja gde se u svakomunutrašnjem koraku duplira dužina niza.

int morzeov(int n) {// rezervisemo prostor za (n+1) elemenata, da bi mogli da izracunamo// element na poziciji n

44

Page 45: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

vector<bool> m(n+1);// prvi element nizam[1] = true;// st je trenutna duzina niza i bice jednaka stepenu dvojke// u pocetku st = 2^0int st = 1;while (st < n) {

// popunjavamo "drugu" polovinu nizafor (int i = st+1; i <= st+st && i <= n; i++)

m[i] = !m[i-st];// dupliramo duzinu niza (naredni stepen dvojke)st += st;

}return m[n];

}

Umesto dvostruke, možemo upotrebiti i jednostruku petlju.

int morzeov(int n) {vector<bool> m(n+1);m[1] = true;int k = 1;for (int i = 2; i <= n; i++) {

m[i] = !m[i - k];if (i == k * 2)

k *= 2;}return m[n];

}

Složenost ovog pristupa je očigledno 𝑂(𝑛) (kako vremenska, tako i memorijska).Prethodno rešenje podrazumeva formiranje svih elemenata niza koji prethode 𝑛-tom članu. Da bi se izračunao 2001. član, mora se izračunati svih 2000 prethod-nih članova, međutim, jasno je da je to suvišno jer svaki element negiranogprethodnog segmenta direktno zavisi samo od jednog elementa tog prethodnogsegmenta, a ne od svih njih, tako da za njegovo određivanje nije potrebno poz-navanje celokupnog prethodnog segmenta, već samo jednog njegovog karakter-ističnog elementa.Problem možemo rešiti mnogo efikasnije induktivno-rekurzivnom konstrukci-jom.Bazu inducije jasno predstavlja slučaj 𝑚1 = 1.Prilikom negiranja početnog segmenta dobijamo da je 𝑚2 =! 𝑚1. Dakle, u ovomslučaju važi da je 𝑚𝑛 =! 𝑚𝑛−1. Negiranjem narednog segmenta dobijamo daje 𝑚3 =! 𝑚1 i 𝑚4 =! 𝑚2. U ovom slučaju važi da je 𝑚𝑛 =! 𝑚𝑛−2. Nakon togadobijamo da je 𝑚5 =! 𝑚1, 𝑚6 =! 𝑚2, 𝑚7 =! 𝑚3 i 𝑚8 =! 𝑚4. U ovom slučajuvaži da je 𝑚𝑛 =! 𝑚𝑛−4.

45

Page 46: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Dakle, za 𝑛 > 1 važi rekurentna formula 𝑚𝑛 =! 𝑚𝑛−𝑘, gde je 𝑘 maksimalnistepen broja 2 koji je strogo manji od 𝑛. Ova rekurentna formula omogućavaveoma efikasno izračunavanje traženog člana niza. Na primer,

𝑚15 =! 𝑚15−8 =! 𝑚7 =! (! 𝑚7−4) = 𝑚7−4 = 𝑚3 =! 𝑚3−2 =! 𝑚1 =! 1 = 0.

Traženi broj se dobija u malom broju koraka i za veće vrednosti broja 𝑛. Naprimer, za 𝑛 = 2001 važi sledeće.

𝑚2001 =! 𝑚2001−1024 = 𝑚977−512 =! 𝑚465−256 = 𝑚209−128 =! 𝑚81−64 = 𝑚17−16 = 1.

Rekurentna formula nam ukazuje na to da rešenje možemo veoma jednostavnorealizovati uz pomoć rekurzivne funkcije.

// vraca najveci stepen od 2, koji je strogo manji od nint maxStepen2(int n) {int max = 1;while ((max << 1) < n)

max <<= 1;return max;

}

int morzeov(int n) {if (n == 1)

return 1;return !morzeov(n - maxStepen2(n));

}

Složenost ovako implementirane funkcije za određivanje najvećeg stepena dvojkekoji je strogo manji od 𝑛 je 𝑂(log 𝑛), jer se promenljiva max u svakom korakupovećava duplo, sve dok ne dostigne ili prestigne vrednost 𝑛/2.U svakom novom pozivu funkcije morzeov skida se jedan bit broja 𝑛, pa je brojrekurzivnih poziva ograničen brojem bitova broja 𝑛, što je 𝑂(log 𝑛). Složenostje u najgorem slučaju 𝑂(log2 (𝑛)).Do rešenja možemo doći i iterativno.

int morzeov(int n) {int m = 1;while (n > 1) {

n -= maxStepen2(n);m = !m;

}return m;

}

46

Page 47: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Odsecanje u pretrazi

Jako često su algoritmi zasnovani na pretrazi tj. na proveri različitih kandi-data za rešenje (ili kandidata za kontraprimer). Računari veoma brzo nabra-jaju stvari, ali nekada je kandidata previše i provera svih njih može dovestido neefikasnog algoritma. Na sreću, često se matematički može pokazati da serešenje ne može nalaziti u nekom skupu kandidata i pretraga se može ubrzatitime što se onda ti kandidati prosto izostave. Naravno, takvi algoritmi uvekmoraju biti praćeni (makar neformalnim) dokazom korektnosti koji nam dajuopravdanje na osnovu kog smo pretragu sasekli. Ilustrujmo ovo kroz nekolikointeresantnih primera.

Ispitivanje da li je broj prost Problem: Definisati efikasnu funkciju kojaproverava da li je broj prost. Proceni joj složenost.Naivno rešenje je zasnovano na linearnoj proveri svih delilaca.

bool prost(unsigned n) {if (n == 1) return false;for (unsigned d = 2; d < n; d++)

if (n % d == 0)return false;

return true;}

Složenost najgoreg slučaja ovog algoritma je očigledno 𝑂(𝑛) (i ona se dobijakada je broj prost).Na osnovu teoreme koja kaže da ako broj 𝑛 ima delioca 𝑑 koji je veći ili jednak√𝑛 onda sigurno ima delioca koji je manji ili jednak

√𝑛 (to je broj 𝑛/𝑑), brojkandidata za delioce možemo značajno smanjiti i dobiti efikasniji algoritam.

bool jeProst(unsigned n) {if (n == 1) return false;for (unsigned d = 2; d*d <= n; d++)

if (n % d == 0)return false;

return true;}

Složenost najgoreg slučaja ovog algoritma je 𝑂(√𝑛).Prvi algoritam utvrđuje da je broj 1000000007 prost nakon oko 3, 171 sekundi,a drugi nakon oko 0, 005 sekundi. Oba algoritma, međutim, utvrđuju da broj1000000008 nije prost nakon oko 0, 004 sekunde.Ključni razlog uštede je to što smo odsekli neke slučajeve u pretrazi pozivajućise na teoremu koja garantuje da je provera tih slučajeva nepotrebna, jer supokriveni drugim slučajevima. Ovakvo odsecanje se veoma često koristi kaonačin popravljanja efikasnosti algoritama.

47

Page 48: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Recimo da postoje mnogo efikasniji algoritmi za rešavanje ovog problema (njihnećemo izučavati u ovom kursu), kao i da se prethodna implementacija možemalo ubrzati. Na osnovu činjenice da su svi prosti brojevi (pa samim tim iprosti delioci) oblika 6𝑘+1 ili 6𝑘+5 (što je jednako 6𝑡−1, za 𝑡 = 𝑘+1)1, petljase može organizovati tako što se brojač 𝑘 množi sa 6, a u jednom prolasku petljese ispituju dva kandidata. Primetimo da nema potrebe posebno proveravati dali je broj 𝑛 oblika 6𝑘 + 1 ili 6𝑘 − 1, jer početna provera deljivosti sa 2 i sa 3eliminiše sve brojeve koji nisu tog oblika.

bool jeProst(int n) {if (n == 1 ||

(n % 2 == 0 && n != 2) ||(n % 3 == 0 && n != 3))

return false;for (int k = 1; (6*k - 1) * (6*k - 1) <= n; k++)

if (n % (6*k + 1) == 0 || n % (6*k - 1) == 0)return false;

return true;}

Ovo je primer optimizacije koja ne menja asimptotsku složenost, već ubrzavaalgoritam samo za konstantni faktor. Naime, broj koraka je takav da je otprilike36𝑘2 > 𝑛 tj. oko

√𝑛6 , međutim, u svakom koraku se proveravaju dve deljivosti,

pa je broj provera oko√𝑛3 , te možemo očekivati ubrzanje najgoreg slučaja oko

tri puta. Asimptotska složenost je i dalje 𝑂(√𝑛).

Eratostenovo sito Problem: Definisati efikasnu funkciju koja za sve brojevemanje od datog određuje da li su prosti i njenim korišćenjem izračunava kolikoima prostih brojeva manjih od datog. Proceniti joj složenost.Direktan način je da za svaki broj pozovemo funkciju iz prethodnog zadatka.

void sviProsti(bool prost[], int n) {for (int i = 0; i <= n; i++)

prost[i] = jeProst(i);}

Pošto je složenost najgoreg slučaja prethodne funkcije 𝑂(√𝑛) i ona se poziva𝑂(𝑛) puta, složenost ove implementacije je 𝑂(𝑛√𝑛) = 𝑂(𝑛3/2).Bolji način da se zadatak reši je da se upotrebi Eratostenovo sito u kom seredom precrtavaju svi umnošci prostih brojeva. Postupak zahteva niz dužine 𝑛(u implementaciji dužine 𝑛 + 1 jer je niz indeksiran od 0) tako da 𝑖-ti elementodgovara broju 𝑖. Inicijalno su svi brojevi označeni kao prosti i u toku postupkase eliminišu svi oni koji to nisu (brojevi se “prosejavaju kroz sito”).

1Proizvoljan broj se može zapisati u obliku 𝑛 = 6𝑘 + 𝑗, za 0 ≤ 𝑗 < 6. Brojevi oblika 6𝑘,6𝑘 + 2, 6𝑘 + 3 i 6𝑘 + 4 su deljivi brojem 2 ili 3, pa prosti brojevi mogu biti zapisani samo uobliku 6𝑘 + 1 ili 6𝑘 + 5.

48

Page 49: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

void eratosten(bool prost[], int n) {for (int i = 0; i <= n; i++)

prost[i] = true;prost[0] = prost[1] = false;for (int d = 2; d*d <= n; d++)

if (prost[d])for (int i = d*d; i <= n; i += d)

prost[i] = false;}

Primetimo da je i ovde pretraga sasečena tj. da se i u ovom slučaju iteracijavršila samo do

√𝑛, što značajano ubrzava ceo postupak, a opravdano je naosnovu istog argumenta kao i u slučaju ispitivanja da li je pojedinačan brojprost.Dokažimo korektnost ovog algoritma.Lema: Invarijanta spoljašnje petlje je da je 2 ≤ 𝑑 ≤ ⌊√𝑛⌋ + 1 i da su precrtani0, 1 i tačno svi oni brojevi manji ili jednaki 𝑛 koji imaju prave delioce u intervalu[2, 𝑑) (tj. za svaki takav broj 𝑥 važi da je u nizu prost zapisano da taj brojnije prost, dok je za sve brojeve koji nemaju delilaca manjih od 𝑑 zapisano dasu prosti).

• Na ulazu u petlju je 𝑑 = 2 i precrtani su samo 0 i 1, što je u skladu sainvarijantom, jer je interval [2, 2) prazan.

• Pretpostavimo da tvrđenje važi na ulasku u telo spoljne petlje.Pošto pri ulasku u petlju važi 𝑑2 ≤ 𝑛, važi i 𝑑 ≤ ⌊√𝑛⌋. Onda važi i𝑑 + 1 ≤ ⌊√𝑛⌋ + 1, odnosno 𝑑′ ≤ ⌊√𝑛⌋ + 1.Pošto su na osnovu pretpostavke precrtani svi oni brojevi manji ili jednaki𝑛 koji imaju delioce iz intervala [2, 𝑑) i pošto je 𝑑′ = 𝑑 + 1 potrebno jepokazati da su nakon izvršavanja tela petlje precrtani i svi oni brojevimanji ili jednaki 𝑛 koji su deljivi brojem 𝑑.Ako u nizu prost piše da 𝑑 nije prost, na osnovu pretpostavke znamo danjega deli neki delilac 𝑡 iz intervala [2, 𝑑) (jer je sigurno različit od 0 i 1).Svi brojevi manji ili jednaki od 𝑛 koji imaju delioce iz intervala [2, 𝑑) suna osnovu pretpostavke su već precrtani. Oni brojevi koje deli 𝑑, deli i𝑡, pa su na osnovu pretpostavke oni već precrtani. Dakle, invarijanta seodržava i kada se ništa ne uradi.Ako u nizu prost piše da je 𝑑 prost, precrtavaju se brojevi 𝑑2, 𝑑2 + 1,…tj. svi brojevi koji su manji ili jednaki od 𝑛 i deljivi su brojem 𝑑, osimbroja 𝑑 (njemu 𝑑 nije pravi delilac). Brojevi 2𝑑, 3𝑑, …, (𝑑 − 1)𝑑, suveć precrtani ranije, jer svi imaju delioce koji su manji od 𝑑. Ovim seeksplicitno invarijanta proširuje i na njih i ostaje održana.

Teorema: Nakon završetka funkcije nisu precrtani tačno brojevi koji su prosti.Pošto se petlja završila, važi da je 𝑑 > ⌊√𝑛⌋, a pošto na osnovu invarijante

49

Page 50: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

znamo da je 𝑑 ≤ ⌊√𝑛⌋ + 1, važi da je 𝑑 = ⌊√𝑛⌋ + 1. Na osnovu invarijanteznamo da su precrtani 0, 1 i tačno svi oni brojevi manji ili jednaki od 𝑛 kojiimaju delioce iz intervala [2, √𝑛]. Pošto iz 𝑥 ≤ 𝑛 sledi

√𝑥 ≤ √𝑛, znamo da suprecrtani tačno oni brojevi 𝑥 koji imaju prave delioce iz intervala [2, √𝑥], štoznači da su neprecrtani ostali tačno prosti brojevi (ponovo na osnovu teoremena osnovu koje znamo da ako broj 𝑥 ima pravog delioca iz intervala [√𝑥, 𝑥) ondaima i pravog delioca iz intervala [2, √𝑥], pa neprecrtani brojevi nemaju pravihdelioca).Analiza složenosti je komplikovanija i zahteva određeno (doduše veoma elemen-tarno) poznavanje teorije brojeva. Procenimo broj izvršavanja tela unutrašnjepetlje. U početnom koraku spoljne petlje precrtava se oko 𝑛

2 elemenata. Unarednom, oko 𝑛

3 . U narednom koraku je broj 4 već precrtan, pa se ne precr-tava ništa. U narednom se precrtava oko 𝑛

5 , nakon toga opet ništa, zatim 𝑛7 itd.

U poslednjem koraku se precrtava oko 𝑛√𝑛 elemenata. Dakle, broj precrtavanjaje najviše

𝑛2 + 𝑛

3 + 𝑛5 + … + 𝑛√𝑛 = 𝑛 ⋅

⎛⎜⎜⎜⎝

∑𝑑 prost,

𝑑≤√𝑛

1𝑑

⎞⎟⎟⎟⎠

Broj je zapravo i manji, jer prilikom precrtavanja u unutrašnjoj petlji precr-tavanje ne krećemo od 𝑑, već od 𝑑2, ali za potrebe lakšeg određivanja gornjegranice složenosti koristićemo prethodnu ocenu.Još je veliki Ojler otkrio da je zbir 𝐻(𝑚) = 1+1/2+1/3+…+1/𝑚 = ∑𝑑≤𝑚

1𝑑

(takozvani harmonijski zbir) asimptotski jednak log 𝑚 (razlika između ove dvefunkcije teži takozvanoj Ojler-Maskeronijevoj konstanti 𝛾 ≈ 0.5772156649) -samim tim znamo da taj zbir divergira. Takođe, otkrio je da kada se sabiranjevrši samo po prostim brojevima, tada se zbir ponaša kao logaritam harmonijskogzbira, tj. kao log log 𝑚 (pa je i on divergentan). Dakle, u našem primeru možemozaključiti da je broj precrtavanja jednak 𝑛 ⋅ log log √𝑛. Pošto je log log √𝑛 =log log 𝑛 1

2 = log ( 12 log 𝑛) = log 1

2 + log log 𝑛, važi da je složenost Eratostenovogsita 𝑂(𝑛⋅log log 𝑛). Iako nije linearna, funkcija log log 𝑛 toliko sporo raste, da seza sve praktične potrebe Eratostanovo sito može smatrati linearnim u odnosuna 𝑛 (što je opet dosta sporije samo od ispitivanja da li je broj 𝑛 prost, što imasloženost 𝑂(√𝑛)).Brojanje prostih brojeva manjih od 107 (ima ih 664579) korišćenjem zasebneprovere svakog od brojeva i osnovne implementacije provere da li je broj prosttraje oko 4, 718 sekundi, a korišćenjem ubrzane implementacije koja proveravasamo brojeve oblika 6𝑘 − 1 i 6𝑘 + 1 traje oko 1, 609 sekundi. Sa druge strane,Eratostenovo sito traje samo oko 0, 143 sekunde.

Binarna pretraga Sprovođenje binarne umesto linearne pretrage takođe jeoptimizacija koja spada u kategoriju odsecanja prilikom pretrage. Naime, zah-

50

Page 51: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

valjujući svojstvu uređenosti elemenata niza, znamo da neke delove niza nemapotrebe pretraživati, jer ne mogu da sadrže rešenje.Problem: Proveriti da li u strogo rastuće sortiranom nizu brojeva postoji nekapozicija 𝑖 takva da je 𝑎𝑖 = 𝑖. Ako postoji vratiti najmanju takvu, a ako nepostoji, vratiti −1.Direktno rešenje je zasnovano na linearnoj pretrazi i ne koristi činjenicu da jeniz sortiran.

bool fiksnaTacka(int a[], int n) {for (int i = 0; i < n; i++)

if (a[i] == i)return i;

return -1;}

Mnogo efikasnije rešenje zasnovano je na binarnoj pretrazi. Ako je 𝑎𝑖 = 𝑖,tada je 𝑎𝑖 − 𝑖 = 0. Pokažimo da je niz 𝑎𝑖 − 𝑖 neopadajući. Posmtarajmo dvaelementa 𝑎𝑖 i 𝑎𝑗 na pozicijama na kojima je 0 ≤ 𝑖 < 𝑗 < 𝑛. Pošto je niz 𝑎 strogorastući, važi da je 𝑎𝑖+1 > 𝑎𝑖, pa je 𝑎𝑖+1 ≥ 𝑎𝑖 + 1. Slično je 𝑎𝑖+2 > 𝑎𝑖+1, pa je𝑎𝑖+2 ≥ 𝑎𝑖+1 + 1 ≥ 𝑎𝑖 + 2. Nastavljanjem ovog rezona važi da je 𝑎𝑗 ≥ 𝑎𝑖 + 𝑗(formalno, ovo je moguće dokazati indukcijom). Zato je 𝑎𝑗 − 𝑗 ≥ 𝑎𝑖 ≥ 𝑎𝑖 − 𝑖.Rešenje, dakle, možemo odrediti tako što binarnom pretragom proverimo da liniz 𝑎𝑖 − 𝑖 sadrži nulu i ako sadrži, tada je rešenje prva pozicija na kojoj se tanula nalazi.

int fiksnaTacka(int[] a, int n) {// sprovodimo binarnu pretragu - ako trazeni element a[i] = i// postoji, on se nalazi u intervalu [l, d]// invarijanta:// za sve elemente u intervalu [0, l) važi da je a[i] < i// za sve elemente u intervalu (d, n) važi da je a[i] >= iint l = 0, d = n-1;// dok interval [l, d] nije prazanwhile (l <= d) {

// pronalazimo sredinu intervalaint s = l + (d - l) / 2;if (a[s] < s)

// najmanji element takav da je a[i]=i može biti samo desno od sl = s + 1;

else// najmanji element takav da je a[i]=i može biti samo levo od sd = s - 1;

}// svi elementi levo od l su takvi da je a[l] < l// ako postoji element takav da je a[l] = l, najmanji takav može// biti jedino na poziciji iif (l < n && a[l] == l)

return l;

51

Page 52: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

elsereturn -1;

}

Prodiskutujmo binaru pretragu iz ugla odsecanja. Naime, zahvaljujući činjenicida je niz 𝑎𝑖 − 𝑖 sortiran, kada za neko 𝑠 utvrdimo da je 𝑎𝑠 < 𝑠, znamo da senajmanje 𝑖 takvo da je 𝑎𝑖 = 𝑖 ne može nalaziti levo od pozicije 𝑠 (niti na njoj).Zbog toga možemo da preskočimo (isečemo) pretragu svih pozicija levo od 𝑠 ida postavimo promenljivu l na vrednost 𝑠 + 1. Slično, ako je 𝑎𝑠 ≥ 𝑠 onda senajmanja pozicija takva da je 𝑎𝑖 = 𝑖 ne može nalaziti na pozicijama iza 𝑠, pamožemo da preskočimo (isečemo) pretragu svih pozicija desno od 𝑠 i postavimopromenljivu d na vrednost 𝑠 + 1.

Maksimalni zbir segmenta Vratimo se problemu određivanja vrednostimaksimalnog zbira nekog segmenta niza. Prethodna dva algoritma koja smoprikazali se mogu slobodno nazvati trivijalnim, jer se do njih dolazi priličnodirektno i veoma jednostavno im se i dokazuje korektnost i analizira složenost.Međutim, oni su prilično neefikasni za rešavanje ovog problema. Značajnounapređenje možemo dobiti kada primetimo da veliki broj segmenata uopštene moramo da obrađujemo, jer iz nekih drugih razloga znamo da njihov zbir nemože biti maksimalan.Posmatrajmo niz -2 3 2 -3 -3 -2 4 5 -8 3 i zbirove svih njegovih nepraznihsegmenata (u 𝑖-tom redu i 𝑗-toj koloni se nalazi zbir segmenta [𝑖, 𝑗]).-2 1 3 0 -3 -5 -1 4 -4 -1

3 5 2 -1 -3 1 6 -2 12 -1 -4 -6 -2 3 -5 -2

-3 -6 -8 -4 1 -7 -4-3 -5 -1 4 -4 -1

-2 2 7 -1 24 9 1 4

5 -3 0-8 -5

3

Razmotrimo bilo koji segment koji počinje negativnim brojem. Takav segmentonda ne može imati maksimalni zbir, pošto se izostavljanjem tog prvog neg-ativnog broja dobija veći zbir. Ovo zapažanje se može uopštiti. Ukoliko nizpočinje prefiksom negativnog zbira, iz istog razloga, nijedan segment čiji je onprefiks ne može imati maksimalni zbir. Otud, pri inkrementalnom proširivanjuintervala udesno, čim se ustanovi da je tekući zbir negativan, moguće je prek-inuti dalje proširivanje.Na primer, čim vidimo da je prvi element -2, možemo prekinuti dalju obraduelemenata prve vrste, jer će svi elementi druge vrste sigurno biti za dva veći negoodgovarajući elementi prve vrste. Slično, u drugoj vrsti, kada se prilikom proširi-vanja segmenta (koji počinje na drugoj poziciji) dobije negativan parcijalni zbir

52

Page 53: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

-1, možemo zaključiti da će segmenti u ostatku niza (iz koga je izbačen tajsegment) imati veći parcijalni zbir. Onda možemo prekinuti sa obradom dužihsegmenata koji počinju na toj poziciji, jer smo sigurni da će za svaki od njihkasnije postojati segment sa većim zbirom koji se dobija izostavljanjem prefiksa3 2 -3 -3 (jer je njegov zbir -1). Zaista, od preostalih zbirova -3 1 6 -2 1 udrugoj vrsti za jedan su veći zbirovi -2 2 7 -1 2 u šestoj vrsti koji su dobijeniizostavljanjem tog prefiksa. Obratimo pažnju da prekid unutrašnje petlje naovaj način uzrokuje da se maksimalna vrednost u tekućoj vrsti ne mora uopštenaći. Petlja koja obrađuje drugu vrstu će biti prekinuta kada je tekuća vrednostmaksimuma 5 iako je maksimum te vrste 6. Sigurni smo da će u nekoj narednojvrsti postojati veća vrednost od te najveće (zaista, u šestoj vrsti se javlja 7), panam nalaženje stvarnog maksimuma u tekućoj vrsti uopšte nije neophodno.

int max = 0;int i = 0;while (i < n) {int z = 0;int j;for (j = i; j < n; j++) {

z += a[j];if (z < 0)

break;if (z > max)

max = z;}i++;

}

Iako se na ovaj način može preskočiti razmatranje nekih segmenata, u najgoremslučaju složenost nije smanjena. Na primer, u slučaju da su elementi niza strogopozitivni, zbir nikad ne postaje negativan i izvršavanje je i dalje kvadratnesloženosti. Primetimo da u tom slučaju maksimalni zbir biva nađen za 𝑖 = 0 i𝑗 = 𝑛−1. Nakon toga se, uvećavanjem indeksa 𝑖, zbir smanjuje pošto se svakimskraćivanjem segmenta sleva izostavlja neki pozitivan broj koji doprinosi zbiru.I ovo zapažanje se može uopštiti. Ne samo što je nepoželjno skratiti intervalsleva za neki pozitivan broj, već je nepoželjno skratiti ga za bilo koji prefiks čijije zbir pozitivan. Pitanje je dokle takvi prefiksi sežu? Bar do elementa čijimobuhvatanjem dobijamo prvi negativan prefiks. Otud segment maksimalnogzbira ne može počinjati ni na jednoj poziciji između tekuće početne pozicije iprve pozicije na kojoj zbir postaje negativan.Na primer, u navedenom primeru maksimalni segment ne može počinjati natrećoj poziciji, jer se proširivanjem nalevo i dodavanjem elementa 3 sa drugepozicije dobijaju sigurno zbirovi koji su veći za tri. Elementi druge vrste su za3, 5 tj. 2 su veći od odgovarajući elemenata treće, četvrte i pete vrste, pa te trivrste uopšte nema potrebe razmatrati.Zahvaljujući ovom zapažanju, nije neophodno uvećavati promenljivu 𝑖 za jedan,

53

Page 54: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

već je moguće nastaviti iza elementa čijim je uključivanjem suma postala nega-tivna.

int max = 0;int i = 0;while (i < n) {int z = 0;int j;for (j = i; j < n; j++) {

z += a[j];if (z < 0)

break;if (z > max)

max = z;}i = j + 1;

}

Ocenimo složenost ovog algoritma. Neko bi naivno mogao pomisliti da ćesloženost algoritma biti bar kvadratna, jer se u programu javljaju dve ugnežđenepetlje. Međutim, prilično lako možemo pokazati da je složenost ovog algoritma𝑂(𝑛). Naime, lako se primećuje da se vrednost obe brojačke promenljive 𝑖 i 𝑗stalno uvećava i nikada ne smanjuje. Naime, kada se prekine iteracija unutrašnjepetlje, promenljiva 𝑖 se postavlja na 𝑗 + 1 i nova runda unutrašnje petlje krećeod 𝑗 + 1, što je veće od vrednosti 𝑗 na kojoj se u prethodnoj rundi stalo. Poštose postupak sigurno prekida kada obe promenljive dostignu vrednost 𝑛, one semogu uvećati najviše 2𝑛 puta, tako da je složenost 𝑂(𝑛).Algoritme u kojima održavamo dve pozicije koje se kroz niz kreću samo u jednomsmeru nazivamo tehnikom dva pokazivača i oni su po pravilu linearne složenosti(ovaj algoritam je tog tipa).Ključni način dobijanja na efikasnosti u ovom algoritmu je isti kao i u slučajuispitivanja prostih brojeva - izvršili smo odsecanje pretrage, tj. na osnovumatematičkih argumenata eliminisali smo potrebu da tokom pretrage prover-avamo mnoge slučajeve. Tu tehniku ćemo intenzivno koristiti i u nastavku.Podsetimo se, prvi algoritam, složenosti 𝑂(𝑛3) nasumično generisan niz sa 10hiljada elemenata između -3 i 3 obrađuje za 27, 051 sekundi, inkrementalnaoptimizacija to skraćuje na 0, 074 sekunde, dok se odsecanjem vreme spušta na0, 004 sekunde.

Pretprocesiranje radi efikasnije pretrage

U nekim slučajevima je potrebno izvršiti određenu vrstu pretprocesiranja, dabi se u kasnijoj fazi algoritam mogao efikasnije izvršiti. Razmotrimo nekolikoprimera.

54

Page 55: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Provera duplikata u nizu Problem: Definisati funkciju koja efikasnoproverava da li u datom nizu celih brojeva ima duplikata i proceniti jojsloženost.Naivni način je da se ispita svaki par elemenata.

bool imaDuplikata(int a[], int n) {for (int i = 0; i < n; i++)

for (int j = i+1; j < n; j++)if (a[i] == a[j])

return true;return false;

}

Složenost najgoreg slučaja ovog pristupa je prilično očigledno kvadratna (parovaima (𝑛2)). U najgorem slučaju (kada nema duplikata) u prvom koraku spoljašnjepetlje vrši se 𝑛−1 koraka unutrašnje petlje, u drugom 𝑛−2 koraka, itd. Ukupnose, dakle, vrši (𝑛−1)+(𝑛−2)+…+1 koraka, što je opet 𝑛(𝑛−1)

2 , te je složenost𝑂(𝑛2).Efikasniji algoritam dobijamo ako prvo niz sortiramo. Ako u nizu ima duplikata,nakon sortiranja oni će se naći na susednim pozicijama, pa da bismo proveriliduplikate možemo samo proveriti susedne elemente niza.

bool imaDuplikata(int a[], int n) {sort(a, a+n); // bibliotečka funkcija za sortiranjefor (int i = 0; i < n-1; i++)

if (a[i] == a[i+1])return true;

return false;}

Sortiranje smo sproveli korišćenjem bibliotečke funkcije i njena složenost je𝑂(𝑛 log 𝑛). Nakon toga, pretraga se vrši u vremenu 𝑂(𝑛). Dakle, ukupnasloženost je veća od ove dve, a to je 𝑂(𝑛 log 𝑛). Videćemo kasnije da zadatakmožemo rešiti i pomoću specijalizovanih struktura podataka u istoj asimptotskojsloženosti, međutim, direktna rešenja, sa običnim nizovima su obično efikasnija(za konstantni faktor).Za niz koji sadrži redom sve brojeve od 0 do 99999 prvi algoritam utvrđuje daduplikata nema za oko 2, 042 sekunde, a drugi za oko 0, 012 sekundi.Ključni dobitak na efikasnosti u ovom primeru je usledio ponovo time što jena osnovu matematičkog argumenta eliminisana potreba za proverom velikogbroja slučajeva. Međutim, za razliku od prethodnih primera gde je to učin-jeno na osnovu analize originalnog ulaza, ovde je ulaz morao biti preprocesirantako da odsecanje bude moguće. Najčešći oblik pretprocesiranja koji dopuštarazličite oblike kasnijih odsecanja prilikom analize elemenata niza je sortiranje.Veoma interesantan savet prilikom dizajna algoritama je “ako ne znaš odakleda kreneš, probaj da sortiraš”. Zbog njihovog značaja primenom sortiranja ibinarne pretragom ćemo se baviti posebno u narednim delovima ovog kursa.

55

Page 56: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Zbirovi segmenata Problem: U svakom minutu tokom dugog vremenskogperioda beležen je broj automobila koji su prošli naplatnu rampu na auto-putu.Napisati program koji može efikasno da daje odgovore na pitanje koliko jeukupno automobila prošlo u datom intervalu [𝑙𝑖, 𝑑𝑖], gde je 𝑙𝑖 početni, a 𝑑𝑖krajnji minut koji nas zanima.Zadatak se svodi na to da se za dati niz celih brojeva efikasno izračunavajuzbirovih njegovih segmenata.Direktan način je da se nakon učitavanja niza za svaki segment zbir iznovaračuna u petlji. Ako niz ima 𝑛 elemenata i želimo da izračunamo zbirove nje-govih 𝑚 segmenata, ukupna složenost bila bi 𝑂(𝑛 ⋅ 𝑚).Jedan od načina da se zadatak efikasno reši je to da se primeti da se svakizbir segmenta može predstaviti kao razlika dva zbira prefiksa niza. Naime, zbirsegmenta određenog pozicijama iz intervala [𝑙, 𝑑] jednak je:

𝑑∑𝑖=𝑙

𝑎𝑖 =𝑑

∑𝑖=0

𝑎𝑖 −𝑙−1∑𝑖=0

𝑎𝑖

Na primer, ako su elementi niza 1, 3, 2, 4, 1, 5, tada je niz zbirova prefiksa jednak0, 1, 4, 6, 10, 11, 16. Pošto pozicije u nizu brojimo od nule, zbir elemenata napozicijama iz intervala [2, 4] jednak je 2 + 4 + 1 = 7. Njega možemo dobiti akood broja 11 (koji je dobijen kao zbir prefiksa 1 + 3 + 2 + 4 + 1 oduzmemo broj4 (koji je dobijen ka zbir prefiksa 1 + 3).Dakle, ako znamo zbirove svih prefiksa, zbir svakog segmenta možemo izračunatiu vremenu 𝑂(1). Zbirove prefiksa, naravno, možemo računati inkrementalno,tako da je vreme potrebno za njihovo izračunavanje 𝑂(𝑛). Ukupna složenost jeonda 𝑂(𝑛 + 𝑚), gde je 𝑚 broj segmenata čiji se zbir računa.

int n;cin >> n;// zbirPrefiksa[i] - zbir elemenata iz intervala [0, i)vector<int> zbirPrefiksa(n+1);zbirPrefiksa[0] = 0;for (int i = 0; i < n; i++) {int x;cin >> x;zbirPrefiksa[i+1] = zbirPrefiksa[i] + x;

}int m;cin >> m;for (int i = 0; i < m; i++) {

int l, d;cin >> l >> d;cout << zbirPrefiksa[d+1] - zbirPrefiksa[l] << endl;

}

56

Page 57: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Ako broj automobila za svaki minut unesemo na jedan histogram, tada seizračunavanje zbira segmenta svodi na izračunavanje površine dela histograma.Površina dela histograma od pozicije 𝑙𝑖 do pozicije 𝑑𝑖 se svodi na razliku površinadela histograma od početka (tj. pozicije 0) do pozicije 𝑑𝑖 i dela histograma odpočetka zaključno sa pozicijom 𝑙𝑖−1. Ovo direktno odgovara Njutn-Lajbnicovojformuli koja se koristi za izračunavanje određenih integrala.

** *

* * ** * * *

* * * * * *1 3 2 4 1 5

Pretprocesiranje je u ovom primeru podrazumevalo izračunavanje pomoćnihpodataka koji su omogućili efikasnije kasnije izvršavanje algoritma. Prefiksnizbirovi su tehnika koja se često koristi prilikom izgradnje efikasnih algoritama ijoš primera njene upotrebe biće dato kasnije.

Maksimalni zbir segmenta Prefiksne sume možemo primeniti i na prob-lem određivanja maksimalnog zbira segmenta i tako dobiti algoritam složenosti𝑂(𝑛2) (kao što smo rekli, ovaj problem ćemo kasnije rešiti i efikasnije).

int n;cin >> n;

vector<int> zbirPrefiksa(n+1);zbirPrefiksa[0] = 0;for (int i = 0; i < n; i++) {int x; cin >> x;zbirPrefiksa[i+1] = zbirPrefiksa[i] + x;

}

int maks_zbir = 0;for (int i = 0; i < n; i++)

for (int j = i; j < n; j++) {int zbir = zbirPrefiksa[j+1] - zbirPrefiksa[i];if (zbir > maks_zbir)maks_zbir = zbir;

}

cout << maks_zbir << endl;

Niz razlika Problem: Dat je niz dužine 𝑛 popunjen nulama. Nakon togase 𝑚 puta izvršavaju upiti koji podrazumevaju da se svi elementi na pozicijamaod 𝑎 do 𝑏 uvećavaju za vrednost 𝑘. Napisati program koji ispisuje stanje nizanakon izvršavanje svih upita.

57

Page 58: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Ova tehnika je veoma slična (zapravo inverzna) tehnici prefiksnih suma, pa jesada izlažemo.Rešenje grubom silom je jednostavno.

int n; cin >> n;vector<int> niz(n, 0);int m; cin >> m;for (int i = 0; i < m; i++) {

int a, b, k;cin >> a >> b >> k;for (int j = a; j <= b; j++)

niz[j] += k;}for (int i = 0; i < n; i++)

cout << niz[i] << endl;

U najgorem slučaju, kada je uvek 𝑎 = 0 i 𝑏 = 𝑛 − 1, složenost ovog algoritma je𝑂(𝑚 ⋅ 𝑛).Umesto niza možemo pamtiti razlike njegovih susednih elemenata. Neka je𝑑0 = 𝑛𝑖𝑧0 i 𝑑𝑖 = 𝑛𝑖𝑧𝑖−𝑛𝑖𝑧𝑖−1, za 1 ≤ 𝑖 < 𝑛. Kada se svi elementi na pozicijamaod 𝑎 do 𝑏 uvećaju za 𝑘, u nizu razlika se promene samo dva elementa. Element𝑑𝑎 se poveća za 𝑘, dok se, ako je 𝑏 < 𝑛 − 1, element 𝑑𝑏+1 smanji za 𝑘. Zaista,𝑑𝑎 = 𝑛𝑖𝑧𝑎 −𝑛𝑖𝑧𝑎−1, pa pošto se 𝑛𝑖𝑧𝑎 uvećao za 𝑘, a 𝑛𝑖𝑧𝑎−1 se nije promenio 𝑑𝑎se povećva za 𝑘. Slično, ako je 𝑏 < 𝑛−1, tada je 𝑑𝑏+1 = 𝑛𝑖𝑧𝑏+1 −𝑛𝑖𝑧𝑏, pa poštose 𝑛𝑖𝑧𝑏+1 nije promenio dok se 𝑛𝑖𝑧𝑏 uvećao za 𝑘, razlika 𝑑𝑏+1 se smanjuje za𝑘. Kada se sve operacije izvrše nad nizom razlika, originalni niz možemo veomajednostavno rekonstruisati nalaženjem prefiksnih suma niza razlika.

int n; cin >> n;vector<int> d(n, 0);int m; cin >> m;for (int i = 0; i < m; i++) {

int a, b, k;cin >> a >> b >> k;d[a] += k;if (b < n-1)

d[b] -= k;}

int zbir = 0;for (int i = 0; i < n; i++) {

zbir += d[i];cout << zbir << endl;

}

Originalno popunjavanje niza razlika nulama zahteva vreme 𝑂(𝑛). Nakon togaizvršavamo 𝑚 upita, međutim, izvršavanje svakog upita vrši se u složenosti𝑂(𝑚). Na kraju niz rekonstruišemo nalaženjem prefiksnih suma u vremenu

58

Page 59: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

𝑂(𝑛). Ukupno vreme izvršavanja je, dakle 𝑂(𝑛 + 𝑚), što je mnogo efikasnijenego algoritam koji radi grubom silom.Prikažimo rad ovog algoritma na jednom jednostavnom primeru.a b k niz d--- --- --- ------------- --------------

0 0 0 0 0 0 0 0 0 0 0 02 4 5 0 0 5 5 5 0 0 0 5 0 0 -50 5 2 2 2 7 7 7 2 2 0 5 0 0 -51 3 1 2 3 8 8 7 2 2 1 5 0 -1 -50 2 1 3 4 9 8 7 2 3 1 5 -1 -1 -53 5 1 3 4 9 9 8 3 3 1 5 0 -1 -5

Primetimo da smo u ovom rešenju promenili reprezentaciju podataka tj. upotre-bili smo suštinski drugačiju strukturu podataka. Ovo je zahtevalo pretprocesir-anje (određivanje početnih vrednosti niza razlika) koje je bilo trivijalno, jersu po pretpostavci u polaznom nizu sve nule i zatim postprocesiranje (koje jepodrazumevalo određivanje prefiksnih suma).Niz razlika i prefiksne sume su međusobno inverzni. Dok prefiksne sume, kaošto smo videli, odgovaraju određenom integralu, razlike odgovaraju pronalaženjuizvoda.

Broj tačaka u svakom trouglu Problem: Dato je 𝑛 tačaka u ravni uopštem položaju (nikoje dve nisu kolinearne). Napisati program koji određujekoliko svaki od trouglova koji se mogu formirati od tih tačaka sadrži tačaka.Trouglova ima (𝑛3) što je 𝑂(𝑛3). Ako bismo za svaki trougao grubom silompronalazili tačke koje mu pripadaju (tako što bismo analizirali jednu po jednutačku) dobili bismo algoritam složenosti 𝑂(𝑛4).Zadatak možemo rešiti i u ukupnoj složenosti 𝑂(𝑛3), ako izvršimo narednopretprocesiranje. Za svaku duž i svaku tačku jednostavno možemo proveritida li se tačka nalazi ispod te duži (x koordinate treba da joj budu između xkoordinata krajnjih tačaka duži i tačka treba da bude ispod prave koja sadržitu duž, što se lako može proveriti ispitivanjem pripadnosti tačke poluravni tj.ispitivanjem važenja jedne linearne nejednakosti). Tokom pretprocesiranja ćemoza svaku duž izračunati broj tačaka koje se nalaze ispod nje. Pošto postoji (𝑛2)tj. 𝑂(𝑛2) duži, pretprocesiranje se može uraditi u vremenu 𝑂(𝑛3). Nakon togaćemo obrađivati jedan po jedan trougao. Pretpostavimo da se središnja tačkatrougla 𝐴 (gledajući x koordinate temena) nalazi iznad donje ivice 𝐵𝐶. Tadase broj tačaka u trouglu može dobiti tako što se od ukupnog broja tačaka ispodduži 𝐴𝐵 i 𝐴𝐶 oduzme ukupan broj tačaka ispod duži 𝐵𝐶. Ako tačka 𝐴 ležiispod duži 𝐵𝐶, tada se broj tačaka može dobiti tako što se od broja tačaka ispodduži 𝐵𝐶 oduzme zbir broja tačaka ispod duži 𝐴𝐵 i ispod duži 𝐴𝐶. Pošto zasvaki trougao broj tačaka možemo izračunati u vremenu 𝑂(1) ukupna složenostovakvog algoritma će biti 𝑂(𝑛3).

59

Page 60: Čas 2.1, 2.2, 2.3 - Efikasnost i složenost algori- tamapoincare.matf.bg.ac.rs/~filip/asp/02_asp-slozenost.pdf · Aritmetički niz Gausu se pripisuje da je još kao dete izračunao

Odabir struktura podataka

Odabir adekvatnih struktura podataka od krucijalne je važnosti za ukupnuefikasnost algoritama i značajan deo kursa biće posvećen opisu različitih struk-tura podataka. Pokažimo samo jedan veoma elementaran primer, obrađen uokviru kursa P2.Problem: Interpretator tokom rada svakoj promenljivoj pridružuje njenutekuću vrednost. Implementirati tablicu simbola kojom se ta funkcionalnostefikasno može ostvariti. Pretpostaviti da su nazivi promenljive proizvoljneniske, a da su vrednosti celobrojne.Jedna moguća implementacija je ona u kojoj se čuva niz parova koji se sastoje odimena promenljive i vrednosti. Kada se naiđe na definiciju promenljive (dodeluvrednosti), tada se taj niz pretražuje da bi se utvrdilo da li ta promenljiva većpostoji. Ako postoji, vrednost joj se ažurira, a ako ne postoji, informacija otoj promenljivoj se dodaje na kraj niza. Pošto su promenljive nisu sortirane,pretraga mora biti linearna i složenost joj je 𝑂(𝑛), ako je 𝑛 broj promenljivih,što je prilično neefikasno.Čuvanje podataka u nizu sortiranom na osnovu naziva promenljivih bi dovelodo brže pretrage (moguće bi bilo vršiti binarnu pretragu), međutim, umetanjenove promenljive na njeno mesto u sortiranom nizu zahtevalo bi ponovo vreme𝑂(𝑛).Jedno rešenje, koje je obrađeno u kursu P2, je da se promenljive čuvaju ubinarnom drvetu sortiranom na osnovu naziva promenljivih. Ako se održavabalansirano binarno drvo (što je moguće, mada prevazilazi okvire početnih kur-seva), tada bi složenost i pretrage i umetanja mogla biti 𝑂(log 𝑛). Alternativno,moguće je upotrebiti i heš-tablicu o čemu će više biti reči u nastavku ovog kursa.

60