algoritmikus bonyolultság. Keresési algoritmusok

Minden programozó számára fontos, hogy ismerje az algoritmusok elméletének alapjait, mivel ez a tudomány vizsgálja Általános jellemzők algoritmusok és ábrázolásuk formális modelljei. Már az informatika órákról is megtanítanak folyamatábrák készítésére, ami később az iskolainál összetettebb feladatok megírásában segít. Az sem titok, hogy egy adott probléma megoldásának szinte mindig többféle módja van: egyesek sok időt igényelnek, mások erőforrást igényelnek, mások pedig csak hozzávetőlegesen segítenek megtalálni a megoldást.

Mindig az optimumot kell keresni a feladatnak megfelelően, különösen, ha algoritmusokat dolgozunk ki egy problémaosztály megoldására.
Fontos azt is értékelni, hogy az algoritmus hogyan fog viselkedni a különböző térfogatú és mennyiségű kezdeti értékekkel, milyen erőforrásokra lesz szüksége, és mennyi ideig tart a végeredmény megjelenítése.
Ezt az algoritmusok elméletének egy része – az algoritmusok aszimptotikus elemzésének elmélete – végzi.

Ebben a cikkben azt javaslom, hogy írjam le a fő értékelési kritériumokat, és adjon példát a legegyszerűbb algoritmus értékelésére. A Habrahabrnak már vannak módszerei az algoritmusok értékelésére, de elsősorban a líceumi tanulókra koncentrál. Ez a kiadvány a cikk elmélyítésének tekinthető.

Definíciók

Az algoritmus összetettségének fő mutatója a probléma megoldásához szükséges idő és a szükséges memória mennyisége.
Ezenkívül egy feladatosztály összetettségének elemzésekor meghatároznak egy bizonyos számot, amely egy bizonyos mennyiségű adatot jellemez - beviteli méret.
Tehát arra következtethetünk algoritmus bonyolultsága a bemeneti méret függvény.
Az algoritmus bonyolultsága eltérő lehet azonos bemeneti méretű, de eltérő bemeneti adatok esetén.

Vannak a komplexitás fogalmai legrosszabb, átlagos vagy legjobb eset. Általában a bonyolultságot a legrosszabb esetben becsülik meg.

Időbeli összetettség legrosszabb esetben a bemeneti méret függvénye, amely megegyezik az algoritmus működése során végrehajtott műveletek maximális számával egy adott méretű feladat megoldása során.
Kapacitív komplexitás legrosszabb esetben a bemeneti méret függvénye, amely megegyezik az adott méretű feladatok megoldása során elért memóriacellák maximális számával.

Az algoritmusok összetettségének növekedési sorrendje

Növekvő nehézségi sorrend(vagy axiomatikus komplexitás) egy algoritmus komplexitásfüggvényének hozzávetőleges viselkedését írja le, amikor a bemeneti méret nagy. Ebből az következik, hogy az időbonyolultság becslésénél nem kell elemi műveleteket figyelembe venni, elég az algoritmus lépéseit figyelembe venni.

Algoritmus lépés szekvenciálisan elrendezett elemi műveletek halmaza, amelyek végrehajtási ideje nem függ a bemenet méretétől, azaz felülről valamilyen konstans határolja.

Az aszimptotikus becslések típusai

O - legrosszabb eset becslése

Vegye figyelembe a bonyolultságot f(n) > 0, azonos sorrendű függvény g(n) > 0, beviteli méret n > 0.
Ha f(n) = O(g(n))és vannak állandók c > 0, n0 > 0, Azt
0 < f(n) < c*g(n),
Mert n > n0.

A g(n) függvény ebben az esetben f(n) aszimptotikusan éles becslése. Ha f(n) egy algoritmus komplexitásának függvénye, akkor a bonyolultság sorrendje f(n) – O(g(n)).

Ez a kifejezés olyan függvényosztályt definiál, amely nem nő gyorsabban, mint g(n) egy állandó tényezőig.

Példák aszimptotikus függvényekre
f(n) g(n)
2n 2 + 7n - 3 n 2
98n*ln(n) n*ln(n)
5n+2 n
8 1
Ω – legjobb esetbecslés

A definíció azonban hasonló a legrosszabb eset becsléséhez
f(n) = Ω(g(n)), Ha
0 < c*g(n) < f(n)


Ω(g(n)) olyan függvényosztályt határoz meg, amely nem növekszik lassabban, mint a függvény g(n)állandó tényezőig.

Θ – becslés az átlagos esetre

Érdemes megemlíteni, hogy ebben az esetben a függvény f(n) nál nél n > n0 mindenhol a kettő között van c 1 *g(n)És c 2 *g(n), ahol c egy állandó tényező.
Például mikor f(n) = n 2 + n; g(n) = n2.

Az algoritmusok komplexitásának értékelési kritériumai

Egységes súlykritérium (RWC) feltételezi, hogy az algoritmus minden lépése egy egységnyi idő alatt, egy memóriacella pedig egy egységnyi térfogatban (maximum egy állandó) kerül végrehajtásra.
Logaritmikus súlyteszt (LWT) figyelembe veszi az adott művelet által feldolgozott operandus méretét és a memóriahelyen tárolt értéket.

Időbonyolítás LVK-valérték határozza meg fodrozódik), Ahol Op az operandus értéke.
Kapacitív komplexitás LVC-nélérték határozza meg l(M), Ahol M- a memóriacella mérete.

Példa összetettségbecslésre a faktorszámításban

Szükséges elemezni a faktoriális számítási algoritmus összetettségét. Ehhez írjuk ezt a problémát C pszeudokódba:

Void main() ( int eredmény = 1; int i; const n = ...; for (i = 2; i<= n; i++) result = result * n; }

Időbonyolultság egységes súlykritérium mellett

Elegendő egyszerűen meghatározni, hogy egy adott feladat bemenetének mérete az n.
Lépések száma - (n - 1).

Így az RVC időbonyolultsága egyenlő Tovább).

Időbonyolultság logaritmikus súlyozási feltétel mellett

