Ťažkosti s veľkosťou a Zložitosť kapacity podľa kritéria jednotnej hmotnosti. O - odhad najhoršieho prípadu

  • 16.05.2019

Algoritmus je presný predpis, ktorý jednoznačne definuje výpočtový proces vedúci od variabilných počiatočných údajov k požadovanému výsledku.

Pri vývoji algoritmov je veľmi dôležité vedieť odhadnúť zdroje potrebné na vykonávanie výpočtov, výsledkom hodnotenia je funkcia zložitosť (práca). Najčastejšie ceneným zdrojom je čas CPU ( výpočtová náročnosť) a pamäť ( pamäťová zložitosť algoritmu). Odhad vám umožňuje predpovedať čas vykonania a porovnávať výkon algoritmov.

Model RAM (stroj s náhodným prístupom)

Každé výpočtové zariadenie má svoje vlastné charakteristiky, ktoré môžu ovplyvniť trvanie výpočtu. Pri navrhovaní algoritmu sa zvyčajne neberú do úvahy detaily, ako je veľkosť vyrovnávacej pamäte procesora alebo typ implementovaného multitaskingu. operačný systém. Analýza algoritmov sa vykonáva na modeli abstraktnej kalkulačky tzv stroj s pamäťou s náhodným prístupom (RAM).

Model sa skladá z pamäte a procesora, ktoré fungujú nasledujúcim spôsobom:

  • pamäť pozostáva z buniek, z ktorých každá má adresu a môže uchovávať jeden prvok údajov;
  • každý prístup do pamäte trvá jednu jednotku času, bez ohľadu na číslo adresovanej bunky;
  • množstvo pamäte je dostatočné na vykonanie akéhokoľvek algoritmu;
  • procesor vykonáva akúkoľvek základnú operáciu (základnú logickú a aritmetické operácie, čítanie z pamäte, zápis do pamäte, volanie podprogramu atď.) v jednom časovom kroku;
  • slučky a funkcie sa nepovažujú za elementárne operácie.

Aj keď tento model má ďaleko skutočný počítač, je vynikajúci na analýzu algoritmov. Po implementácii algoritmu pre konkrétny počítač môžete vykonať profilovanie a optimalizáciu na nízkej úrovni, ale bude to optimalizácia kódu, nie optimalizácia algoritmu.

Operácie počítania. Vstupné triedy

Jedným zo spôsobov, ako odhadnúť zložitosť (\(T_n\)) je počítanie počtu vykonaných operácií. Zvážte, ako príklad, algoritmus na nájdenie minimálneho prvku poľa.

Štart; nájsť minimálny prvok poľa N prvkov min:= pole pre i od 2 do N do: if pole[i]< min min:= array[i] конец; вернуть min

Pri vykonávaní tohto algoritmu sa vykoná nasledovné:

  1. N — 1 operácia priradenia novej hodnoty počítadlu cyklov i;
  2. N - 1 porovnávacia operácia počítadla s hodnotou N;
  3. N — 1 operácia porovnávania prvkov poľa s minimálnou hodnotou;
  4. od 1 do N operácií priradenia hodnoty premennej min.

Presný počet operácií bude závisieť od spracovávaných údajov, takže má zmysel hovoriť o najlepších, najhorších a priemerných prípadoch. Zároveň sa osobitná pozornosť vždy venuje najhoršiemu prípadu, a to aj preto, že „zlé“ údaje môže útočník úmyselne zadať.

koncepcie priemerný prípad sa používa na vyhodnotenie správania algoritmu za predpokladu, že súbory údajov sú rovnako pravdepodobné. Takéto hodnotenie je však dosť komplikované:

  1. počiatočné údaje sú rozdelené do skupín tak, aby zložitosť algoritmu (\(t_i\)) pre akýkoľvek súbor údajov jednej skupiny bola rovnaká;
  2. na základe podielu súborov údajov skupiny na celkovom počte súborov sa vypočíta pravdepodobnosť pre každú skupinu (\(p_i\));
  3. priemerný odhad prípadu sa vypočíta podľa vzorca: \(\sum\limits_(i=1)^m p_i\cdot t_i\).

Asymptotická notácia

Počítanie počtu operácií vám umožňuje porovnať účinnosť algoritmov. Je však možné dosiahnuť podobný výsledok jednoduchý spôsob. Analýza sa vykonáva s očakávaním dostatočne veľkého množstva údajov na spracovanie (\(n \to \infty \)), takže rýchlosť rastu funkcie zložitosti, ale nie presné množstvo operácií.

Pri analýze rýchlosti rastu sa ignorujú konštantné členy a faktory vo výraze, t.j. funkcie \(f_x = 10 \cdot x^2 + 20 \) a \(g_x = x^2\) sú ekvivalentné z hľadiska rýchlosti rastu. Nevýznamní členovia len pridávajú "vlnenie", čo komplikuje analýzu.

Pri vyhodnocovaní algoritmov, špeciálne asymptotický zápis, ktorý definuje nasledovné funkčné triedy:

  • \(\mathcal(O)(g)\) — funkcie rastú pomalšie ako g;
  • \(\Omega(g)\) — funkcie rastú rýchlejšie ako g;
  • \(\Theta(g)\) sú funkcie rastúce rovnakou rýchlosťou ako g.

Zápis \(f_n = \mathcal(O)(g_n)\) znamená, že funkcia f patrí do triedy \(\mathcal(O)(g)\), t.j. funkcia f je zhora ohraničená funkciou g pre dostatočne veľké hodnoty argumentu. \(\existuje n_0 > 0, c > 0: \forall n > n_0, f_n \leq c \cdot g_n\).

