180 likes | 571 Views
Algoritmu un programmu sarežģītība. Datorzinātnes pamati. Programmu ātrdarbība – ievada apsvērumi. Algoritmiska problēma – uzdevums : - pēc dota x no fiksētas klases X noteikt, vai x piemīt īpašība p, vai arī - pēc dota x no fiksētas klases X aprēķināt noteiktas funkcijas f vērtību uz x, utt…
E N D
Algoritmu un programmu sarežģītība Datorzinātnes pamati
Programmu ātrdarbība – ievada apsvērumi Algoritmiska problēma – uzdevums:- pēc dota x no fiksētas klases X noteikt, vai x piemīt īpašība p, vai arī- pēc dota x no fiksētas klases X aprēķināt noteiktas funkcijas f vērtību uz x, utt… Eksistē tādas algoritmiskas problēmas, kurām nav algoritma, kas to risina:- pēc dotas Tjūringa mašīnas M noteikt, vai M apstājas, sākot darbu ar lentu, uz kuras uzrakstīts M numurs #M,- pēc dotas programmas c valodā IMP un dotas specifikācijas <p,q>, noteikt vai c ir daļēji korekta attiecībā pret <p,q>, utt … Tagad skatīsimies situāciju, kad algoritms noteiktas problēmas risināšanai eksistē. Jautājums –cik ilgi šāda algoritma implementācija būs jādarbina, lai iegūtu rezultātu? Cik lieli resursi nepieciešami algoritma realizācijai? Ja algoritms strādā pārāk ilgi (piemēram, 1000 gadus uz modernākajiem datoriem), tad šāda algoritma esamībai praktiski nav lielas nozīmes. Resursi – laiks, atmiņa. Algoritma laika un atmiņas sarežģītība. a) Varam vērtēt konkrēta algoritma sarežģītību (resursu patēriņu, ātrdarbību) – ja viens algoritms dotās problēmas risināšanai strādā pārāk ilgi, varbūt varam ņemt citu algoritmu? b) Var būt problēmas, kuras ir tik sarežģītas, ka tām neeksistē vienkāršāki (resursus mazāk patērējoši) algoritmi, kas tās risina. Algoritmiskas problēmas sarežģītība.
Ievada piemērs: Fibonači skaitļi Fibonači skaitļu virkne: f(0) = 1, f(1) = 1, f(n) = f(n-1) + f(n-2), ja n 2. Pazīstama skaitļu virkne: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, … Divas programmas, kas rēķina n-to Fibonači skaitli: functionfib1( n : int ) : intbegin if n >= 2 then return (fib1(n-1) + fib1(n-2)) else return 1;end fib1 functionfib2( n : int ) : intdeclare x : int, y : int, z : intbegin x := 1; y :=1; while n > 2 do begin n := n-1; z := x + y; x := y; y := z; end return y;end fib2 Pamēģiniet implementēt abus algoritmus, un izpildīt fib1(5), fib2(5), fib1(20), fib2(20), fib1(40), fib2(40), fib1(1000), fib2(1000).Vai redzat atšķirību?
Algoritma laika sarežģītība – kā novērtēt? Algoritma A izpildes laiks R(A,x) uz datiem x – elementāro operāciju (soļu) skaits, ko A izpilda, lai no x iegūtu rezultātu. Elementāro operāciju skaits ir atkarīgs no tā, kas ir elementāra operācija – kāda ir tā abstraktā mašīna, kuras komandu terminos algoritms ir izteikts. Tjūringa mašīnai: izpildīto qiakqjare komandu skaits. RAM mašīnai: izpildīto LOAD, SAVE, ADD, SUB, JUMP, utt. komandu skaits Valoda IMP: varam rēķināt dažādi: - piešķiršanas operatoru un zarošanās un cikla nosacījumu pārbaudes reižu skaits; - varam skaitīt klāt arī soļo skaitu, kas nepieciešami aritmētisko un loģisko izteiksmju vērtību aprēķināšanai Valodas C++, PASCAL: arī var būt dažādi “soļu skaitīšanas” veidi. Lai runātu par algoritma sarežģītību, ir katru reizi jāprecizē, kāda ir komandu sistēma, kādā šī sarežģītība tiek rēķināta. Tomēr: Bieži vien sarežģītība tiek novērtēta “ar precizitāti līdz konstantam reizinātājam”, no šī viedokļa komandu sistēmas, kurās programmu laika sarežģītība atšķiras ne vairāk kā par konstantu reizinātāju, ir uzskatāmas par ekvivalentām. Konkrētās novērtējuma situācijās var izmantot intuitīvu operāciju elementaritātes jēdzienu, precizējot to tad, kad nepieciešams.
Algoritma laika sarežģītība – kā novērtēt? (2) Fiksēta abstraktā mašīna – elementāro operāciju kopums, kas izmantojamas algoritmu realizācijai. Algoritma A izpildes laiks R(A,x) uz datiem x – elementāro operāciju (soļu) skaits, ko A izpilda, lai no x iegūtu rezultātu. Kā vērtēt algoritma izpildes laiku “vispār”, nefiksējot konkrētu argumenta vērtību x? Kā salīdzināt divus algoritmus, kas risina vienu problēmu, kurš no tiem darbojas “ātrāk”? Sliktākā gadījuma sarežģītība: R(A,n) – algoritma A maksimālais izpildes laiks, kādā A sasniedz rezultātu uz datiem, kuru apjoms nepārsniedz n. R(A,n) = max { R(A,x) | size(x) n } R(A,n) katram algoritmam A ir funkcija no n – ieejas datu apjoma. Var salīdzināt 2 algoritmiem, kādas ir attiecības starp funkcijām R(A,n) un R(B,n) konkrētām n vērtībām, kā arī, kas notiek, ja n . Vidējā laika sarežģītība: RV(A,n) = ( { R(A,x) | size(x) = n } ) / (card { x | size(x) = n } )
Kāpēc būtiska sarežģītības kārta? Aplūkojam 5 algoritmus: A1 – A5 kādas problēmas risināšanai. Pieņemsim, ka R(A1,n) = c1 nR(A2, n) = c2 n log n R(A3, n) = c3 n2R(A4, n) = c4 n3R(A5, n) = c5 2n (c1 – c5 kaut kādas konstantes). Pieņemsim, ka ar šodienas datoru jaudām katrs no šiem algoritmiem spēj 1 stundas laikā atrisināt problēmu apjomā a. Jautājums – ar cik liela apjoma problēmām katrs no šiem algoritmiem tiks galā 1 stundas laikā, ja dators darbosies 10 reizes ātrāk? A1 c1 n 10 a A2 c2 n log2 n 10 a A3 c3 n2 3.16 a A4 c4 n3 2.15 a A5 c5 2n a + 3.3 - nepraktiski algoritmi. Eksponenciālas sarežģītības gadījumā datora jaudas palielināšana dod ļoti maz, ko. Pietiekami lieliem n algoritms ar mazāku sarežģītības kārtu strādās ātrāk. Jāņem vērā, ka arī tad, ja R(A,n) ir asimptotiski labāks par R(B,n), tomēr algoritms B var izrādīties labāks (ātrāks) par A daudzās praktiskās situācijās.
Sarežģītības kārta: definīcijas Dotas funkcijas f: N N, g: N N. Sakām, ka f = o(g), ja limn f(n) / g(n) = 0 ( f(n) ir asimptotiski mazāks, nekā g(n) ). Sakām, ka f = O(g), ja eksistē tāds c R, c > 0 un n0 N, ka katramn > n0 spēkā f(n) c g(n) ( f(n) asimptotiski nepārsniedz g(n) vairāk, kā par konstantu reizinātāju c ). Ja f = O(g) un g = O(f), tad f un g ir “asimptotiski salīdzināmas”. Vingrinājums Sakām, ka f = O’(g), ja eksistē tāds c R, ka limn f(n) / g(n) = c. Atrast funkcijas f un g, kurām f = O(g), bet f = O’(g) nav spēkā. Ievērojam: - Izteikumi f = O(g) un f = o(g) dod tikai augšējo novērtējumu funkcijai f. - Izteikumi f = O(g) un f = o(g) novērtē tikai f augšanas kārtu. Ja c R, c > 0, tad cf = O(g) f = O(g) un cf = o(g) f = o(g) . Tipiskas ātrdarbības novērtēšanā izmantotas sarežģītības kārtas (tiek lietotas arī citas): f = O( log n ) , f = O(n), f = O( n log n ), f = O (n2), f = O (n3), f = O (2n), … log n = log2 n (no sarežģītības kārtas viedokļa logaritma bāze nav būtiska).
Algoritma sarežģītība: Kā noteikt? Kā samazināt? IMP valodas variants ar masīviem, “burbuļa” kārtošana: i :=1; while i n-1 do j := i + 1; while j n do if x[ i ] > x[ j ] then t := x[ j ]; x[ j ] := x [ i ]; x [ i ] := t else skip fi; j := j + 1 od i := i + 1 od Kā noteikt sarežģītību? Viens cikls iekļauts otrā, ārējais cikls: n iterācijas, iekšējais cikls: iterāciju skaits no n līdz 1, vidēji n/2 iterācijas katrā no ārējā cikla iterācijām, sarežģītība O(n2). Skaitīt iekļautos ciklus: viena no vienkāršākajām metodēm. Var būt arī kādi papildus apsvērumi, kāpēc programmai ar k iekļautiem cikliem izpildes laiks ir mazāks par O(nk). Ja programma darbojas pārāk lēni: meklēt vispirms iespēju nest ārā operatorus no dziļākā cikla, skatīties, kā optimizēt programmas darbību tieši dziļākā cikla vietā. Tālāks jautājums: kā vērtēt sarežģītību (darbības laiku) rekursīvām programmām?
Programmēšanas metode: skaldi un valdi procedure MergeSort (int T[ ]): int [ ] Kārtošana ar saliešanu – pseidokods: if len(T) = 0 returnmid := len(T) div 2Split( T, A, B, mid ) MergeSort ( A )MergeSort ( B )return (Merge (A, B)) procedure Merge (int A[ ], int B[ ]): int [ ] Merge – divu sakārtotu virkņu sapludināšana. int T [ ] i := 1; j :=1;while ( i + j < len(A) + len(B) + 2) do if i = len(A) then T[ i + j -1] := B[ j ] ; j:= j + 1 else if j = len(B) then T[ i + j -1] := A[ i ]; i := i + 1 else if A[ i ] < B[ j ] then T[ i + j -1] := A[ i ]; i := i + 1 else T[ i + j -1] := B[ j ] ; j:= j + 1 fi fi fi od Split – arī paveicama lineārā laikā no aplūkojamās virknes garuma. Viena problēma apjomā 2n reducēta uz 2 problēmām apjomā n, izmantojot O(n) operācijas. Ko mēs varam teikt par MergeSort darbības laiku? Līdzīga “skaldīšana” tika arī izmantota “ātrajā saskaitīšanā” ar loģiskajām shēmām.
Papildus materiāls: Merge sort valodā PROLOG merge([X|LX],[Y|LY],[X|LZ]) :- X =< Y, !, merge(LX,[Y|LY],LZ). merge([X|LX],[Y|LY],[Y|LZ]) :- X > Y, !, merge([X|LX],LY,LZ). merge(LX,[],LX). merge([],LY,LY). split([H|T],[H|U],Z,N) :- N > 0, !, M is N-1, split(T,U,Z,M). split(X,[],X,_) :- !. len([H|T],N) :- !, len(T,M), N is M + 1. len([],0) :- !. split(X,Y,Z) :- len(X,N), M is (N / 2), split(X,Y,Z,M). merge_sort([],[]) :- !. merge_sort([X|[]],[X|[]]) :- !. merge_sort(X,Y) :- split(X,U,T),merge_sort(U,U1),merge_sort(T,T1),merge(U1,T1,Y).
Saskaitīšana: induktīvā pāreja n -> 2n x1,y1 x2,y2 Bloks n-ciparu skaitļiem Bloks n-ciparu skaitļiem z1,u1 z2,u2 Bloks FIX (n) z,u Doti 2n – ciparu skaitļi: x = (x1) 2n + (x2) , y = (y1) 2n + (y2) , (x1), (y1) – n-ciparu sk. Apzīmējam: z = x + y, u = x + y + 1, (z1) = (x1) + (y1), (u1) = (x1) + (y1) + 1, (z2) = (x2) + (y2), (u2) = (x2) + (y2) + 1 Kā dabūt zi, uino (z1)i, (u1)iun (z2)0, (u2)0? zi izmanto (z2)0 : zi = (z1)i, ja (z2)0 = 0, un zi = (u1)i, ja (z2)0 = 1 ui izmanto (u2)0 : ui = (z1)i, ja (u2)0 = 0, un ui = (u1)i, ja (u2)0 = 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 F F F F F F F F FIX FIX FIX FIX FIX (4) FIX (4) FIX (8) Ātrāka saskaitīšana: novērtējums Saskaitīšanas bloka struktūra 16-bitu skaitļiem. 32 bitu skaitļiem tā vēl dubultojas platumā, klāt nāk vēl bloks FIX(16), utt. Varam novērtēt: R(1) = 3, R(2) = 6, R(4) = 9, R(8) = 12, R(16) = 15, R(32) = 18, R(64) = 21 Lineārajā variantā (apzīmējam darbības laiku ar T): T(1) = 3, T(2) = 6, T(4) = 12, T(8) = 24, T(16) = 48, T(32) = 96, T(64) = 192 Novērtējums: R(2n) 2 R(n) + 3, T(n) T(n-1) + 3 Vispārīgajā gadījumā varam secināt: R(n) = O(log n), T(n) = O(n) Praktiski: 32 bitu mašīnas (kādas tās parasti mūsdienās sastopamas). Ātrākas saskaitīšanas algoritms dod paātrinājumu vairāk, nekā 5 reizes.
Teorēma par redukcijas novērtējumu Ja problēma apjomā n var tikt reducēta uz c problēmām apjomā f(n), izmantojot b(n) operācijas, tad iegūstam novērtējumu šādam redukcijas algoritmam A: T(A,n) c T(A, f(n)) + b(n) Apskatīsim situācijas: f(n) = n/d, f(n) = n-1. b(n) vietā izmantosim b nk. Teorēma: Ja T(n) c T( n/d )) + b nk , tad 1) ja c > dk, tad T(n) = O( na ), kur a = logdc2) ja c < dk, tad T(n) = O( nk ), 3) ja c = dk, tad T(n) = O( nk logdn ) 4) Ja T(n) c T( n-1 )) + b nk , tad T(n) O(cn) [ eksponente! ] 5) JaT(n) n T( n-1 )) + b nk , tad T(n) O(n!). Sekas: Merge sort algoritmam: T(M,n) 2 T(M,n/2) + b n1, tātad T(M,n) = O (n log n) “Ātrā saskaitīšana” (loģiskās shēmas): T(X,n) 2 T(X,n/2) + 3 n0, tātad T(M,n) = O (n0 log n) = O (log n) Var izmantot vēl daudzu citu algoritmu ātrdarbības novērtēšanai.
Teorēma par redukcijas novērtējumu: pierādījums Teorēma: Ja T(n) c T( n/d )) + b nk , tad 1) ja c > dk, tad T(n) = O( na ), kur a = logdc2) ja c < dk, tad T(n) = O( nk ), 3) ja c = dk, tad T(n) = O( nk logdn ) Pierādījums: T(1) ir konstante, T(1) = b0, definējam b = max(b, b0). Secinām: T(1) b T(d) c T( 1 )) + b dk T(d2) c T( d )) + b d2k T(d3) c T( d2 )) + b d3k … … … T(dm) c T( dm-1 )) + b dmk Sareizinām visas šīs rindiņas ar koeficientiem un saskaitām. Iegūstam: T(dm) b [ c0 (dk)m + c1 (dk)m-1 + … + cm-1 (dk)1 + cm (dk)0 ] = = b cm [(dk/c)m + (dk/c)m-1 + … + (dk/c)1 + (dk/c)0 ] = (*) Aplūkojam gadījumu (1), c > dk. Tad (dk/c) < 1. Secinām: (*) b cm [ (dk/c)0 + (dk/c)1 + (dk/c)2 + … + (dk/c)m + …] = b cm (1 / (1 - (dk/c))) Apzīmējam b1 = b (1 / (1 - (dk/c))), esam ieguvuši T(dm) b1 cm . cm cm-1 cm-2 cm-3 … … … c0
Teorēma par redukcijas novērtējumu: pierādījums Teorēma: Ja T(n) c T( n/d )) + b nk , tad 1) ja c > dk, tad T(n) = O( na ), kur a = logdc2) ja c < dk, tad T(n) = O( nk ), 3) ja c = dk, tad T(n) = O( nk logdn ) Pierādījums: Esam ieguvuši: T(dm) b [ c0 (dk)m + c1 (dk)m-1 + … + cm-1 (dk)1 + cm (dk)0 ] = = b cm [(dk/c)m + (dk/c)m-1 + … + (dk/c)1 + (dk/c)0 ] = (*) Gadījums (1),c > dk. Tad (dk/c) < 1. Secinām: (*) b cm (1 / (1 - (dk/c))) Apzīmējam: b1 = b (1 / (1 - (dk/c))), tātad T(dm) b1 cm . Kā vērtēt T(n) patvaļīgam n? T(n) T(da), kur a = logd n + 1, tātad T(n) b1 ca = b1 c (c exp logdn ) b1 c (c exp logdn ) = b2 (n exp logdc ), kur b2 = b1 c. Tātad, T(n) = O( n exp logdc ), kas arī bija jāpierāda. Gadījums (2): mājas darbs. Gadījums (3):c = dk. Iegūstam T(dm) (*) = b cm (m+1) 2b cm m. Pēc analoģijas, T(n) T(d exp a) 2b ca a = b1 c (c exp logdn ) a b1 c (c exp logdn) a = b1 c (n exp logdc) a = b1 c nk a == O( nk logdn ).
Par efektīva algoritma eksistenci problēmai Efektīvs (praktisks) algoritms – algoritms, kas darbojas polinomiālā laikā. Problēma 1. Pēc dotas izteikumu loģikas formulas noskaidrot, vai tā ir izpildāma (vai eksistē tādas mainīgo vērtības, ar kurām formula ir patiesa). Problēma 2. Pēc dota grafa noskaidrot, vai tajā eksistē Hamiltona cikls (cikls, kas katru virsotni apmeklē tieši vienu reizi). Problēma 3. Pēc dota grafa ar šķautnēm pierakstītiem svariem atrast ceļu grafā, kas apmeklē visas virsotnes, un kuram ir vismazākā svaru summa (ceļojošā tirgotāja problēma). Fakts.Ne par vienu no problēmām (1) – (3), nedz arī par daudzām citām praktiski nozīmīgām problēmām nav zināms, vai to risināšanai eksistē polinomiāls algoritms. Problēmu klase P – problēmas, kas atrisināmas ar determinētu algoritmu polinomiālā laikā. Problēmu klase NP – problēmas, kas atrisināmas ar nedeterminētu algoritmu poliomiālā laikā (pieņemot, ka nedeterminētā izvēle vienmēr ir mums “par labu”). Problēmas (1) – (3) – pieder klasei NP. Atklāts jautājums: vai P = NP ? Problēmas (1) – (3) – pieder NP-pilno problēmu klasei NPC: ja kādai no šīm problēmām izdotos atrast algoritmu, kas to risina polinomiālā laikā, tad šādi algoritmi būtu arī visām NP klases problēmām (t.i. būtu spēkā P = NP).
Par efektīva algoritma neeksistenci problēmai Efektīvs (praktisks) algoritms – algoritms, kas darbojas polinomiālā laikā. Problēmu klase P – eksistē polinomiāli algoritmi Problēmu klase NP – polinomiālu algoritmu eksistence nav zināma. Ir arī problēmas, par kurām ir zināms, ka to risināšanai neeksistē efektīvi (polinomiāli) algoritmi. Piemēri: Hanojas torņu problēma Uzvarošās stratēģijas eksistence no dotas pozīcijas n n dambretes spēlē u.c.