Ebben a bekezdésben szükséges kiemelni az értékelendő műveleteket. Először is, ezek összehasonlítási műveletek. Másodsorban a változók megváltoztatásának műveletei (összeadás, szorzás). A hozzárendelési műveleteket a rendszer nem veszi figyelembe, mivel azt feltételezzük, hogy azonnal bekövetkeznek.

Tehát ebben a feladatban három műveletet különböztetünk meg:

1) én<= n

Az i-edik lépésnél kiderül log(n).
Lépések óta (n-1), ennek a műveletnek a bonyolultsága az lesz (n-1)*log(n).

2) i = i + 1

Az i-edik lépésnél kiderül log(i).
.

3) eredmény = eredmény * i

Az i-edik lépésnél kiderül log((i-1)!).
Így az összeg .

Ha összeadjuk az összes kapott értéket, és elvetjük azokat a kifejezéseket, amelyek nyilvánvalóan lassabban nőnek a növekedéssel n, megkapjuk a végső kifejezést .

Kapacitás összetettsége egységes súlykritérium mellett

Itt minden egyszerű. Meg kell számolni a változók számát. Ha tömböket használunk a feladatban, akkor a tömb minden cellája változónak számít.
Mivel a változók száma nem függ a bemenet méretétől, a komplexitás egyenlő lesz O(1).

A kapacitás összetettsége logaritmikus súlyozási feltétel mellett

Ebben az esetben azt a maximális értéket kell figyelembe venni, amely egy memóriacellában lehet. Ha az érték nincs megadva (például ha az i operandus > 10), akkor a rendszer úgy tekinti, hogy van határérték Vmax.
Ebben a feladatban van egy változó, amelynek értéke nem haladja meg n(i), és egy olyan változó, amelynek értéke nem haladja meg n! (eredmény). Így a pontszám az O(log(n!)).

következtetéseket

Az algoritmusok komplexitásának tanulmányozása meglehetősen lenyűgöző feladat. Jelenleg a legegyszerűbb algoritmusok elemzése szerepel a számítástechnika és az alkalmazott matematika informatika területén érintett műszaki szakterületek (pontosabban az általánosított "Informatika és számítástechnika" irányzat) tanterveiben.
A komplexitás alapján a feladatok különböző osztályait különböztetjük meg: P, NP, NPC-k. De ez már nem probléma az algoritmusok aszimptotikus elemzésének elméletében.

Bizonyára gyakran találkozott olyan jelölésekkel, mint az O (log n), vagy olyan kifejezésekkel, mint a "logaritmikus számítási bonyolultság" bármilyen algoritmus kapcsán. És ha még mindig nem érti, mit jelent ez, ez a cikk az Ön számára készült.

Nehézségi fokozat

Az algoritmusok bonyolultságát általában a végrehajtási idő vagy a felhasznált memória alapján becsülik meg. A bonyolultság mindkét esetben a bemeneti adatok méretétől függ: egy 100 elemből álló tömb gyorsabban kerül feldolgozásra, mint egy hasonló 1000 elemből álló tömb. A pontos idő ugyanakkor keveseket érdekel: ez a processzortól függ, adattípus, programozási nyelv és sok más paraméter. Csak az aszimptotikus komplexitás a fontos, azaz a komplexitás, mivel a bemenet mérete a végtelenbe hajlik.

Tegyük fel, hogy egy algoritmusnak 4n 3 + 7n feltételes műveletet kell végrehajtania n bemeneti adatelem feldolgozásához. Ahogy n növekszik, a teljes futási időt lényegesen jobban befolyásolja az n kockára emelése, mint 4-gyel való szorzása vagy 7n hozzáadása. Ekkor azt mondjuk, hogy ennek az algoritmusnak az időbonyolultsága O(n 3) , azaz köbösen függ a bemeneti adatok méretétől.

A nagy O használata (vagy az ún. O-jelölés) a matematikából származik, ahol a függvények aszimptotikus viselkedésének összehasonlítására szolgál. Formálisan az O(f(n)) azt jelenti, hogy az algoritmus futási ideje (vagy az elfoglalt memória mennyisége) a bemeneti adatok mennyiségétől függően nem növekszik gyorsabban, mint valamelyik állandó szorozva f(n) -nel.

Példák

O(n) - lineáris komplexitás

Az ilyen összetettséghez tartozik például egy rendezetlen tömb legnagyobb elemének megtalálására szolgáló algoritmus. A tömb mind az n elemét végig kell mennünk, hogy kitaláljuk, melyik a legnagyobb.

O(log n) - log komplexitás

A legegyszerűbb példa a bináris keresés. Ha a tömb rendezve van, felezéssel ellenőrizhetjük, hogy tartalmaz-e egy adott értéket. Ellenőrizzük a középső elemet, ha nagyobb, mint a kívánt, akkor a tömb második felét eldobjuk - biztosan nincs ott. Ha kevesebb, akkor fordítva - a kezdeti felét eldobjuk. Így folytatjuk a felezést, ennek eredményeként napló n elemet fogunk ellenőrizni.

O(n 2) - másodfokú komplexitás

Ilyen összetettség például a beillesztési rendezési algoritmus. A kanonikus megvalósításban ez két egymásba ágyazott ciklusból áll: az egyik a teljes tömb áthaladásához, a másik pedig a következő elem helyének megtalálásához a már rendezett részben. Így a műveletek száma a tömb méretétől függ: n * n, azaz n 2.

Vannak más nehézségi osztályok is, de mindegyik ugyanazon az elven alapul.

Az is előfordul, hogy az algoritmus futási ideje egyáltalán nem függ a bemenő adatok méretétől. Ekkor a komplexitást O(1)-ként jelöljük. Például egy tömb harmadik elemének értékének meghatározásához nem kell emlékeznie az elemekre, és nem kell többször ismételnie őket. Mindig csak meg kell várni a harmadik elemet a bemeneti adatfolyamban, és ez lesz az eredmény, amelynek kiszámítása tetszőleges adatmennyiség esetén ugyanannyi időt vesz igénybe.