Ohraničenosť funkcie g zdola funkciou f sa zapíše takto: \(g_n =\Omega(f_n)\). Označenia \(\Omega\) a \(\mathcal(O)\) sú vzájomne zameniteľné: \(f_n = \mathcal(O)(g_n) \Šípka doľava doprava g_n =\Omega(f_n)\).

Asymptotické notácie "Big O" a "Big Omega"

Ak funkcie f a g majú rovnakú rýchlosť rastu (\(f_n = \Theta(g_n)\)), potom existujú kladné konštanty \(c_1\) a \(c_2\) také, že \(\existuje n_0 > 0 : \ forall n > n_0, f_n \leq c_1 \cdot g_n, f_n \geq c_2 \cdot g_n\). Navyše \(f_n = \Theta(g_n) \Šípka doľava g_n = \Theta(f_n)\).


Asymptotická notácia "Theta veľká"

Príklady analýzy algoritmov

Algoritmus na nájdenie minimálneho prvku poľa, vyššie, vykoná N iterácií cyklu. Zložitosť každej iterácie nezávisí od počtu prvkov poľa, preto má zložitosť \(T^(iter) = \mathcal(O)(1)\). V tomto ohľade horná hranica celého algoritmu \(T^(min)_n = \mathcal(O)(n) \cdot \mathcal(O)(1) = \mathcal(O)(n \cdot 1) = \ mathcal(O)(n)\). Spodný odhad zložitosti sa vypočíta podobne a vďaka tomu, že sa zhoduje s horným, môžeme tvrdiť \(T^(min)_n = \Theta(n) \).

Algoritmus bublinového triedenia používa dve vnorené slučky. Vo vnútorných pároch prvkov sa porovnávajú postupne a ak sa ukáže, že prvky sú v nesprávnom poradí, vykoná sa permutácia. Vonkajšia slučka sa vykonáva dovtedy, kým sa v poli nenachádza aspoň jeden pár prvkov, ktoré porušujú požadované poradie.

Štart; bublina druh poľa N prvkov nPairs:= N; počet párov prvkov hasSwapped:= false; zatiaľ žiadny pár neporušil poradie pre všetky i od 1 do nPairs-1 spusť: if pole[i] > pole then: swap(pole[i], pole); zameniť prvky hasSwapped:= true; nájdená permutácia nPairs:= nPairs - 1; najväčší prvok zaručené, že sa umiestni na koniec, ak hasSwapped = true - potom prejdite na koniec bodu 3; pole je zoradené

Zložitosť funkcie swap nezávisí od počtu prvkov v poli, preto sa odhaduje ako \(T^(swap) = \Theta(1) \). V dôsledku vykonania vnútornej slučky sa najväčší prvok posunie na koniec poľa neusporiadanej časti, takže po N takýchto volaniach sa pole aj tak zoradí. Ak je pole zoradené, vnútorná slučka sa vykoná iba raz.

Touto cestou:

  • \(T^(bublina)_n = \mathcal(O)(\sum\limits_(i=1)^n \sum\limits_(j=1)^(n-i) 1) = \mathcal(O)(\sum \limits_(i=1)^n n) = \mathcal(O)(n ^2)\);
  • \(T^(bublina)_n = \Omega(1 \cdot \súčet\limity_(j=1)^(n-i) 1) = \Omega(n)\).

AT algoritmus triedenia výberu pole je mentálne rozdelené na usporiadanú a surovú časť. V každom kroku, z neusporiadanej časti poľa, minimálny prvok a pridal sa do triedenej časti .

Štart; select_sort - triedenie poľa N prvkov metódou výberu pre všetky i od 1 do N vykonajte: imin:= indMin(pole, N, i) swap(pole[i], pole) end; pole je zoradené

Na vyhľadávanie najmenší prvok neusporiadaná časť poľa sa používa funkcia indMin, ktorá preberá pole, veľkosť poľa a číslo pozície, z ktorej sa má hľadať. Analýza zložitosti tejto funkcie môže byť vykonaná rovnakým spôsobom, ako bola vykonaná funkcie min— počet operácií lineárne závisí od počtu spracovaných prvkov: \(T^(indMin)_(n, i) = \Theta(n - i)\).

Triedenie výberu nemá vetvy, ktoré by mohli robiť rozdiely v hodnotení najlepšieho a najhoršieho prípadu, jeho zložitosť: \(T^(vyber)_n = \Theta(\sum\limits_(i=1)^(n) (T^ (indMin )_(n, i) + T^(swap))) =\Theta(\sum\limits_(i=1)^(n) (n-i)) = \Theta(\frac(n-1)( 2) \cdot n) = \Theta(n^2) \).

Matematický aparát na analýzu algoritmov

Asymptotická notácia použitá pri analýze rýchlosti rastu bola zvážená vyššie. Umožňujú vám výrazne zjednodušiť úlohu vyradením významnej časti výrazu. Avšak v matematická analýza takychto metod je cela kopa.

Sumačné vzorce

Vo vyššie diskutovaných príkladoch sme sa už stretli s netriviálnymi sčítacími vzorcami. Na odhad sumy možno použiť niekoľko známych identít:

  • vyňatie konštantného faktora zo znamienka súčtu: \(\sum\limits_(i=1)^(n) (c \cdot f_i) = c \cdot \sum\limits_(i=1)^(n) \cdot f_i\);
  • zjednodušenie súčtu zloženého výrazu: \(\sum\limits_(i=1)^(n) (f_i + g_i) = \sum\limits_(i=1)^(n) (f_i) + \sum\limits_ (i= 1)^(n) (g_i)\);
  • súčet aritmetických progresívnych čísel: \(\sum\limits_(i=1)^(n) (i^p) = \Theta(n^(p+1))\);
  • súčet čísel geometrickej postupnosti: \(\sum\limits_(i=0)^(n) (a^i) = \frac (a \cdot (a^(n+1) - 1))(a - 1) \ ). S \(n \to \infty \) sú možné 2 možnosti:
    • Ak< 1, то сумма стремится к константе: \(\Theta(1)\);
    • ak a > 1, tak súčet smeruje k nekonečnu: \(\Theta(a^(n+1))\);
    • ak a = 0, vzorec nie je použiteľný, súčet smeruje k N: \(\Theta(n)\);
  • logaritmická zložitosť algoritmu: \(\sum\limits_(i=1)^(n) \frac(1)(i) = \Theta(\log n)\);
  • súčet logaritmov: \(\sum\limits_(i=1)^(n) \log i = \Theta(n \cdot \log n)\).

Prvá z týchto identít je pomerne jednoduchá, možno ich odvodiť pomocou metódy matematickej indukcie. Ak je pod znakom súčtu zložitejší vzorec, ako v posledných dvoch identitách - súčet možno nahradiť integráciou(prijatie integrálu funkcie je oveľa jednoduchšie, pretože na to existuje široká škála techník).

Zhrnutie a integrácia

Je známe, že integrál možno chápať ako oblasť obrázku umiestnenú pod grafom funkcie. Na aproximáciu (približný výpočet) integrálu existuje množstvo metód, medzi ktoré patrí najmä metóda obdĺžnikov. Plocha pod grafom je rozdelená do mnohých obdĺžnikov a je približne vypočítaná ako súčet ich plôch. Preto je možný prechod z integrálu na súčet a naopak.

Obrázky ukazujú príklad aproximácie funkcie \(f_x = \log x\) ľavým a pravým obdĺžnikom. Očividne budú horné a dolné skóre oblasti pod grafom:

  • \(\int\limits_a^n f_x dx \leq \sum\limits_(i=a )^(n) f_i \leq \int\limits_(a+1)^(n+1) f_x dx\) pre zvýšenie funkcií ;
  • \(\int\limits_a^n f_x dx \geq \sum\limits_(i=a )^(n) f_i \geq \int\limits_(a+1)^(n+1) f_x dx\) pre funkcie klesania .

Táto metóda umožňuje najmä vyhodnocovať algoritmy s logaritmickou zložitosťou (posledné dva sumačné vzorce).

Porovnanie zložitosti algoritmov. limity

Zložitosť algoritmov je určená pre veľké objemy spracovávaných dát, t.j. s \(n\to\infty\). V tomto ohľade, keď porovnávame zložitosť týchto dvoch algoritmov, môžeme zvážiť limit pomeru funkcií ich zložitosti: \(\lim\limits_(n \to \infty) \frac (f_n)(g_n)\). V závislosti od hodnoty limitu je možné vyvodiť záver o rýchlosti rastu funkcií:

  • limita je konštantná a nenulová, takže funkcie rastú rovnakou rýchlosťou:\(f_n = \Theta(g_n)\);
  • limit je nula, takže \(g_n\) rastie rýchlejšie ako \(f_n\): \(f_n = \mathcal(O)(g_n)\);
  • limit je nekonečno, \(g_n\) rastie pomalšie ako \(f_n\): \(f_n = \Omega(g_n)\).