Hasonlóképpen, az értékelés emlékezetből történik, amikor fontos. Az algoritmusok azonban lényegesen több memóriát használhatnak, ha a bemeneti méret növekszik, mint mások, de még mindig gyorsabban futnak. És fordítva. Ez segít kiválasztani a problémák megoldásának legjobb módjait az aktuális feltételek és követelmények alapján.

Helló! A mai előadás kicsit más lesz, mint a többi. Abban különbözik, hogy csak közvetve kapcsolódik a Java-hoz. Ez a téma azonban nagyon fontos minden programozó számára. Majd megbeszéljük algoritmusok. Mi az algoritmus? Egyszerűen fogalmazva, ez néhány műveletsor, amelyet végre kell hajtani a kívánt eredmény eléréséhez. Gyakran használunk algoritmusokat mindennapi életünkben. Például minden reggel van egy feladatod: jöjjön el iskolába vagy munkába, és ugyanakkor legyen:

  • Öltözött
  • tiszta
  • teljes
Melyik algoritmus lehetővé teszi, hogy elérje ezt az eredményt?
  1. Ébresztővel ébredj fel.
  2. Zuhanyozz le, mosakodj meg.
  3. Reggeli elkészítése, kávé/teafőzés.
  4. Eszik.
  5. Ha este óta nem vasalta a ruháit, vasalja ki.
  6. Öltözz fel.
  7. Hagyja el a házat.
Ez a műveletsor biztosan lehetővé teszi a kívánt eredmény elérését. A programozásban munkánk lényege a problémák folyamatos megoldásában rejlik. Ezen feladatok jelentős része már ismert algoritmusokkal is elvégezhető. Például a következő feladattal kell szembenéznie: rendezzen egy 100 nevet tartalmazó listát egy tömbben. Ez a feladat meglehetősen egyszerű, de többféleképpen is megoldható. Íme egy lehetséges megoldás: A nevek ábécé szerinti rendezésének algoritmusa:
  1. Vásárolja meg vagy töltse le az interneten az "Orosz személynévi szótár" 1966-os kiadását.
  2. Keresse meg a listánk összes nevét ebben a szótárban.
  3. Írd fel egy papírra, hogy a szótár melyik oldalán található a név.
  4. Tedd sorba a neveket egy papírlapra jegyzetekkel!
Megoldja-e a problémánkat egy ilyen műveletsor? Igen, megengedi. Vajon ez a döntés hatékony? Alig. Itt elérkezünk az algoritmusok egy másik nagyon fontos tulajdonságához - azokhoz hatékonyság. A problémát többféleképpen is megoldhatja. De mind a programozásban, mind a mindennapi életben azt a módszert választjuk, amelyik a leghatékonyabb lesz. Ha az a feladata, hogy vajas szendvicset készítsen, minden bizonnyal kezdheti a búza ültetésével és a tehenfejéssel. De lesz hatástalan megoldás - ez nagyon hosszú ideig tart, és sok pénzbe fog kerülni. Az egyszerű probléma megoldásához egyszerűen vásárolhat kenyeret és vajat. És a búzával és a tehénnel végzett algoritmus, bár lehetővé teszi a probléma megoldását, túl bonyolult ahhoz, hogy a gyakorlatban alkalmazzák. A programozási algoritmusok bonyolultságának felmérésére egy speciális jelölést hoztak létre, az úgynevezett Big-O ("nagy O"). A Big-O lehetővé teszi annak becslését, hogy egy algoritmus végrehajtási ideje mennyire függ a neki továbbított adatoktól. Nézzük a legegyszerűbb példát - az adatátvitelt. Képzelje el, hogy bizonyos információkat fájlként kell átvinnie nagy távolságra (például 5000 kilométerre). Melyik algoritmus lesz a leghatékonyabb? Attól függ, hogy milyen adatokkal kell dolgoznia. Például van egy 10 megabájtos hangfájlunk.
Ebben az esetben a leghatékonyabb algoritmus a fájl interneten keresztüli átvitele lenne. Ez legfeljebb pár percet vesz igénybe! Tehát ismét hangot adunk az algoritmusunknak: "Ha 5000 kilométeres távolságra szeretne információkat fájl formájában továbbítani, akkor az interneten keresztüli adatátvitelt kell használnia." Nagy. Most pedig elemezzük. Megoldja a problémánkat?Általánosságban elmondható, hogy igen. De mi a helyzet a bonyolultságával? Hmm, itt kezdenek érdekesek lenni a dolgok. Az a tény, hogy az algoritmusunk nagyon függ a bejövő adatoktól, nevezetesen a fájlok méretétől. Most 10 megabájtunk van, és minden rendben van. Mi van, ha 500 megabájtot kell átvinnünk? 20 gigabájt? 500 terabájt? 30 petabájt? Leáll az algoritmusunk működése? Nem, ezek az adatmennyiségek továbbra is átvihetők. Tovább fog futni? Igen fog! Most ismerjük algoritmusunk egy fontos jellemzőjét: minél nagyobb az átvinni kívánt adatok mérete, annál tovább tart az algoritmus befejezése. De szeretnénk pontosabban megérteni, hogyan is néz ki ez a függőség (az adatok mérete és átviteli ideje között). Esetünkben az algoritmus bonyolultsága lesz lineáris. A „lineáris” azt jelenti, hogy az adatok mennyiségének növekedésével az átvitelük ideje hozzávetőleg arányosan növekszik. Ha az adatok kétszeresére nőnek, és 2-szer több időt vesz igénybe az átvitelük. Ha az adat 10-szeresére nő, az átviteli idő 10-szeresére nő. A Big-O jelölés használatával algoritmusunk összetettségét a következőképpen határozzuk meg TOVÁBB). Ez a jelölés a legjobban megjegyezhető a jövőre nézve – mindig a lineáris összetettségű algoritmusoknál használják. Figyelem: itt egyáltalán nem beszélünk különféle „változó” dolgokról: az internet sebességéről, számítógépünk teljesítményéről stb. Egy algoritmus összetettségének értékelése során egyszerűen nincs értelme – úgysem tudjuk irányítani. A Big-O magát az algoritmust értékeli, függetlenül attól, hogy milyen „környezetben” kell működnie. Folytassuk példánkkal. Tegyük fel, hogy a végén kiderült, hogy az átvinni kívánt fájlok mérete 800 terabájt. Ha ezeket az interneten keresztül továbbítjuk, a probléma természetesen megoldódik. Csak egy probléma van: nagyjából 708 napba telne egy szabványos modern linken (100 megabit/másodperc sebességgel), amelyet legtöbben otthon használunk. Majdnem 2 éve! :O Tehát az algoritmusunk itt nyilvánvalóan nem megfelelő. Más megoldás kellene! Váratlanul egy informatikai óriás, az Amazon jön a segítségünkre! Amazon Snowmobile szolgáltatása lehetővé teszi, hogy nagy mennyiségű adatot töltsön be a mobil tárolóba, és teherautóval szállítsa ki a megfelelő címre!
Tehát van egy új algoritmusunk! "Ha 5000 kilométernél hosszabb fájlok formájában szeretne információkat továbbítani, és ez a folyamat több mint 14 napot vesz igénybe az interneten keresztül, akkor az adatszállítást egy Amazon teherautón kell használnia." A 14 napos számot itt véletlenszerűen választjuk ki: mondjuk ez a maximális időszak, amit megengedhetünk magunknak. Elemezzük az algoritmusunkat. Mi a helyzet a sebességgel? Még ha egy teherautó csak 50 km/h-val halad is, mindössze 100 óra alatt 5000 kilométert tesz meg. Ez alig több mint négy nap! Ez sokkal jobb, mint az internetes átviteli lehetőség. Mi a helyzet ennek az algoritmusnak a bonyolultságával? Lineáris is lesz, O(N)? Nem, nem fog. Végül is a teherautót nem érdekli, hogy mennyit rakod be – továbbra is nagyjából ugyanolyan sebességgel megy, és időben érkezik. Akár 800 terabájt, akár 10-szer több adat áll rendelkezésünkre, a kamion 5 nap múlva is a helyszínre ér. Más szóval, a teherautón keresztüli adattovábbítás algoritmusa állandó komplexitás. Az „állandó” azt jelenti, hogy nem függ az algoritmusnak átadott adatoktól. Tegyél egy 1 GB-os pendrive-ot a kamionba - 5 napon belül megérkezik. Tegyél oda 800 terabájtnyi adatot tartalmazó lemezeket – 5 napon belül megérkezik. A Big-O használatakor az állandó komplexitást a következőképpen jelöljük O(1). Mióta megismertük TOVÁBB)És O(1), akkor most nézzünk még több „programozási” példát :) Tegyük fel, hogy kapsz egy 100 számból álló tömböt, és a feladat az, hogy mindegyiket kinyomtasd a konzolra. Írsz egy normál for ciklust , amely ezt a feladatot végzi int numbers = new int [ 100 ] ; // ..töltsük ki a tömböt számokkal for (int i: számok) ( System. out. println (i) ; ) Mekkora az írott algoritmus bonyolultsága? Lineáris, O(N). A programnak végrehajtandó műveletek száma attól függ, hogy hány számot adtak át a programnak. Ha 100 szám van a tömbben, akkor 100 művelet (kimenet a képernyőn) lesz. Ha 10 000 szám van a tömbben, akkor 10 000 műveletet kell végrehajtani. Javítható-e az algoritmusunk? Nem. Mindenesetre muszáj N áthalad a tömbönés hajtson végre N kimenetet a konzolra. Nézzünk egy másik példát. public static void main(String args) ( LinkedList < Integer> számok = új LinkedList< >() ; számok. hozzáadás (0 , 20202 ); számok. add (0 , 123 ); számok. add (0 , 8283 ); ) Van egy üres LinkedListünk, amelybe beszúrunk néhány számot. Értékelnünk kell a példánkban szereplő LinkedList-be egyetlen szám beillesztésére szolgáló algoritmus bonyolultságát, és azt, hogy ez hogyan függ a lista elemeinek számától. A válasz az lesz O(1) - állandó komplexitás. Miért? Vegye figyelembe, hogy minden alkalommal beszúrunk egy számot a lista elejére. Ezen kívül, mint emlékszel, amikor beszúr egy számot a LinkedListbe, az elemek nem mozdulnak el sehova - a hivatkozások újradefiniálódnak (ha hirtelen elfelejtette a LinkedList működését, nézze meg valamelyikünket). Ha most a listánkban az első szám az x szám, és beszúrjuk az y számot a lista elejére, akkor csak x kell. előző = y; y. előző = null; y. következő = x; Ehhez a link felülbírálásához most nem érdekel, hogy hány szám van a LinkedListben- legalább egy, legalább egy milliárd. Az algoritmus összetettsége állandó - O(1).

Logaritmikus komplexitás