Ak sú funkcie dostatočne zložité, potom táto technika značne zjednodušuje úlohu, pretože namiesto limity pomeru funkcií možno analyzovať limitu pomeru ich derivácií (podľa L'Hopitalovo pravidlo): \(\lim\limits_(n \to \infty) \frac (f_n)(g_n) = \lim\limits_(n \to \infty) \frac (f'_n)(g'_n)\). L'Hopitalovo pravidlo možno použiť, ak funkcie majú nasledujúce vlastnosti:

  • \(\lim\limits_(n \to \infty) \frac (f_n)(g_n) = \frac(0)(0) alebo \frac (\infty)(\infty) \);
  • \(f_n \) a \(g_n\) sú diferencovateľné;
  • \(g'_n \ne 0 \) s \(n \to \infty\);
  • \(\existuje \lim\limits_(n \to \infty) \frac (f'_n)(g'_n)\).

Predpokladajme, že chceme porovnať účinnosť dvoch algoritmov s odhadmi zložitosti \(\mathcal(O)(a^n)\) a \(\mathcal(O)(n^b)\), kde a a b sú konštanty. Vieme, že \((c^x)' =c^x \cdot ln(c)\), ale \((x^c)' = c \cdot x^(c-1) \). Aplikovaním L'Hopitalovho pravidla na limitu pomeru našich funkcií b-krát dostaneme: \(\lim\limits_(n \to \infty) \frac (\mathcal(O)(a^n))(\mathcal(O )(n^b )) = \lim\limits_(n \to \infty) \frac (\mathcal(O)(a^n * ln^b (a)))(\mathcal(O)(b!) ) = \infty\). Takže funkcia \(a^n\) má viac ako vysoká rýchlosť rast. Bez použitia limitov a L'Hopitalovho pravidla sa takýto záver nemusí zdať samozrejmý.

Logaritmy a zložitosť algoritmov. Príklad

Podľa definície \(\log_a(x) = b \Šípka vľavo x = a^b\). Musíte však vedieť, že \(x \to \infty \Rightarrow \log_a(x) \to \infty\), logaritmus je veľmi pomaly rastúca funkcia. Existuje množstvo vzorcov, ktoré vám umožňujú zjednodušiť výrazy pomocou logaritmov:

  • \(\log_a(x \cdot y) = \log_a(x) + \log_a( y)\);
  • \(\log_a(x^b) = b \cdot \log_a( x)\);
  • \(\log_a(x) =\frac(\log_b(x))(\log_b(a))\).

Pri analýze algoritmov sa zvyčajne stretávame s logaritmami na základ 2, ale základňa nehrá veľkú úlohu, takže sa často neuvádza. Posledný vzorec vám umožňuje nahradiť základ logaritmu jeho vynásobením konštantou, ale konštanty sú pri asymptotickej analýze vyradené.

Algoritmy, ktoré nepotrebujú spracovať všetky vstupné dáta, sú logaritmicky zložité, využívajú princíp „rozdeľuj a panuj“. V každom kroku sa časť údajov (zvyčajne polovica) zahodí. Príkladom môže byť funkcia na vyhľadávanie prvku v triedenom poli (binárne vyhľadávanie):

Štart; vyhľadávací prvok E vzostupne zoradené pole z N prvkov vľavo:= 1, vpravo:= N; ľavé a pravé ohraničenie, v ktorom sa nachádza požadovaný prvok, sa vykoná pri ľavom =< right: mid:= (right + left) div 2; вычисление индекса элемента посередине рассматриваемой части массива если array = E конец; вернуть true (элемент найден) если array < E; искомый элемент больше середины, значит все элементы левее mid можно отбросить left:= mid + 1; иначе right:= mid конец; вернуть false (элемент не найден)

Je zrejmé, že v každom kroku algoritmu sa počet uvažovaných prvkov zníži dvakrát. Počet prvkov, medzi ktorými môže byť požadovaný prvok v k-tom kroku, je určený vzorcom \(\frac(n)(2^k)\). V najhoršom prípade bude vyhľadávanie pokračovať dovtedy, kým v poli nezostane len jeden prvok, t.j. na určenie počtu iterácií pre horný odhad stačí vyriešiť rovnicu \(1 = \frac(n)(2^k) \Rightarrow 2^i = n \Rightarrow i = \log_2 n\). Preto má algoritmus logaritmickú zložitosť: \(T^(binSearch)_n = \mathcal(O)(\log n)\).

Výsledky analýzy. Poznámky

Odhad zložitosti je skvelý spôsob, ako nielen porovnávať algoritmy, ale aj predpovedať, ako dlho budú fungovať. Žiadne výkonnostné testy neposkytnú takéto informácie, pretože. závisia od vlastností konkrétneho počítača a spracovávajú špecifické údaje (nie nevyhnutne najhorší prípad).

Analýza algoritmov nám umožňuje určiť minimálnu možnú zložitosť, napríklad:

  • Algoritmus na nájdenie prvku v neusporiadanom poli nemôže mať hornú hranicu zložitosti lepšiu ako \(\mathcal(O)(n)\). Je to spôsobené tým, že nie je možné určiť neprítomnosť prvku v poli (najhorší prípad) bez toho, aby sme sa pozreli na všetky jeho prvky;
  • je možné formálne dokázať, že nie je možné zoradiť ľubovoľné pole lepšie ako \(\mathcal(O)(n \cdot \log n)\) .

Na pohovoroch sú často požiadaní, aby vytvorili algoritmus najlepší odhad než je možné. Samotné úlohy sú zredukované na nejaký štandardný algoritmus. Osoba, ktorá nie je oboznámená s asymptotickou analýzou, začne písať kód, ale všetko, čo sa vyžaduje, je ospravedlniť nemožnosť existencie takéhoto algoritmu.

Zoznam použitých zdrojov

  1. - režim prístupu: https://site/archives/578. Dátum prístupu: 01.06.2014.
  2. Vasiliev V. S. [Elektronický zdroj] - režim prístupu: https://website/archives/1462. Dátum prístupu: 01.06.2014.
  3. J. McConnell. Aktívny prístup k učeniu. - 3. upravené vydanie. M: Technosféra, 2009. -416s.
  4. Miller, R. Sekvenčné a paralelné algoritmy: Všeobecný prístup / R. Miller, L. Boxer; za. z angličtiny. - M.: BINOM. Vedomostné laboratórium, 2006. - 406 s.
  5. Skiena S. Algorithms. Sprievodca vývojom. 2. vyd.: Per. z angličtiny. - Petrohrad: BHV-Petersburg. 2011. - 720 b.: chor.

Ak existuje jeden algoritmus na riešenie problému, potom je možné vynájsť mnoho ďalších algoritmov na riešenie rovnakého problému. Ktorý algoritmus je najlepší na riešenie konkrétnu úlohu? Aké kritériá by sa mali použiť na výber algoritmu zo súboru možných? Spravidla robíme úsudok o algoritme na základe jeho posúdenia ľudskou osobou. Algoritmus sa nám zdá zložitý, ak ani po jeho dôkladnom preštudovaní nedokážeme pochopiť, čo robí. Algoritmus môžeme nazvať zložitým a mätúcim, pretože je rozvetvený logická štruktúra, ktorý obsahuje mnoho kontrol stavu a prechodu. Pre počítač však spustenie programu, ktorý implementuje takýto algoritmus, nebude ťažké, pretože vykonáva jeden príkaz za druhým a pre počítač je jedno, či ide o operáciu násobenia alebo test podmienok.

Okrem toho môžeme napísať ťažkopádny algoritmus, v ktorom sa opakujúce akcie vypisujú za sebou (bez použitia cyklickej štruktúry). Z hľadiska počítačovej implementácie takéhoto algoritmu však nie je prakticky žiadny rozdiel, či program používa príkaz loop (napríklad slovo „Ahoj“ sa zobrazí 10-krát) alebo či príkazy na zobrazenie slova „Ahoj“ sa za sebou píšu 10-krát.

Na vyhodnotenie efektívnosti algoritmov, pojem zložitosť algoritmu.

Definícia. výpočtový proces, generovaný algoritmom je sekvencia krokov algoritmu, ktoré prejdú počas vykonávania tohto algoritmu.

Definícia. Zložitosť algoritmu je počet základných krokov v výpočtový proces tento algoritmus.

Upozorňujeme, že je to vo výpočtovom procese a nie v samotnom algoritme. Samozrejme na porovnanie zložitosti rôzne algoritmy je potrebné, aby sa zložitosť vypočítala v rovnakých elementárnych akciách.

Definícia. Časová zložitosť algoritmu je čas T potrebný na jeho dokončenie. Rovná sa súčinu počtu základných akcií a priemerného času na dokončenie jednej akcie: T = kt.

Pretože t závisí od vykonávateľa implementujúceho algoritmus, je prirodzené predpokladať, že zložitosť algoritmu závisí predovšetkým od k. Je zrejmé, že v najväčšej miere závisí počet operácií pri vykonávaní algoritmu od množstva spracovávaných údajov. Zoradenie zoznamu so 100 priezviskami podľa abecedy si skutočne vyžaduje podstatne menej operácií ako zoradenie zoznamu so 100 000 priezviskami. Preto je zložitosť algoritmu vyjadrená ako funkcia množstva vstupných údajov.

Nech existuje algoritmus A. Pre neho existuje parameter a, charakterizujúci množstvo údajov spracovaných algoritmom, tento parameter sa často nazýva rozmer úlohy. Označiť podľa T(n)čas vykonávania algoritmu f- nejaká funkcia z P.

Definícia. To si povieme T(n) algoritmus má poradie rastu f(n), alebo, inými slovami, algoritmus má teoretická zložitosťO * (f(n)), ak sú také konštanty od 1, od 2> 0 a číslo n 0,čo c 1 f(n)) < T(p)< c 2 f(n) pre všetkých n >= n°. Tu sa predpokladá, že funkcia f(n) nie negatívne, ale aspoň n >= n°. Ak pre T(p) podmienka T(n)<= cf(n), potom sa hovorí, že algoritmus má teoretická zložitosť O(p) (čítaj „o“ veľké od n).

Napríklad algoritmus, ktorý vykonáva iba operácie čítania údajov a ich vkladania do pamäte RAM, má lineárne zložitosť 0(n). Algoritmus triedenia priameho výberu má kvadratický zložitosť 0 (p 2), pretože pri triedení akéhokoľvek poľa musíte vykonať (p 2 - p) / 2 porovnávacie operácie (zároveň nemusia existovať vôbec žiadne permutačné operácie, napr. na usporiadanom poli, v najhoršom prípade bude potrebné vykonať P permutácie). A zložitosť algoritmu násobenia matice(tabuľky) veľkosť P X P už bude kubický O(n 3) , keďže každý prvok výslednej matice vyžaduje P násobenia a n - 1 dodatky, ale všetky tieto prvky n 2.

Na vyriešenie problému je možné vyvinúť algoritmy akejkoľvek zložitosti. Je logické použiť spomedzi nich to najlepšie, t.j. s najmenšou zložitosťou.

Spolu so zložitosťou je dôležitou charakteristikou algoritmu efektívnosť. Efektívnosťou sa rozumie splnenie nasledujúcej požiadavky: nielen celý algoritmus, ale aj každý jeho krok musí byť taký, aby ich interpret bol schopný dokončiť v konečnom primeranom čase. Napríklad, ak algoritmus, ktorý vytvára predpoveď počasia na nasledujúci deň, bude spustený týždeň, potom nikto takýto algoritmus nepotrebuje, aj keď je jeho zložitosť minimálna pre triedu podobných problémov.

Ak vezmeme do úvahy algoritmy, ktoré sú implementované na počítači, potom sa k požiadavke na vykonanie v obmedzenom množstve pridáva požiadavka vykonania v primeranom čase. Náhodný vstup do pamäťe.

Je známe, že v mnohých programovacích jazykoch neexistuje žiadna operácia umocňovania, preto takýto algoritmus musí napísať programátor sám. Operácia umocnenia sa realizuje prostredníctvom operácií násobenia; so zvyšujúcim sa exponentom sa zvyšuje počet operácií násobenia, ktorých dokončenie trvá dlho. Preto je dôležitá otázka vytvorenia efektívneho umocňovacieho algoritmu.

Zvážte metódu na rýchly výpočet prirodzenej mocniny reálneho čísla X. Táto metóda bola opísaná pred naším letopočtom v starovekej Indii.

Poďme si zapísať P v dvojkovej sústave.

Nahradme každé "1" v tomto zázname dvojicou písmen KH, a každé "O" - písmeno K.

Vymažte pár CH úplne vľavo.

Výsledný reťazec čítaný zľava doprava dáva pravidlo rýchleho výpočtu xn, ak list Komu považovaný za operáciu kvadratúry výsledku a list X- ako operácia násobenia výsledku o X. Spočiatku je výsledok X.

Príklad 1. Zvýšte x na mocninu n = 100.

Preložme číslo n do binárneho číselného systému: n \u003d 100 10 \u003d 1100100,

Zostavíme postupnosť KHKKKKKKKK

Prečiarknite AX vľavo => KHKKKKKK

Vypočítame požadovanú hodnotu

K => odmocníme x => dostaneme x 2,

X => vynásobte výsledok x => dostanete x 3

K => druhá mocnina výsledku => získajte x 6

K => výsledok odmocníme => dostaneme x 12,

K => druhá mocnina výsledku => získajte x 24,

X => vynásobte výsledok x => dostanete x 25

K => druhá mocnina výsledku => získajte x 50

K => druhá mocnina výsledku => získajte x 100.

Vypočítali sme teda stotinovú mocninu čísel len v 8 násobeniach. Táto metóda je pomerne efektívna, pretože na ukladanie medzivýsledkov nevyžaduje dodatočnú pamäť RAM. Upozorňujeme však, že táto metóda nie je vždy najrýchlejšia.

Napríklad s n = 49 môžu študenti prísť s nasledujúcim efektívnym umocňovacím algoritmom:

Pri implementácii tohto algoritmu bolo vykonaných 7 operácií násobenia (namiesto 48 operácií pri výpočte „na čelo“) a 3 bunky (na uloženie premennej X, na uloženie hodnoty x 16 a uložiť aktuálny výsledok získaný v každom kroku). Ak použijeme algoritmus „Rýchle umocnenie“, dostaneme rovnaký počet operácií (7 operácií násobenia), ale na implementáciu tohto algoritmu sú potrebné iba 2 bunky (na uloženie premennej X a uložiť aktuálny výsledok).

Samotná operácia násobenia je implementovaná v procesore nie „na čele“, ale opäť prostredníctvom efektívnych rekurzívnych algoritmov. O jednom z týchto algoritmov si môžete prečítať v aplikácii Reader. Budeme uvažovať o „rýchlom“ multiplikačnom algoritme, ktorý bol známy v starovekom Egypte, nazýva sa aj „ruská“ alebo „roľnícka“ metóda. Pozrime sa na príklad aplikácie tohto algoritmu.

Príklad 2. Vynásobme 23 43 pomocou „ruskej“ metódy.

Odpoveď: 23 x 43 = 23 + 46 + 184 + 736 = 989.

Konečný súčet zahŕňa čísla v prvom stĺpci, vedľa ktorých sú v druhom stĺpci nepárne čísla.


Podobné informácie.


Označenie Intuitívne vysvetlenie Definícia
f zhora obmedzená funkciou g src="/pictures/wiki/files/101/eebfe73c29ff3f9bc886d263bd3e91f3.png" border="0"> alebo src="/pictures/wiki/files/100/d96907f9d7419a7e0c74e4089c"3"
f zospodu obmedzená funkciou g(až do konštantného faktora) asymptoticky src="/pictures/wiki/files/48/0fda981f377ae7b8d361f58ce148c173.png" border="0">
f ohraničené zhora a zdola funkciou g asymptoticky 0), n_0: \forall (n>n_0) \; |Cg(n)|
g dominuje f asymptoticky src="/pictures/wiki/files/49/176ce786e936badb831a0bb87f25249d.png" border="0">
f dominuje g asymptoticky src="/pictures/wiki/files/53/554bc3f42cfa6d0638722e58e4a99d8b.png" border="0">
f je ekvivalentné g asymptoticky

Príklady

Poznámky

Je potrebné zdôrazniť, že tempo rastu najhoršieho času vykonania nie je jediné alebo najväčšie dôležité kritérium vyhodnocovanie algoritmov a programov. Tu je niekoľko úvah, ktoré vám umožnia pozrieť sa na kritérium runtime z iných uhlov pohľadu:

Ak riešenie nejakého problému pre n-vrcholový graf jedným algoritmom zaberie čas (počet krokov) rádovo n C a druhým - rádovo n + n! / C, kde C - konštantné číslo, potom podľa „polynomiálnej ideológie“ je prvý algoritmus prakticky efektívny a druhý nie, hoci napríklad pri C=10 (10 10) je situácia práve opačná.

  1. Efektívne, ale zložité algoritmy nemusia byť žiaduce, ak pripravené programy bude podporovať osoby, ktoré sa nezúčastňujú na písaní týchto programov. Dúfajme, že základné aspekty technológie na vytváranie efektívnych algoritmov sú všeobecne známe a pomerne zložité algoritmy sú voľne aplikované v praxi. Je však potrebné predvídať možnosť, že efektívne, ale „zložité“ algoritmy nebudú žiadané kvôli ich zložitosti a ťažkostiam, ktoré vznikajú pri pokuse o ich vyriešenie.
  2. Je známych niekoľko príkladov, kedy efektívne algoritmy vyžadujú také veľké množstvo pamäte stroja (bez možnosti použitia pomalších externé fondyúložisko), že tento faktor neguje výhodu „efektívnosti“ algoritmu.
  3. V numerických algoritmoch nie je presnosť a stabilita algoritmov o nič menej dôležitá ako ich časová efektívnosť.