Semmi pánik! :) Ha a „logaritmikus” szónál le szeretné zárni az előadást, és nem szeretne tovább olvasni, várjon néhány percet. Itt nem lesznek matematikai nehézségek (más helyeken is van ilyen magyarázat bőven), és az összes példát „az ujjakon” elemezzük. Képzeld el, hogy az a feladatod, hogy egy 100 számból álló tömbben találj egy adott számot. Pontosabban annak ellenőrzésére, hogy van-e egyáltalán. Amint megtalálta a kívánt számot, a keresést le kell állítani, és a következő bejegyzést kell kiírni a konzolra: „A kívánt szám megtalálható! Az indexe a tömbben = ....” Hogyan oldana meg egy ilyen problémát? Itt a megoldás kézenfekvő: egyenként kell végigmenni a tömb elemein az elsőtől (vagy az utolsótól) kezdve, és ellenőrizni, hogy az aktuális szám megegyezik-e a keresett számmal. Ennek megfelelően a műveletek száma közvetlenül függ a tömb elemeinek számától. Ha 100 számunk van, akkor 100-szor kell a következő elemhez lépnünk, és 100-szor ellenőrizni kell a szám egyezését. Ha 1000 szám van, akkor 1000 ellenőrző lépés lesz. Ez nyilvánvalóan lineáris bonyolultság, TOVÁBB). És most egy pontosítást adunk a példánkhoz: a tömb, amelyben meg kell találnia a számot, növekvő sorrendben van rendezve. Változtat ez valamit a mi feladatunkon? Nyers erővel továbbra is megkereshetjük a kívánt számot. De helyette használhatjuk a jól ismert bináris keresési algoritmus.
A kép felső sorában egy rendezett tömböt látunk. Meg kell találnunk benne a 23-as számot. A számok rendezése helyett egyszerűen csak 2 részre osztjuk a tömböt, és ellenőrizzük az átlagos számot a tömbben. Megkeressük a 4-es cellában található számot, és ellenőrizzük (a képen a második sor). Ez a szám 16, mi pedig 23-at keresünk. A jelenlegi szám kevesebb. Mit is jelent ez? Mit az összes korábbi szám (amely a 16-os előtt található) nem ellenőrizhető: biztosan kevesebben lesznek, mint amit keresünk, mert a tömbünk rendezett! Folytassuk a keresést a fennmaradó 5 elem között. Figyelem: csak egy ellenőrzést végeztünk, de a lehetséges lehetőségek felét már kiküszöböltük. Már csak 5 termékünk maradt. Megismételjük lépésünket - a maradék tömböt ismét 2-vel osszuk el, és vegyük újra a középső elemet (az ábrán a 3. sor). Ez a szám 56, és több, mint amit keresünk. Mit is jelent ez? Még 3 lehetőséget elutasítunk - magát az 56-os számot, és két számot utána (ezek határozottan nagyobbak, mint 23, mert a tömb rendezve van). Már csak 2 számot kell ellenőriznünk (az ábra utolsó sora) - 5-ös és 6-os tömbindexű számokat. Ezek közül az elsőt ellenőrizzük, és ezt kerestük - a 23-as számot! Az indexe = 5! Nézzük meg az algoritmusunk eredményeit, majd foglalkozzunk annak összetettségével. (Mellesleg, most már érti, miért nevezik binárisnak: a lényege az adatok állandó 2-vel való osztása). Az eredmény lenyűgöző! Ha lineáris kereséssel a megfelelő számot keresnénk, akkor 10 csekkre lenne szükségünk, bináris kereséssel pedig 3-at kihagytunk! A legrosszabb esetben 4 db lenne, ha az utolsó lépésnél a második, és nem az első szám lenne, amire szükségünk volt. Mi a helyzet a bonyolultságával? Ez egy nagyon érdekes pont :) A bináris keresési algoritmus sokkal kevésbé függ a tömb elemeinek számától, mint a lineáris keresési algoritmus (vagyis egy egyszerű felsorolás). Nál nél 10 egy tömb elemeit, a lineáris keresés legfeljebb 10, a bináris keresés pedig legfeljebb 4 ellenőrzést igényel. A különbség 2,5-szeres. De egy tömbhöz 1000 tétel a lineáris kereséshez 1000 ellenőrzésre lesz szükség, és a bináris - összesen 10! Már 100-szoros a különbség! Vegyük észre, hogy a tömb elemeinek száma 100-szorosára nőtt (10-ről 1000-re), míg a bináris kereséshez szükséges ellenőrzések száma csak 2,5-szeresére, 4-ről 10-re nőtt. Ha megkapjuk nak nek 10000 tétel, a különbség még lenyűgözőbb: 10 000 csekk a lineáris kereséshez, és összesen 14 ellenőrzés binárishoz. És még egyszer: az elemek száma 1000-szeresére nőtt (10-ről 10000-re), az ellenőrzések száma pedig mindössze 3,5-szeresére (4-ről 14-re). A bináris keresési algoritmus bonyolultsága logaritmikus, vagy Big-O jelöléssel, - O(log n). Miért hívják így? A logaritmus a hatványozás inverze. A bináris logaritmus egy 2 szám hatványának kiszámítására szolgál. Például 10 000 elemünk van, amelyeket bináris kereséssel kell rendeznünk.
Most egy kép van a szemed előtt, és tudod, hogy ehhez maximum 14 csekk szükséges. De mi van, ha nincs kép a szeme előtt, és ki kell számítania a szükséges ellenőrzések pontos számát? Elég egy egyszerű kérdésre válaszolni: Milyen hatványra kell emelni a 2-es számot, hogy a kapott eredmény >= az ellenőrizendő elemek száma legyen? 10000-ért a 14. fok lesz. 2 13 hatványához túl kevés (8192) De 2-től a 14. hatványig = 16384, ez a szám kielégíti a feltételünket (ez >= a tömb elemeinek száma). Megtaláltuk a logaritmust - 14. Annyi ellenőrzésre van szükségünk! :) Az algoritmusok és bonyolultságuk túl kiterjedt téma ahhoz, hogy egy előadásba beleférjen. De ennek ismerete nagyon fontos: sok interjúban algoritmikus feladatokat kap. Elméleti szempontból tudok ajánlani néhány könyvet. Kezdheti a „Big-O videó a YouTube-on. Találkozunk a következő előadásokon! :)

Egy algoritmus összetettségének meghatározása

Az aszimptotikus elemzés során kapott komplexitási függvény becslését az algoritmus komplexitásának nevezzük.

Nem szabad megfeledkezni arról, hogy az algoritmus összetettségére többféle becslés létezik.

A komplexitási függvény aszimptotikus viselkedése a műveleti komplexitás. Ezen kívül a következő nehézségi típusokat adhatja meg.

Ideiglenes bonyolultság – az algoritmus futási idejének aszimptotikus becslése a hosszúságú bemeneti adatokon P. Nyilvánvaló, hogy a számítási eljárások párhuzamosításának hiányában az algoritmus futási idejét egyértelműen az elvégzett műveletek száma határozza meg. A műveletek időtartamát kifejező állandó együtthatók nem befolyásolják az időbonyolultság sorrendjét, így a műveleti és az időbonyolultság képlete gyakran egybeesik egymással.