Triedy obtiažnosti

Trieda zložitosti je súbor problémov rozpoznávania, pre ktoré existujú algoritmy, ktoré sú z hľadiska výpočtovej zložitosti podobné. Dvaja významní predstavitelia:

Trieda P

Problém rovnosti tried P a NP

slávnych vedcov

  • Leonid Levin
  • Alexander Razborov
  • Edie Sheimir

pozri tiež

Odkazy

  • Yuri Lifshits "Moderné problémy teoretickej informatiky". Kurz prednášok o algoritmoch pre NP-ťažké problémy.
  • A. A. Razborov Teoretická informatika: pohľad matematika // Computerra. - 2001. - č. 2. (alternatívny odkaz)
  • A. A. Razborov O zložitosti výpočtov // Matematické vzdelanie. - MTSNMO, 1999. - č. 3. - S. 127-141.

Nadácia Wikimedia. 2010.

dodacia lehota programy pre rôzne vstupné údaje (parameter ).

Nájsť presnú závislosť pre konkrétny program je pomerne náročná úloha. Z tohto dôvodu je zvyčajne obmedzený asymptotické odhady túto funkciu, teda popis jej približného správania pre veľké hodnoty parametra. Niekedy sa pre asymptotické odhady používa tradičný vzťah (čítaj „veľké O“) medzi dvoma funkciami , ktorého definíciu možno nájsť v ktorejkoľvek učebnici matematickej analýzy, hoci sa používa častejšie vzťah ekvivalencie(čítaj „theta veľká“). Jeho formálne vymedzenie je napríklad v knihe, aj keď nám na pochopenie zatiaľ postačí táto záležitosť v obryse.