kapacitívösszetettség - az egyidejűleg létező skalárok számának aszimptotikus becslése az algoritmus hosszúságú bemeneti adatokon történő végrehajtása során P.

Szerkezeti komplexitás - az algoritmusban lévő vezérlőstruktúrák számának és relatív helyzetük sajátosságainak jellemzője.

kognitívösszetettség – az alkalmazási területek szakértői általi megértéshez szükséges algoritmus elérhetőségének jellemzője.

Az aszimptotikumok típusai és jelölése

Az algoritmusok aszimptotikus elemzésénél a matematikai aszimptotikus elemzés jelölését szokás használni. Ebben az esetben az algoritmusok összetettségének három becslését (aszimptotikumát) veszik figyelembe, amelyeket a következőképpen jelölünk:

  • 1) /(i) = O^(n))- aszimptotikusan pontos becslés a komplexitásfüggvény /(«), vagy az algoritmus működési összetettségére;
  • 2) /(n) = 0 (§(n)) - A komplexitási függvény aszimptotikusan pontos felső becslése /( P);
  • 3) /(l) = ?2(#(l)) - aszimptotikusan pontos alsó becslés a munkaerő-ráfordítás függvényére /( P).

Kijelölés helyett C1^(n)) nagyon gyakran az egyszerűbb o(^(“))-t használják az „o” betű kisbetűs kurzívjával.

Magyarázzuk meg a képletek szemantikáját egy példán keresztül: ha / (n) = 0 (^2 (n)), akkor EZ azt jelenti, hogy a függvény g(n)=og2 (n) a komplexitásfüggvény /(«) aszimptotikusan pontos becslése. Valójában van egy kétpozíciós meghatározás állítás formájában:

Ha f(n)= @(log2(")),

mo g(n)\u003d log 2 (l) - f(n) aszimptotikusan pontos becslése.

Megjegyzendő, hogy a konstans tényező nem befolyásolja az algoritmus bonyolultsági sorrendjét, ezért a logaritmus alapját kihagyjuk a logaritmikus komplexitás megadásakor, és egyszerűen csak f(l) = @(lо§(l))-t írnak, ami azt jelenti, hogy a logaritmusnak tetszőleges bázisa van egynél nagyobb.

Az aszimptotika formális definíciói

A munkaerő-ráfordítás függvény aszimptotikusan pontos becslése Val vel, Val vel 2 , l 0, úgy, hogy l>l 0 esetén a /(l) függvény állandó tényezőkig nem tér el a függvénytől g( k), majd a függvény g(n) a /(k) függvény aszimptotikusan egzakt becslésének nevezzük.

  • 3 s ] , s 2 eÉS, x-szel > 0, 2-vel > 0:
    • (3 l 0 e K, l 0 > 0: (/l e K, l > l 0:0 g(n) / (l) = 0(?(l)),

ahol 9^, N az összes valós és természetes szám halmaza.

Aszimptotikusan pontos felső korlát a komplexitási függvényre szóban a következőképpen definiálva: ha vannak pozitív számok Val velés l 0 úgy, hogy l>l 0 esetén az /(l) függvény nem nő gyorsabban, mint a függvény g(n) egy állandó c tényezőig, majd a függvény g(n) a függvény aszimptotikusan egzakt felső korlátjának nevezzük Ap).

Egy pontosabb formális definíció a következő:

  • 3 Val vel e %s > 0:
    • (3 l 0 e X, l 0 > 0: (/l e K, l > l 0:0 s? #(l))) 0(g(n))

A komplexitásfüggvény aszimptotikusan pontos alsó korlátja szóban a következőképpen definiálva: ha vannak pozitív számok Val velés l 0 úgy, hogy l>l 0 esetén az /(l) függvény nem nő lassabban, mint a függvény g(n)állandó tényezőig Val vel, akkor a?(k) függvényt aszimptotikusan egzakt alsó korlátnak nevezzük a függvényre

Egy pontosabb formális definíció a következő:

  • 3 Val vel e 9^, Val vel > 0:
    • (3 i 0 e X, i 0 > 0: (/i e K, i > én 0: 0 s? g(n)

/(ÉN) = 0.^(n))

Vegye figyelembe a következőket:

  • 1) az aszimptotika formális definícióiban jelzett egyenlőtlenségeket általános esetben nem egy, hanem egy bizonyos függvényhalmaz elégítheti ki, gyakran megszámlálhatatlan taghalmazzal, így a konstrukciók Q(g(n)), 0^(n))És 0.^(n)) szimbolizálják függvénykészlet, amellyel a munkaráfordítás /(i) vizsgált függvényét hasonlítjuk össze; emiatt az /(n) = 0(?(n)), /(/0 = 0(? max (n)), Dn) = ?2(? m1n (n) alakú aszimptotika jelölésében )) az "= » jel helyett ésszerűbb lenne az "e" jelet használni;
  • 2) tervek (d^(n)), 0^(n))És ?1^(n)), bizonyos mennyiségek megjelölésére használva, ennek megfelelően a következőképpen értelmezendő: minden olyan funkció, amely ugyanaz, nem növekszik gyorsabban és nem növekszik lassabban g(n).

Az aszimptotikumok egybeesése és különbsége

Figyeljünk a következő tényre: a /(s) = 0(?(s)) becslés a /(s) felső és alsó becslését is beállítja, mivel definíciója feltételezi az összefüggés érvényességét. c g g(n)

Az aszimptotika következő tulajdonsága egészen nyilvánvaló: ha a becslés φ(n) = ©^(n)) létezik, akkor az egyenlőségek /( P) = 0(^(n)) és /(n) = ?2(#(n)), azaz. a munkaintenzitás felső és alsó becslése egybeesik egymással; ha /(i) = 0(? max (i)) és φ(n) = C1^ mn (n)), És g max (n)Фg m 1n(i), akkor nincs függvény g(n),úgy, hogy /(i) = 0(?(i)).

A munkaintenzitás felső és alsó becslésének egybeesése a következő esetekben lehetséges:

  • 1) a komplexitási függvény a bemeneti hossz összes értékére egy determinisztikus (nem véletlenszerű) függvény, pl. az elvégzett műveletek száma nem függ a kiindulási adatértékek sajátosságaitól; ilyenek például a szorzási és osztási műveletek számának függőségi függvényei az ismeretlen mennyiségek számától a lineáris algebrai egyenletrendszerek IS bővítési módszerrel történő megoldására szolgáló algoritmusban;
  • 2) a munkaintenzitás függvény egy véletlen függvény, azaz. a végrehajtott műveletek száma a kiindulási adatok sajátosságaitól és (vagy) a beérkezésük sorrendjétől függ, és megadhatja a / m | n (s), / max (s) függvényeket, leírva a minimális ill. maximális összeget Az algoritmus végrehajtója által meghatározott i bemeneti hosszra végrehajtott műveletek, azonban mindkét függvénynek ugyanazok a dominánsai - például azonos rendű polinomok.