Ako prvý príklad sa vráťme k programom, ktoré sme práve zvažovali na nájdenie faktoriálu čísla. Je ľahké vidieť, že počet operácií, ktoré je potrebné vykonať, aby ste našli faktoriál! číslo v prvej aproximácii je priamo úmerné tomuto číslu, pretože počet opakovaní cyklu (iterácií) v tomto programe je rovný . V takejto situácii je zvykom povedať, že program (alebo algoritmus) má lineárna zložitosť(zložitosť alebo).

Je možné vypočítať faktoriál rýchlejšie? Ukazuje sa, že áno. Je možné napísať program, ktorý poskytne správny výsledok pre rovnaké hodnoty, pre ktoré to robia všetky vyššie uvedené programy, bez použitia iterácie alebo rekurzie. Jeho zložitosť bude , čo vlastne znamená organizáciu výpočtov podľa nejakého vzorca bez použitia cyklov a rekurzívnych volaní!

Nemenej zaujímavý je príklad výpočtu t. Fibonacciho čísla. V procese jeho štúdia sa v skutočnosti už zistilo, že jeho zložitosť je exponenciálny a rovná sa . Takéto programy nie sú v praxi prakticky použiteľné. Dá sa to veľmi ľahko overiť tak, že sa s tým pokúsite vypočítať 40. Fibonacciho číslo. Z tohto dôvodu je nasledujúci problém celkom relevantný.

Úloha 5.4 lineárna zložitosť.

Tu je riešenie tohto problému, v ktorom premenné j a k obsahujú hodnoty dvoch po sebe idúcich Fibonacciho čísel.

Text programu

public class FibIv1 ( public static void main(String args) vyvolá výnimku ( int n = Xterm.inputInt("Zadajte n ->< 0) { Xterm.print(" не определено\n"); } else if (n < 2) { Xterm.println(" = " + n); } else { long i = 0; long j = 1; long k; int m = n; while (--m >0) ( k = j; j + = i; i = k; ) Xterm.println(" = " + j); )))

Ďalšia otázka je celkom prirodzená – je možné nájsť Fibonacciho čísla ešte rýchlejšie?

Po preštudovaní určitých častí matematiky je celkom jednoduché odvodiť nasledujúci vzorec pre -té Fibonacciho číslo, ktorý sa dá ľahko skontrolovať na malé hodnoty:

Mohlo by sa zdať, že na jeho základe je jednoduché napísať komplexný program, ktorý nepoužíva iteráciu ani rekurziu.

Text programu

public class FibIv2 ( public static void main(String args) vyvolá výnimku ( int n = Xterm.inputInt("Enter n -> "); double f = (1,0 + Math.sqrt(5.)) / 2,0; int j = (int)(Math.pow(f,n) / Math.sqrt(5.) + 0,5); Xterm.println("f(" + n + ") = " + j); ) )

V skutočnosti tento program používa volanie funkcie umocňovania ( Math.pow(f,n) ), ktorá nemôže byť implementovaná rýchlejšie ako v logaritmickom čase (). O algoritmoch, v ktorých je počet operácií približne úmerný (v informatike je zvykom neuvádzať základ binárneho logaritmu), hovoria, že majú logaritmická zložitosť ().

Na výpočet Fibonacciho čísla existuje taký algoritmus, ktorého softvérovú implementáciu uvedieme bez ďalších komentárov, inak budete musieť príliš veľa vysvetľovať (spojenie Fibonacciho čísel s mocninami určitej matice rádu dva, pomocou tried pre práca s maticami, algoritmus na rýchle zvýšenie matice na mocninu) .

Úloha 5.5. Napíšte program, ktorý vypíše -té Fibonacciho číslo, ktoré má logaritmická zložitosť.

Text programu

public class FibIv3 ( public static void main(String args) vyvolá výnimku ( int n = Xterm.inputInt("Enter n -> "); Xterm.print("f(" + n + ")"); if (n< 0) { Xterm.println(" не определено"); } else if (n < 2) { Xterm.println(" = " + n); } else { Matrix b = new Matrix(1, 0, 0, 1); Matrix c = new Matrix(1, 1, 1, 0); while (n>0) ( if ((n&1) == 0) ( n >>>= 1; c.square(); ) else ( n -= 1; b.mul(c); ) ) Xterm.println(" = " +b.fib()); ) ) ) class Matrix ( private long a, b, c, d; public Matrix (long a, long b, long c, long d) ( this.a = a; this.b = b; this.c = c; this.d = d; ) public void mul(Matrix m) (dlhé a1 = a*m.a+b*m.c; dlhé b1 = a*m.b+b*m.d; dlhé c1 = c*m.a+ d *m.c; dlhé d1 = c*m.b+d*m.d; a = a1; b = b1; c = c1; d = d1; ) public void square() ( mul(this); ) public long fib( ) (návrat b; ) )

Ak sa pokúsite vypočítať desaťmiliónte Fibonacciho číslo pomocou tohto a predchádzajúce programy, potom bude rozdiel vo výpočtovom čase celkom zrejmý. Bohužiaľ, výsledok bude nesprávny (v oboch prípadoch) kvôli obmedzenému rozsahu čísel typu long .

Na záver uvádzame porovnávacia tabuľka doby vykonávania algoritmov s rôznou zložitosťou a vysvetliť, prečo je s rastúcou rýchlosťou počítača dôležité ich používať rýchle algoritmy výrazne zvyšuje.

Zvážte štyri algoritmy na riešenie rovnakého problému so zložitosťami , , a , v tomto poradí. Predpokladajme, že druhý z týchto algoritmov vyžaduje presne jednu minútu času na vykonanie na nejakom počítači s hodnotou parametra. Potom časy vykonávania všetkých týchto štyroch algoritmov na rovnakom počítači rôzne hodnoty parametre budú približne rovnaké ako v

Rovnako ako pri iných hodnoteniach z ruky (na základe intuície) ide o už naučené skúsenosti: v dostupnosti stavebných kameňov, ktoré môžete vďaka cieľavedomému cvičeniu nevedome operovať vo zvolenej oblasti.

Väčšina kódu má jednoduchý algoritmická štruktúra. A ak poznáte odhad pre bežné bloky (algoritmy a operácie s dátovými štruktúrami vo vašej oblasti), potom je zložitosť kódu zrejmá. V C++ je zložitosť štandardných algoritmov explicitne uvedená. Vedieť, do ktorej z troch kategórií vstup patrí (random access/RandomAccessIterator, sekvenčný/ForwardIterator, single pass/InputIterator), v mnohých prípadoch stačí na to, aby ste ocenili zložitosť algoritmu.

Možno ani neviete, ako sa niečo konkrétne implementuje. Napríklad, ak algoritmus vyžaduje triedenie náhodných údajov v určitom kroku, potom je rozumné predpokladať O(n log n) pre algoritmus založený na porovnaní, bez ohľadu na špecifickú implementáciu. Alebo pri vyhľadávaní v tabuľke v databáze, ak je tam veľa riadkov (keď má zmysel hovoriť o veľkom O), môžete očakávať, že dobrá implementácia indexu vytvorí (hľadajte od O (n) po O (log n) otočí). V prípade pochybností sa dá zmerať.

Ak chcete nájsť alebo otestovať intuitívnu odpoveď, môžete zostaviť rekurzívne výrazy alebo čiastkové súčty, ktoré je možné vypočítať pomocou počítača. Pretože existuje O(c*n) == O(n) a O(n*n + n) == O(n*n) a ďalšie zjednodušujúce transformácie, mnohé algoritmy možno zredukovať na malý počet základné prípady. Tento proces vyžaduje pozornosť, ale je celkom jednoduchý (najmä ak používate niečo ako wolframalpha, Maple, Maxima, sympy). Ako nájsť časovú zložitosť algoritmu.

V súlade s tým existujú prípady, keď sústredené úsilie s použitím zrejmých prístupov neprináša výsledok, potom stojí za to nechať sa na chvíľu rozptýliť a prejsť na iné úlohy. Pohľad môže prísť v najneočakávanejšom momente (ale to už presahuje rámec „offhand“).

Pozrite sa, aké algoritmy sa používajú v úlohách, ktoré vás zaujímajú. Nové algoritmy s lepšou zložitosťou sa neobjavujú každý deň.

Začnite od samého jednoduchý kód vo vašom jazyku, frameworku a zistite jeho zložitosť (napríklad „odstránenie prvku z poľa indexom“). Keď poznáte zložitosť elementárnych konštrukcií, nájdite zložitosť blokov kódu (zložených z týchto konštrukcií), s ktorými sa často stretávate.

Môže byť v opačná strana: začnite s kódom vyššej úrovne a postupne zostupujte na nižšie úrovne abstrakcie, kým nedosiahnete známe bloky (pridanie pevných čísel, ktoré sa hodia do strojového slova: O(1). ľubovoľné číslo n vziať, potom O(log n) - úmerné počtu bitov v čísle). Pozrite si tabuľku náročnosti podľa času.

Cvičte, kým nebude možné posúdiť väčšinu každodenného kódu, ktorý vás zaujíma.