Tartsa szem előtt a következő hármat fontos szabályokat a működési összetettség becsléséhez kapcsolódik:

  • 1) az állandó tényezők nem számítanak a komplexitási sorrend meghatározásánál, pl. 0 (k? g(n)) = 0(g(")) ;
  • 2) két függvény szorzatának összetettségi sorrendje megegyezik a komplexitásuk szorzatával, mivel az egyenlőség igaz:
  • 0(gl(i) §2(i)) = 0 (?| (i)) 0 (#2(i));
  • 3) a függvények összegének összetettségi sorrendje megegyezik a kifejezések dominánsának sorrendjével, például: 0(i e + n 2 + n) = 0(i 5).

A fenti szabályok csak egy aszimptotikus 0(") szimbólumát használják, de minden aszimptotikus becslésre érvényesek - és 0( ) , És &.{ ).

sokaságában elemi függvények megadhatja a funkcionális dominancia listáját: if -változó, a,b- numerikus állandók, akkor a következő állítások igazak: i" dominál i-n!; i! dominál a"; a" uralja Zj" if a>b a p dominál P b, ha A> 1 bármely b e 9? ; n a dominál a/ ha a>b i uralja a log q(i) if-t A > 1.

Az átlagos munkaintenzitás becslése

A gyakorlatban re Számítási számítások során igen érdekes az M komplexitásának matematikai elvárásának f(n) becslése, mivel az esetek túlnyomó többségében f(n) véletlen függvény. Azonban a véletlenszerű f(n) algoritmusok kísérleti vizsgálata során, további probléma- a kísérletek számának kiválasztása az M megbízható becsléséhez. Ennek a problémának a leküzdése központi feladat a -ban. A javasolt megoldás a béta eloszlás /(n) közelítésén alapul. Ez egy nagyon konstruktív és sokoldalú technika. Azonban in modern körülmények között, amikor a számítógép teljesítménye kellően magas, sok esetben lehetőség van egyszerűbb módszerrel is kiválasztani a tesztek körét, az értékek aktuális változékonyságának figyelése alapján f(n) - az értékeket addig becsüljük, amíg a becslések szórása kisebb lesz, mint a megadott hiba.

Egy algoritmus működési összetettségének becslése

Egy algoritmus összetettsége a vezérlési struktúráinak elemzése alapján határozható meg. A hurkok és rekurzív hívások nélküli algoritmusok állandó bonyolultságúak. Ezért az algoritmusok összetettségének meghatározása főként a ciklusok és a rekurzív hívások elemzésére korlátozódik.

Tekintsük a törlési algoritmust Nak nek-edik elem egy méretű tömbből P, amely a tömb elemeinek áthelyezéséből áll (+-ra 1) -th to P-lépjünk vissza egy pozíciót a tömb elejére, és csökkentsük az elemek számát P egységenként. A tömb feldolgozási hurok összetettsége az Ó (p-k), mivel a huroktest (mozgatás művelet) végrehajtásra kerül PC alkalommal, és a huroktest összetettsége 0(1), azaz. egy állandó.

A vizsgált példában a bonyolultságot a 0(") aszimptotika jellemzi, mivel az algoritmusban végrehajtott műveletek száma nem függ az adatértékek sajátosságaitól. A komplexitásfüggvény determinisztikus, és mindenféle aszimptotika egybeesik egymással: f(n) = Q(n-k), f(n) = 0(n-k)És f(n) = Q(n- to). Ezt a tényt pontosan ©( ) jelzése bizonyítja. Csak akkor használja a 0(*) és/vagy?2(*) értéket, ha ezek az aszimptotikumok eltérnek.

A hurok típusa (for, while, ismétlés) nem befolyásolja a bonyolultságot. Ha az egyik hurok egy másikba van beágyazva, és mindkét ciklus ugyanannak a változónak a méretétől függ, akkor az egész konstrukciót négyzetes összetettség jellemzi. Az ismétlések egymásba ágyazása a komplexitás növekedésének fő tényezője. Példaként bemutatjuk a jól ismert keresési és rendezési algoritmusok bonyolultságát egy méretű tömbhöz P:

  • összehasonlítási műveletek száma szekvenciális keresésben: 0(n);
  • összehasonlítási műveletek száma bináris keresésben: 0(log 2 P);
  • összehasonlítási műveletek száma az egyszerű cseremódszerben (buborékos rendezés): 0(n 2);
  • permutációs műveletek száma buborékos rendezésben: 0 (n 2);

Vegye figyelembe, hogy az egyszerű cseremódszerben az összehasonlítási műveletek száma aszimptotikus 0 (n 2), és a permutációs műveletek számának két különböző aszimptotikája van 0 (p 2)és?2(0), mivel az összehasonlítások száma nem függ a rendezett adatok értékeinek specifitásától, míg a permutációk számát ez a specifitás határozza meg. A permutációk egyáltalán nem hajthatók végre - ha az adattömb kezdetben helyesen van rendezve, vagy a permutációk száma maximális lehet - a sorrendben P 2 , - ha a rendezett tömb kezdetben az ellenkező irányba van rendezve.

Funkció neve g(n) az aszimptotikában /(x) = @(x(x)) és /(a) = 0(g(n)) az algoritmus jellemzésére a /(«) komplexitásfüggvény szolgál. Így tehát polinomiális, exponenciális, logaritmikus stb. bonyolultságú algoritmusokról beszélünk.

Összetettség funkció 0 (1). Az állandó bonyolultságú algoritmusokban a program legtöbb műveletét egyszer vagy többször hajtják végre. Bármely algoritmus, amely mindig (adatmérettől függetlenül) ugyanannyi időt igényel, állandó bonyolultságú.

Bonyolultsági függvény 0(N). A program futási ideje általában lineáris, amikor a bemenő adatok egyes elemeit csak lineárisan kell feldolgozni. Ez a bonyolultsági függvény egy egyszerű hurkot jellemez.

Bonyolultsági függvény 0(N 2), 0(N 3), 0(№) - komplexitás polinomfüggvénye: a műveletek száma a négyzettel arányosan nő N.Általános esetben a probléma összetettségétől függően O(A^) is előfordulhat. Ez a komplexitási függvény egy összetett ciklust jellemez.

Összetettség funkció O(2. napló (A0), 0 (N log 2 (A0). Ez az az időszak, amikor olyan algoritmusok dolgoznak, amelyek egy nagy problémát sok kicsire osztanak, majd miután megoldották azokat, kombinálják a megoldásokat.

0(e N) összetettségi függvény. Az exponenciális bonyolultságú algoritmusok leggyakrabban a nyers erőnek nevezett megközelítésből származnak.

Bonyolultsági függvény 0(M) - a műveletek száma a faktoriálissal arányosan nő N.

A programozónak képesnek kell lennie az algoritmusok elemzésére és bonyolultságuk meghatározására. Egy algoritmus időbonyolultsága a vezérlési struktúráinak elemzése alapján számítható ki.

A hurkok és rekurzív hívások nélküli algoritmusok állandó bonyolultságúak. Ha nincs rekurzió és hurkok, akkor az összes vezérlőstruktúra állandó bonyolultságú struktúrákra redukálható. Ebből következően az egész algoritmust is állandó bonyolultság jellemzi. Egy algoritmus bonyolultságának meghatározása alapvetően a hurkok és a rekurzív hívások elemzésén múlik.

Vegyünk például egy algoritmust a tömbelemek feldolgozására.

/" esetén: = 1-től N do Kezdje

Ennek az algoritmusnak a bonyolultsága RÓL RŐL(A), mert a ciklus törzsét A-szor hajtják végre, és a huroktest összetettsége 0(1). Ha az egyik hurok egy másikba van beágyazva, és mindkét ciklus ugyanannak a változónak a méretétől függ, akkor az egész konstrukciót négyzetes összetettség jellemzi.

A /: = 1-től N csinálni érte j:= 1-től N do Kezdje

A program összetettsége 0(N2).

1. példa Becsüljük meg egy olyan program összetettségét, amely a billentyűzetről belép egy tömbbe, és megtalálja benne a legnagyobb elemet. Az algoritmus a következő lépésekből áll:

  • - tömbbevitel (az A elemeket kell olvasni);
  • - keresse meg a legnagyobb elemet (A - 1 összehasonlítást kell végeznie);
  • - az eredmény kimenete (egy számot vagy karakterláncot kell kiadnia).

Összeadjuk az A + (A - 1) + 1 = 2A műveletek számát, azaz. létezik

olyan állandó, hogy bármely A művelet száma ne haladja meg a CA-t. Ezért az algoritmus összetettsége 0(A).

2. példa Becsüljük meg egy olyan program összetettségét, amely a billentyűzetről belép egy tömbbe, és talál benne egy adott tulajdonságú (például egy bizonyos értékkel egyenlő) elemet. Az algoritmus a következő lépésekből áll:

  • - tömbbevitel (Input műveletek);
  • - adott tulajdonságú elem keresése (egy elem lehet közelebb a tömb elejéhez vagy a legvégén; ha az elem nem létezik, akkor minden A összehasonlítást el kell végezni, hogy megbizonyosodjunk erről);
  • - eredmény kimenet.

A megadott algoritmus legjobb esetben A + 2 műveletet igényel (a teljes tömb bevitele, egyetlen összehasonlítás, kimenet), a legrosszabb esetben (ha nincs ilyen elem, 2A + 1 művelet). Ha A akarat egy nagy szám, például kb 10 6 , akkor az egység elhanyagolható. Ezért az algoritmus összetettsége az 0(N).

3. példa Határozzuk meg a titkosítási algoritmus összetettségi függvényét egy hosszúságú szóra L helyettesítési módszer. Legyen egy táblázat, amelyben az ábécé minden karakteréhez tartozik egy karakter, amelyre ki kell cserélni. Jelölje az ábécé betűinek számát S. Az algoritmus a következő lépésekből áll:

  • - szó bevitele (egy művelet);
  • - ciklusszervezés:
    • 1) minden karakterhez keresse meg a helyettesítését a táblázatban (ha a táblázat nincs rendezve, és nincs olyan tulajdonsága, amely megkönnyíti a keresést, akkor a legrosszabb esetben szükséges S műveletek egy karakterre, ha a szükséges elem a legvégén van);
    • 2) a talált szimbólum kimenete;
  • - a ciklus vége.

Műveletek teljes száma 1+ (S+)L. Abban az esetben, ha elég nagy SÉs L egységeket elhanyagolhatjuk, és kiderül, hogy a fenti algoritmus komplexitásfüggvénye az O(S L).

4. példa Határozzuk meg a természetes szám-fordítási algoritmus komplexitásfüggvényét 1 V be kettes számrendszer kalkulus (adatbeviteli és -kiadási műveletek nélkül). Az algoritmus a következő lépésekből áll:

  • - ciklus addig, amíg egy szám 2-vel való elosztásának eredménye 0 lesz:
  • - ossza el a számot 2-vel, és emlékezzen a maradékra;
  • - fogadja el az osztás eredményét új számként;
  • - a ciklus vége.

A műveletek teljes száma nem haladja meg az 1 + log 2 A-t. Ezért a leírt algoritmus bonyolult 0 (og 2 N).



Betöltés...
Top