Aszimptotikus becslések online. Az algoritmus aszimptotikus kiértékelése

Egy bizonyos probléma egy példányának, nagy mennyiségű bemeneti adattal történő megoldására végrehajtott algoritmusok időköltségének összehasonlításának elemzését ún. aszimptotikus. Az alacsonyabb aszimptotikus komplexitású algoritmus a leghatékonyabb.

Az aszimptotikus elemzésben algoritmus bonyolultsága egy olyan függvény, amely lehetővé teszi annak meghatározását, hogy az algoritmus futási ideje milyen gyorsan nő az adatmennyiség növekedésével.

Az aszimptotikus elemzésben található fő növekedési becslések a következők:

  • Ο (O-big) – felső aszimptotikus becslés az időfüggvény növekedésére;
  • Ω (Omega) – alacsonyabb aszimptotikus becslés az időfüggvény növekedésére;
  • Θ (Theta) – alsó és felső aszimptotikus becslések az időfüggvény növekedésére.

Hadd n– az adatmennyiség. Ezután az algoritmus függvény növekedése f(n) korlátozhatja a funkciókat g(n) aszimptotikus:

Például egy szoba takarításának ideje lineárisan függ ugyanannak a helyiségnek a területétől (Θ( S)), azaz növekvő területtel n alkalommal, a tisztítási idő is megnő n egyszer. Egy név keresése a telefonkönyvben lineáris időt vesz igénybe Ο (n), ha lineáris keresési algoritmust használ, vagy olyan időt, amely logaritmikusan függ a rekordok számától ( Ο (napló 2 ( n))), bináris keresés használata esetén.

Számunkra a legnagyobb érdeklődés az Ο -funkció. Sőt, a következő fejezetekben az algoritmusok bonyolultságát csak az aszimptotikus felső korlátra adjuk meg.

Az „algoritmus összetettsége” kifejezés alatt az Ο (f(n))" azt jelenti, hogy a bemeneti adatok mennyiségének növekedésével n, az algoritmus futási ideje nem növekszik gyorsabban, mint valamelyik állandó szorozva f(n).

Az aszimptotikus elemzés fontos szabályai:

  1. O(k*f) = O(f) – állandó tényező k(konstans) elveendő, mert az adatok mennyiségének növekedésével a jelentése elveszik, például:

O(9,1n) = O(n)

  1. O(f*g) = O(f)*O(g) – két függvény szorzatának becslése megegyezik a komplexitásuk szorzatával, például:

O(5n*n) = O(5n)*O(n) = O(n)*O(n) = O(n*n) = O(n 2)

  1. O(f/g)=O(f)/O(g) – két függvény hányadosának összetettségének becslése megegyezik azok összetettségének hányadosával, például:

O(5n/n) = O(5n)/O(n) = O(n)/O(n) = O(n/n) = O(1)

  1. O(f+g) egyenlő a dominánssal O(f) És O(g) – egy függvényösszeg összetettségének becslése az első és a második tag dominánsának összetettségének becslése, például:

O(n 5 + n 10) = O (n 10)

A műveletek számának számolása fárasztó, és ami fontos, egyáltalán nem szükséges. A fent felsorolt ​​szabályok alapján egy algoritmus bonyolultságának meghatározásához nem szükséges, mint korábban, az összes műveletet megszámolni, elég tudni, hogy egy adott algoritmus milyen bonyolultságú (operátor vagy operátorcsoport); van. Így egy olyan algoritmus, amely nem tartalmaz ciklusokat és rekurziókat, állandó bonyolultságú O(1). A hurok végrehajtásának összetettsége n iterációk egyenlő O(n). Két egymásba ágyazott hurok felépítése ugyanazon változó függvényében n, négyzetes bonyolultságú O(n 2).


Különféle megközelítések használhatók az algoritmusok teljesítményének értékelésére. A legegyszerűbb, ha minden algoritmust több feladaton futtatunk, és összehasonlítjuk a végrehajtási időt. Egy másik módszer a végrehajtási idő matematikai becslése műveletek számlálásával.

Tekintsünk egy algoritmust egy fokszámú polinom értékének kiszámítására n V adott pont x.
P n (x) = a n x n + a n-1 x n-1 + ... + a i x i + ... + a 1 x 1 + a 0

1. algoritmus- minden tagra, kivéve a 0-s konstrukciót x V adott végzettség egymást követő szorzás, majd szorzás együtthatóval. Ezután adja hozzá a feltételeket.

Számítás én kifejezés ( i=1..n) igényel én szorzások Szóval összesen 1 + 2 + 3 + ... + n = n(n+1)/2 szorzások Ezen kívül kötelező n+1 kiegészítés. Teljes n(n+1)/2+n+1=n2/2+3n/2+1 tevékenységek.

2. algoritmus- kivesszük x-s zárójelből, és írja át a polinomot a formába
P n (x) = a 0 + x(a 1 + x(a 2 + ... (a i + .. x(a n-1 + a n x))).

Például,
P 3 (x) = a 3 x 3 + a 2 x 2 + a 1 x 1 + a 0 = a 0 + x (a 1 + x (a 2 + a 3 x))

A kifejezést belülről fogjuk értékelni. A legbelső zárójelhez 1 szorzás és 1 összeadás szükséges. Értékét a következő zárójelhez használjuk... És így minden zárójelhez 1 szorzás és 1 összeadás, ami... n-1 dolog. És a legkülső zárójel kiszámítása után szorozzuk meg x-szel, és adjuk hozzá egy 0. Teljes n szorzások + n kiegészítések = 2n tevékenységek.

Gyakran nincs szükség ilyen részletes értékelésre. Ehelyett csak a műveletek számának aszimptotikus növekedési ütemét adják meg n növekedésével.

f(n) = függvény n 2/2 + 3n/2 + 1 körülbelül annyival nő n 2 /2(elvetjük a viszonylag lassan növekvő kifejezést 3n/2+1). Állandó tényező 1/2 Eltávolítjuk és megkapjuk az 1. algoritmus aszimptotikus becslését is, amelyet jelölünk különleges karakter O(n 2)[olvasható: "O big from en square"].

Ez egy felső becslés, azaz a műveletek száma (és így a működési idő) nem nő gyorsabban, mint az elemek számának négyzete. Hogy megtudja, milyen ez, nézze meg a táblázatot, amely számos különböző függvény növekedési ütemét szemlélteti.

n*log n

1 0 0 1
16 4 64 256
256 8 2,048 65,536
4,096 12 49,152 16,777,216
65,536 16 1,048,565 4,294,967,296
1,048,576 20 20,969,520 1,099,301,922,576
16,775,616 24 402,614,784 281,421,292,179,456

Ha feltételezzük, hogy a táblázatban szereplő számok mikroszekundumnak felelnek meg, akkor a következővel van probléma n = 1048576 az algoritmus elemei futási idővel O(log n) 20 mikroszekundumot vesz igénybe, az algoritmus végül megteszi Tovább)- 17 perc, és az algoritmus működési idővel O(n 2)- több mint 12 nap... Most a 2. algoritmus előnye kiértékeléssel Tovább) az 1. algoritmus előtt egészen nyilvánvaló.

A legjobb becslés az O(1)... Ebben az esetben az idő egyáltalán nem függ attól n, azaz tetszőleges számú elemre állandó.

És így, O()- az algoritmus futási idejének „csonkított” becslése, amelyet sokszor sokkal könnyebb megszerezni, mint a műveletek számának pontos képletét.

Tehát fogalmazzunk meg két szabályt az O() becslés kialakítására.

Egy függvény kiértékelésénél a leggyorsabban növekvő műveletek számát veszik függvénynek.
Azaz, ha egy programban egy függvény, például szorzás, végrehajtásra kerül Tovább) alkalommal, és kiegészítés - O(n 2) alkalommal, akkor a program teljes összetettsége az O(n 2), hiszen végül a növekvő n gyorsabban (egy bizonyos állandó hányszor) olyan gyakran hajtják végre az összeadásokat, hogy azok sokkal jobban befolyásolják a teljesítményt, mint a lassú, de ritka szorzások. Szimbólum O() mutatja kizárólagosan aszimptotikumok!

Az O() kiértékelésénél az állandókat nem veszik figyelembe.
Végezzen el az egyik algoritmus 2500n + 1000 műveletet, a másik pedig - 2n+1. Mindkettőnek van minősítése Tovább), mivel a végrehajtási idejük lineárisan növekszik.

Különösen, ha mindkét algoritmus, pl. O(n*log n), ez nem jelenti azt, hogy egyformán hatékonyak. Az első mondjuk 1000-szer hatékonyabb lehet. Az O() egyszerűen azt jelenti, hogy az idejük megközelítőleg növekszik függvényként n*log n.

Az állandó elhagyásának másik következménye az algoritmus időbeli alakulása O(n 2) sokkal gyorsabban működhet, mint az algoritmus Tovább) kis n... Annak a ténynek köszönhetően, hogy az első algoritmus tényleges műveletszáma lehet n2 + 10n + 6és a második - 1000000n + 5. A második algoritmus azonban előbb-utóbb lekörözi az elsőt... n 2 sokkal gyorsabban nő 1000000n.

Logaritmus alapja a szimbólum belsejében O() nincs írva.
Ennek oka egészen egyszerű. Hagyjuk O(log2n). De log 2 n=log 3 n/log 3 2, A napló 3 2, mint minden állandó, az aszimptotika is egy szimbólum RÓL RŐL() nem veszi figyelembe. És így, O(log2n) = O(log 3 n).

Bármely bázisra ugyanúgy költözhetünk, ami azt jelenti, hogy nincs értelme megírni.

Az O() szimbólum matematikai értelmezése.

Meghatározás
O(g)- sok funkció f, amelyre ilyen állandók léteznek CÉs N, Mit |f(x)|<= C|g(x)| mindenkinek x>N.
Rekord f = O(g) szó szerint azt jelenti f a készlethez tartozik O(g). Ebben az esetben az inverz kifejezés O(g) = f nincs értelme.

Konkrétan azt mondhatjuk f(n) = 50n tartozik O(n 2). Itt pontatlan becsléssel van dolgunk. természetesen f(n)<= 50n 2 nál nél n>1, azonban erősebb kijelentés lenne f(n) = O(n), mivel a C=50És N=1 jobb f(n)<= Cn, n>N.

Egyéb típusú értékelések.

Az értékeléssel együtt Tovább)értékelést használják Ω(n)[olvasható: "Omega big from en"]. A függvény növekedésének alsó korlátját jelöli. Például az algoritmus műveleteinek számát írja le a függvény f(n) = Ω(n 2). Ez azt jelenti, hogy még a legsikeresebb esetben is legalább egy nagyságrendet előállítanak n 2 akciók.
...Míg a pontszám f(n) = O(n 3) garantálja, hogy a legrosszabb esetben is rend lesz n 3, nem több.

Becslés is használatos Θ(n)["Theta from en"], ami egy hibrid O()És Ω() .
Θ(n 2) egyszerre egy felső és egy alsó aszimptotikus becslés - mindig a sorrendben fog tartani n 2 tevékenységek. Fokozat Θ() csak akkor létezik O()És Ω() egybeesik és egyenlő velük.

A fent tárgyalt polinomszámítási algoritmusok esetében a talált becslések egyidejűleg O(), Ω() És Θ() .
Ha hozzáadjuk az első algoritmushoz az ellenőrzéshez x=0 hatványozásban, majd a legsikeresebb kezdeti adatokon (mikor x=0) rendelésünk van n ellenőrzések, 0 szorzás és 1 összeadás, új becslést adva Ω(n) a régivel együtt O(n 2).

Általában a fő figyelmet továbbra is a felső becslésre fordítják O(), tehát a "javítás" ellenére a 2. algoritmus továbbra is előnyösebb.

Így, O()- az algoritmus aszimptotikus kiértékelése a legrosszabb bemeneti adatokon, Ω() - a legjobb bemeneti adatokon, Θ() - ugyanennek a rövidített jelölése O()És Ω() .

Annak ellenére, hogy egy algoritmus időbonyolultsági függvénye bizonyos esetekben pontosan meghatározható, a legtöbb esetben értelmetlen annak pontos értékét keresni. A helyzet az, hogy egyrészt az időbonyolultság pontos értéke az elemi műveletek definíciójától függ (például a komplexitás az aritmetikai műveletek számában vagy a Turing-gépen végzett műveletek számában mérhető), másodszor pedig az A bemeneti adatok megnövekednek, a konstans tényezők hozzájárulása és a pontos működési időre vonatkozó kifejezésben megjelenő alacsonyabb rendű tagok rendkívül jelentéktelenné válnak.

Aszimptotikus komplexitás- nagy bemeneti adatok figyelembevétele és az algoritmus működési idejének növekedési sorrendjének felmérése. Általában egy alacsonyabb aszimptotikus bonyolultságú algoritmus minden bemeneti adat esetében hatékonyabb, kivéve talán a kis adatméretet.

Aszimptotikus komplexitásbecslés görög Θ (théta) betűvel jelöljük.

f(n) = Θ(g(n)), ha létezik c1, c2>0 és n0 úgy, hogy c1*g(n)<=f(n)<=c2*g(n) , при n>n0.

A g(n) függvény az algoritmus bonyolultságának aszimptotikusan pontos becslése - az f(n) függvény, a fenti egyenlőtlenséget aszimptotikus egyenlőségnek nevezzük, maga a Θ jelölés pedig a „olyan gyorsan” növekvő függvényhalmazt szimbolizálja. mint a g(n) függvény - azaz e. konstanssal való szorzásig. Amint a fenti egyenlőtlenségből következik, a Θ becslés a komplexitás felső és alsó becslése is. Ebben a formában nem mindig lehet becslést szerezni, ezért a felső és az alsó becslést esetenként külön határozzák meg.
Felső nehézségi pontszám a görög Ο (omikron) betűvel jelöljük, és olyan függvények halmaza, amelyek nem nőnek gyorsabban, mint g(n).
f(n)= Ο(g(n)), ha van c>0 és n0 úgy, hogy 0<=f(n)<=cg(n), при n>n0.
Alacsonyabb nehézségi pontszám a görög Ω (omega) betűvel jelöljük, és olyan függvények halmaza, amelyek nem nőnek lassabban, mint g(n).
f(n)= Ω(g(n)), ha van olyan c>0 és n0, hogy 0<=cg(n)<=f(n), при n>n0.
Következésképpen: aszimptotikus becslés csak akkor létezik, ha az algoritmus komplexitásának alsó és felső korlátja egybeesik. Az algoritmusok elemzésének gyakorlatában a komplexitásbecslést leggyakrabban a komplexitás felső becsléseként értik. Ez teljesen logikus, mivel az a legfontosabb, hogy megbecsüljük azt az időt, amely alatt az algoritmus garantáltan befejezi a munkáját, és nem azt az időt, amelyen belül biztosan nem.

($APPTYPE KONZOL)

használ
SysUtils;
var n:Integer;
függvény eredmény(n:integer):Integer; //a világ kialakulása
var i:Integer;
kezdődik
eredmény:=0;
for i:= 2 - n div 2 do
ha n mod i =0 akkor
eredmény:=eredmény+1;
vége;


kezdődik
olvas(n); // a neved
write(eredmény(n));
readln;
readln;
vége.
vége.

4. Rekurzió memorizálással (a dinamikus programozás transzparens változata). Példa a binomiális együtthatók gyors kiszámítására a C(n,m)=C(n-1,m)+C(n-1,m-1) képlet segítségével

Van mód az ismételt számítások problémájának megoldására. Nyilvánvaló - emlékeznie kell a talált értékekre, hogy ne számítsa ki őket minden alkalommal. Természetesen ehhez aktív memóriahasználatra lesz szükség. Például a Fibonacci-számok kiszámítására szolgáló rekurzív algoritmus könnyen kiegészíthető három „sorral”:

hozzon létre egy globális FD tömböt, amely nullákból áll;

az F(n) szám kiszámítása után helyezzük az értékét FD[n]-be;

a rekurzív eljárás elején ellenőrizzük, hogy FD[n] = 0, és ha igen, akkor adjuk vissza az FD[n] eredményt, egyébként pedig folytassuk az F(n) rekurzív számítását.

(Funkció Pascalban)

függvény C(m, n:Byte):Longint;

Ha (m=0) vagy (m=n)

Egyéb C:=C(m, n-1)+C(m-1, n-1)

(Az eljárás Pascalban)

Eljárás C(m, n: Byte; Var R: Longint);

Var R1, R2: Longint;

Ha (m=0) vagy (m=n)

Algoritmus elemzés –

Az elemzés típusai

Legrosszabb esetben: T(n)

Átlagos eset: T(n)

Aszimptotikus becslés

O

f (n) = O(g(n)) Þ

($c > 0, n 0 >

O(g(n)) = (f(n) : $ c > 0, n 0 >

Példa: 2n 2 = O(n 3)


Összevonás rendezés

if(o< r) //1


Rekurziós fa: T(n) = 2*T(n/2) +cn, ahol –const, c>0

Rekurzív algoritmusok kiértékelésének módszertana.

Iterációs módszer

A T(n) képlet alapján felírjuk a T(n) képletének jobb oldalán található kisebb elem képletét.

Helyettesítse a kapott képlet jobb oldalát az eredeti képletbe

Az első két lépést addig hajtjuk végre, amíg a képletet sorozattá nem bővítjük T(n) függvény nélkül.

Becsüljük meg a kapott sorozatot számtani ill geometriai progresszió

T(n)=T(n-1)+n, T(1)=1

T(n)=θ(g(n)), g(n)=?

T(n-1)=T(n-2)+(n-1)

T(n-2)=T(n-3)+(n-2)

T(n-3)+(n-2)+(n-1)+n=…

1+…(n-2)+(n-1)+n=

Rekurziós fa - grafikus módszer egy reláció önmagába való helyettesítésének megjelenítésére

T(n)=2T(n/2)+n 2

T(n/4) T(n/4) T(n/4) T(n/4)

(n/2) 2 (n/2) 2 log n (1/2)*n 2

(n/4) 2 (n/4) 2 (n/4) 2 (n/4) 2 (1/4)*n 2

Helyettesítő módszer

  1. Találja ki (javasolja) a megoldást
  2. Ellenőrizze az oldatot indukcióval
  3. Állandók keresése és helyettesítése

T(n) = 2T(n/2) + n


T(n) = (n log n)

Indukciós feltevés: T(n) ≤ с * n* log n, c>0

Legyen igaz ez a becslés erre n/2

T(n/2) ≤ c*(n/2)*log(n/2)

Helyettesítsük be az eredeti képletbe T(n)

T(n) ≤ 2*(c*(n/2)*log(n/2))+n ≤

c*n*log(n/2)+n =

c*n*log n - c*n*log 2 +n =

c*n*log n - c*n +n ≤

c*n*log n

c≥1, n ≥ 2

Fő tétel az ismétlődő becslésekről

T (n) = aT (n/b) + f (n), a ≥ 1, b > 1, f (n) − (f (n) > 0, n > n0)


Algoritmusok tömbök rendezésére polinomiális időben

A rendezés az objektumok átrendezésének folyamata egy adott adottsághoz

aggregálódik egy bizonyos sorrendben (növekvő

vagy csökkenő).

A válogatás célja általában a későbbiek megkönnyítése

elemek keresése egy rendezett halmazban.

Egyszerű beillesztési rendezés

void sort_by_insertion(kulcs a , int N)

for (i=1; i< N; i++)

for (j=i-1; (j>=0)&& (x< a[j]); j--)

a = a[j];

Egyszerű beillesztési rendezés elemzése

Összehasonlítások száma:

C (N) = 1 + 2 + 3 + ... + N - 1 = (N * (N -1))/2 = O (N 2)

Teljes idő: T(N) = θ(N 2)

Rendezés egyszerű cserével. Buborékos módszer.

void bubble_sort (a kulcs, int N)

for (i=0; i

(j=N-l; j>i; j--)

ha (a > a[j]) (

x = a[j]; a[j] = a[j-1]; a[j-1] = x;

Az elemzés rendezése egyszerű cserével

Legrosszabb eset: fordított sorrendű tömb

Összehasonlítások száma:

C(N) = (N - 1) + (N - 2) + ... + 1 = (N * (N-1))/2 = O (N 2)

Teljes idő: T(N) = θ(N 2)


Kiegészítés

Csomópont* _Hozzáadás(Csomópont *r, T s)

r = új csomópont(ok);

különben ha(s< r->inf)

r->left = _Add(r->left, s);

r->jobbra = _Hozzáadás(r->jobbra, s);


Elem eltávolítása a fáról

T fa n gyökérrel és K kulccsal.

távolítson el egy csomópontot a T fából a K kulccsal (ha van).

Algoritmus:

Ha a T fa üres, álljon meg;

Ellenkező esetben hasonlítsa össze K-t az n gyökércsomópont X kulcsával.

Ha K>X, rekurzív módon távolítsa el K-t a T jobb oldali részfájából;

Ha K

Ha K=X, akkor három esetet kell figyelembe venni.

Ha mindkét gyermek nem létezik, akkor töröljük az aktuális csomópontot, és visszaállítjuk a hivatkozást a szülő csomópontból;

Ha valamelyik gyermek hiányzik, akkor a gyökércsomópont megfelelő értékei helyett az m gyermek mezők értékeit helyezzük el, felülírva a régi értékeit, és felszabadítjuk az m csomópont által elfoglalt memóriát;

Ha mindkét gyerek jelen van, akkor megkeressük azt az m csomópontot, amelyik az adott mellett van;

másolja át az adatokat (kivéve a gyermekelemekre mutató hivatkozásokat) m-ből n-be;

rekurzívan törölje az m csomópontot.

A következő elem adott

Adott: T fa és x kulcs

Visszatérés az x vagy NULL melletti elemre, ha x az utolsó elem a fában.

Algoritmus:

Két esetet külön vesz figyelembe.

Ha egy x csúcs jobb oldali részfája nem üres, akkor az x melletti elem a minimális elem ebben a részfában.

Ellenkező esetben, ha az x csúcs jobb oldali részfája üres. Feljebb lépünk x-ből, amíg meg nem találunk egy csúcsot, amely a szülőjének bal oldali gyermeke. Ez a szülő (ha van) lesz a keresett elem.


Csomópontok beillesztése

Az új kulcs beszúrása egy AVL fába ugyanúgy történik, mint az egyszerű keresési fákban: lefelé megyünk a fán, kiválasztva a jobb vagy bal mozgási irányt az aktuális csomópontban lévő kulcs összehasonlításának eredményétől függően. a behelyezett kulcsot.

Az egyetlen különbség az, hogy a rekurzióból való visszatéréskor (vagyis miután a kulcsot beszúrták a jobb vagy a bal részfába), az aktuális csomópont kiegyensúlyozódik. Az ilyen beillesztéssel fellépő kiegyensúlyozatlanság a mozgási út bármely csomópontjában nem haladja meg a kettőt, ami azt jelenti, hogy a fent leírt kiegyenlítő függvény alkalmazása helyes.

Csomópontok eltávolítása

Az AVL-fából egy csúcs eltávolításához az algoritmust veszik alapul, amelyet általában akkor használnak, amikor csomópontokat távolítanak el egy szabványos bináris keresési fából. Megtaláljuk a p csomópontot az adott k kulccsal, a jobb oldali részfában megtaláljuk a legkisebb kulcsú min csomópontot és a törölt p csomópontot a talált min csomópontra cseréljük.

A megvalósítás során több lehetőség is felmerül. Először is, ha a talált p csomópontnak nincs jobb oldali részfája, akkor az AVL fa tulajdonsága szerint a bal oldalon ennek a csomópontnak csak egyetlen gyermekcsomópontja lehet (1 magasságú fa), vagy a p csomópont akár egy levél. Mindkét esetben egyszerűen töröljük a p csomópontot, és ennek eredményeként visszaadunk egy mutatót a p bal oldali gyermekcsomópontjára.

Legyen most p-nek egy jobb oldali részfája. Ebben a részfában megtaláljuk a minimális kulcsot. A bináris keresőfa tulajdonságai szerint ez a kulcs a bal ág végén található, a fa gyökerétől kezdve. A rekurzív findmin függvényt használjuk.

Removemin függvény - a minimális elem eltávolítása egy adott fából. Az AVL fa tulajdonságai szerint a jobb oldali minimális elem vagy egyetlen csomóponttal rendelkezik, vagy üres. Mindkét esetben csak vissza kell vinnie a mutatót a megfelelő csomópontra, és a visszaúton (a rekurzióból visszatérve) egyensúlyozást kell végrehajtania.


Hash táblák, láncolási módszer

A közvetlen címzést kis kulcskészletekhez használják. Meg kell határozni egy dinamikus halmazt, melynek minden eleméhez tartozik egy kulcs az U = (0,1,..., m - 1) halmazból, ahol m nem túl nagy, nincs két egyforma kulcsú elem.

Egy dinamikus halmaz ábrázolására egy T tömböt (közvetlenül címzett táblát) használnak, amelynek minden pozíciója vagy cellája az U kulcstér egyik kulcsának felel meg.

A k cella a k kulcsú halmaz egy elemére mutat. Ha a halmaz nem tartalmaz k kulcsú elemet, akkor T[k] = NULL.

A kulcskeresési művelet időt vesz igénybe O(1)

A közvetlen címzés hátrányai:

Ha az U kulcstér nagy, egy |U| méretű T táblázat tárolása nem praktikus, ha nem lehetetlen, a rendelkezésre álló memória mennyiségétől és a kulcstér méretétől függően

A ténylegesen tárolt kulcsok K halmaza kicsi lehet az U kulcstérhez képest, ebben az esetben a T táblához lefoglalt memória nagyrészt elpazarolódik.

A hash függvény egy h függvény, amely meghatározza az U halmaz elemeinek helyét a T táblázatban.



A kivonatolás során egy k kulcsú elemet a h(k) cellában tárolunk, a h hash függvény segítségével számítjuk ki az adott k kulcs celláját. A h függvény leképezi az U kulcsteret a T hash tábla celláira [O..m - 1]:

h: U → (0,1,..., m -1).

a h(k) értéket a k kulcs hash értékének nevezzük.

Ha a szótárban tárolt K kulcshalmaz sokkal kisebb, mint a lehetséges U kulcsok tere, egy hash tábla lényegesen kevesebb helyet igényel, mint egy közvetlenül címző tábla.

A hash függvény célja a tömbindexek munkatartományának csökkentése, és |U| helyett értékeket, meg lehet boldogulni mindössze m értékkel.

A memóriaigény θ(|K|)-ra csökkenthető, miközben egy elem keresési ideje a hash táblában O(1) marad - ez egy korlát az átlagos keresési időhöz, míg a közvetlen keresési idő címzési tábla ez a korlát a legrosszabb esetre érvényes.

Az ütközés olyan helyzet, amikor két kulcs ugyanahhoz a cellához van rendelve.

Például h(43) = h(89) = h(112) = k

Láncos módszer:

Ötlet: Egy halmaz elemeinek tárolása ugyanazzal a hash értékkel, mint egy lista.

h(51) = h(49) = h(63) = i

Elemzés

Legrosszabb eset: ha a hash függvény a halmaz összes elemére ugyanazt az értéket adja. A hozzáférési idő Θ(n), |U-val | = n.

Átlagos eset: arra az esetre, amikor a hash értékek egyenletesen oszlanak el.

Minden kulcs egyformán valószínű, hogy a táblázat bármely cellájába kerül, függetlenül attól, hogy a többi kulcs hova esik.

Legyen adott egy T tábla és tároljunk benne n kulcsot.

Ekkor a = n/m a kulcsok átlagos száma a táblázat celláiban.

A hiányzó elem keresésének ideje Θ(1 + α).

Állandó idő a hash függvény értékének kiszámításához plusz a lista végéig való bejáráshoz szükséges idő, mert a lista átlagos hossza α, akkor az eredmény Θ(1) + Θ(α) = Θ(1 + α)

Ha a táblázat celláinak száma arányos a benne tárolt elemek számával, akkor n = O(m) és ezért α = n/m = O(m)/m = O(1), ami azt jelenti, egy elem a hash táblázatban átlagosan Θ(1) időt igényel.

Tevékenységek

Elem beszúrása egy táblázatba

Eltávolítás

is O(1) időt igényel

Hash függvény kiválasztása

A kulcsokat egyenletesen kell elosztani az összes cellában

A hash funkcióbillentyűk eloszlási mintája nem korrelálhat az adatok mintázataival. (Például az adatok páros számok).

Mód:

Osztási módszer

Szorzási módszer

Osztási módszer

h (k) = k mod m

Az m kis osztó problémája

1. számú példa. m = 2és minden kulcs páros Þ páratlan cellák nem

megtöltött.

2. példa. m = 2 rÞ A hash nem függ a fenti bitektől r.

Szorzási módszer

Hadd m= 2 r , a kulcsok w-bites szavak.

h(k) = (A k mod 2 w) >> (w - r), Ahol

A mod 2 = 1 ∩ 2 w-1< A< 2 w

Nem szabad választani A közel 2 v-1És 2w

Ez a módszer gyorsabb, mint az osztási módszer

Szorzási módszer: példa

m = 8 = 2 3, w = 7

Címzés megnyitása: keresés

A keresés is szekvenciális kutatás

Siker, ha megtalálják a jelentést

Hiba, ha üres cellát talál, vagy átmegy az egész táblázaton.

Kutatási stratégiák

Lineáris -

h(k, i) = (h′(k) + i) mod m

Ez a stratégia könnyen megvalósítható, de problémáknak van kitéve

hosszú sorozat létrehozásához kapcsolódó elsődleges klaszterezés

az elfoglalt sejtek aktivitása, ami növeli az átlagos keresési időt.

Négyzetes

h(k, i) = (h′(k) + c 1 i+ c 2 i 2) mod m

ahol h′(k) egy szabályos hash függvény

Dupla kivonatolás –

h(k,i) = (h 1 (k) + i h 2 (k)) mod m.

Dupla hashelés

Ez a módszer kiváló eredményeket ad, de h 2 (k) m-nek másodlagosnak kell lennie.

Ezt lehet elérni:

Ha m-t használunk kettő hatványaként, és h 2 (k)-t állítunk elő, csak páratlan számokat kapunk

m = 2 r иh 2 (k)- páratlan.

m- prímszám, értékek h 2 – m-nél kisebb pozitív egész számok

Egyszerűen m telepíthető

h1(k)=k mod m

h2(k)=1+(k mod m’)

m’ kisebb mint m (m’ =m-1 vagy m-2)

Nyitott címzés: Beillesztési példa

Legyen adott az A táblázat:

Dupla hashelés

h2(k)=1+(k mod 11)

Hol lesz beágyazva az elem?

Nyílt címzéselemzés

További feltevés az egységes kivonatoláshoz: minden kulcs egyformán valószínű, hogy megkapja bármelyik m! táblázatfeltáró sorozatok permutációi

a többi kulcstól függetlenül.

Hiányzó elem megtalálása

A sikeres kereséshez szükséges próbálkozások száma

Nyitott címzés

A< 1 - const Þ O(1)

Hogyan viselkedik? V:

táblázat 50%-ban teljes Þ2 kutatás

A táblázat 90%-ban teljes Þ 10 vizsgálatot tartalmaz

A táblázat 100%-ban teljes Þ m tanulmányokat tartalmaz


A mohó választás elve

Azt mondják, hogy ez az optimalizálási problémára alkalmazható mohó választás elve, ha a lokálisan optimális választások sorozata globálisan optimális megoldást ad.

Az optimalitás bizonyítása általában a következő mintát követi:

Bebizonyosodott, hogy a mohó választás az első lépésnél nem zárja le az utat az optimális megoldás felé: minden megoldáshoz létezik egy másik, amely összhangban van a mohó választással, és nem rosszabb, mint az első.

Megmutatjuk, hogy az első lépésben a mohó választás után fellépő részprobléma hasonló az eredetihez.

Az érvelést indukció fejezi be.

Optimális részfeladatokhoz

A probléma állítólag az ingatlannal van részproblémák optimálissága, ha egy probléma optimális megoldása optimális megoldást tartalmaz az összes részproblémára.


A Huffman-kód felépítése

Bármely üzenet valamilyen ábécé karaktersorozatából áll. A memória megtakarítása és az információátvitel sebességének növelése érdekében gyakran felmerül az információ tömörítésének feladata. Ebben az esetben speciális karakterkódolási módszereket használnak. Ide tartoznak a Huffman-kódok, amelyek az információ típusától függően 20%-tól 90%-ig terjedő tömörítést biztosítanak.

A Huffman algoritmus megtalálja az optimális karakterkódokat a tömörített szövegben használt karakterek gyakorisága alapján.

A Huffman-algoritmus egy példa a mohó algoritmusra.

Tegyük fel, hogy egy 100 000 karakter hosszúságú fájlban a karakterek gyakorisága ismert:

Meg kell alkotnunk egy bináris kódot, amelyben minden karakter véges bitsorozatként van ábrázolva, amelyet kódszónak nevezünk. Egységes kód használatakor, amelyben minden kódszó azonos hosszúságú, karakterenként minimum három bitet kell elkölteni, és a teljes fájl 300 000 bitet fogyaszt.

Az egyenetlen kódok gazdaságosabbak, ha a gyakran előforduló karaktereket rövid bitsorozatokkal, a ritkán előforduló karaktereket pedig hosszú sorozatokkal kódolják. Kódoláskor a teljes fájl költsége (45*1 + 13*3 + 12*3 + 16*3 + 9*4 + 5*4)*1000 = 224000. Vagyis az egyenetlen kód körülbelül 25%-os megtakarítást eredményez.

Előtag kódok

Tekintsünk olyan kódokat, amelyekben a különböző karaktereket képviselő két bitsorozat mindegyikéhez egyik sem előtagja a másiknak. Az ilyen kódokat prefix kódoknak nevezzük.

Kódoláskor minden karaktert saját kóddal helyettesítünk. Például az abc karakterlánc így néz ki: 0101100. Előtagkód esetén a dekódolás egyedi, és balról jobbra haladva történik.

Az előtagkóddal kódolt szöveg első karaktere egyedileg meghatározott, mivel a kódszava nem lehet más karakter eleje. Miután azonosította ezt a szimbólumot, és elvetette a kódszót, megismételjük a folyamatot a fennmaradó bitekre, és így tovább.

A dekódolás hatékony megvalósításához kényelmes formában kell tárolnia a kóddal kapcsolatos információkat. Az egyik lehetőség az, hogy a kódot mint kód bináris fa, amelynek levelei megfelelnek a kódolandó karaktereknek. Ebben az esetben a gyökértől a kódolt szimbólumig vezető út határozza meg a bitek kódolási sorrendjét: a fa mentén balra haladva 0, jobbra haladva 1-et kapunk.

A belső csomópontok a megfelelő részfa leveleihez tartozó frekvenciák összegét jelzik.

Optimális a ez a fájl A kód mindig egy bináris fának felel meg, amelyben minden olyan csúcsnak, amely nem levél, két gyermeke van. Az egységes kód nem optimális, mivel a megfelelő fának van egy fia csúcsa.

Egy olyan fájl optimális előtagkódjának fája, amely valamilyen C halmaz összes karakterét használja, és csak azok tartalmazzák pontosan | C | minden szimbólumhoz egyet hagy, és pontosan | C | - 1 csomópont, amely nem levelek.

Az előtag kódjának megfelelő T fa ismeretében könnyen meg lehet találni a fájl kódolásához szükséges bitek számát. A C ábécé minden c karakterére jelölje f[c] az előfordulások számát a fájlban, dT(c) pedig a megfelelő levél mélységét, és így a c kódoló bitsorozat hosszát. Ezután a fájl kódolásához szüksége lesz:

Ezt az értéket a T fa költségének nevezzük. Ezt az értéket minimálisra kell csökkenteni.

Huffman egy mohó algoritmust javasolt, amely optimális előtag kódot hoz létre. Az algoritmus az optimális kódnak megfelelő T fát alkot alulról felfelé, a | halmazból kiindulva C | levelek és készítés | C | - 1 egyesülés.

Minden szimbólumhoz meg van adva az f [c] gyakorisága. Két egyesítendő objektum megtalálásához egy Q prioritási sor kerül felhasználásra, f frekvenciákat használva prioritásként – a két legalacsonyabb frekvenciájú objektumot egyesíti.

Az egyesülés eredményeként egy új objektum (belső csúcs) keletkezik, melynek gyakoriságát kiszámítjuk összeggel egyenlő két egyesített objektum frekvenciája. Ez a csúcs hozzáadódik a sorhoz.

Huffman ( C)

1. n ←│C│ │ C │ - C teljesítmény

2. K ← C Q – prioritási sor

3. számáraén ← 1 nak nek n-1

4. csináld z ← Create_Node() z – f, bal, jobb mezőkből álló csomópont

5. x ← balra [z] ← Sorból(K)

6. y ← jobbra [z] ← Sorból(K)

7. f[z] ← f[x] + f[y]

8. Sorba állítás(Q,z)

9. visszatérés Sorból(Q) adja vissza a fa gyökerét

Fokozat

A várólista bináris halomként van megvalósítva.

Létrehozhat egy sort az O(n).

Az algoritmus egy ciklusból áll, amelyet n-1 alkalommal hajtanak végre.

Minden sorművelet O(log n)-ben fejeződik be.

A teljes futási idő O(n log n).

Hálózatépítési probléma

Előfordulási területek: kommunikáció és úthálózatok.

Adott: sok hálózati csomópont (gazdagépek, városok).

Szükséges: a legkisebb összéltömegű hálózat kiépítése (hálózati kábelek hossza, utak hossza).

Grafikon modell: A hálózati csomópontok gráfcsomópontok, E = V 2, ismerjük az összes él súlyát.

Eredmény: szabad fa.

A MOD keresésének megközelítése

Az A fát úgy hozzuk létre, hogy egy-egy élt adunk hozzá, és minden iteráció előtt az aktuális fa valamilyen MOD részhalmaza.

Az algoritmus minden lépésében meghatározunk egy élt (u, v), amely hozzáadható A-hoz anélkül, hogy megsértené ezt a tulajdonságot. Az ilyen élt széfnek hívjuk

Általános MST(G,w)

2, míg A nem MOD

3 Keressen egy élt (u, v), amely biztonságos A számára

4 A ← A U ((u, v))

____________________________________________________________________________

A bordák osztályozása

1. Fa bordák(fa élek) a G gráf élei. Egy él (u, v) egy fa él, ha a v csúcs nyitva van ennek az élnek a vizsgálatakor.

2. Hátsó élek(hátsó élek) azok az élek (u,v), amelyek összekötik az u csúcsot a v ősével a mélység-első keresési fában. Az irányított gráfokban előforduló cikluséleket hátsó éleknek tekintjük.

3. Egyenes bordák(elülső élek) olyan élek (u,v), amelyek nem faélek, és összekötik az u csúcsot a v leszármazottjával a mélység-első keresési fában.

4. Kereszt bordák(kereszt élek) - a gráf összes többi éle. Összekapcsolhatják ugyanannak a mélységi keresőfának a csúcsait, ha egyik csúcs sem őse egy másiknak, vagy összekapcsolhatnak különböző fák csúcsait.

Az elosztott fájlrendszer algoritmusa módosítható úgy, hogy osztályozza a működés során talált éleket. A kulcsgondolat az, hogy minden él (u, v) osztályozható a v csúcs színe alapján, amikor először vizsgáljuk (bár az egyenes és a kereszt éleket nem különböztetjük meg).

  1. fehér szín jelzi, hogy ez egy fa széle.
  2. A szürke szín határozza meg a hátsó szegélyt.
  3. A fekete egyenes vagy keresztirányú élt jelöl.

Az első eset közvetlenül következik az algoritmus definíciójából.

Figyelembe véve a második esetet, vegyük figyelembe, hogy a szürke csúcsok mindig lineáris leszármazotti láncot alkotnak, amely megfelel a DFS_Visit eljárás aktív hívásainak; a szürke csúcsok száma eggyel nagyobb, mint a mélység-első keresési fa utolsó nyitott csúcsának mélysége. A feltárás mindig a legmélyebb szürke csúcstól indul, így egy másik szürke csúcsot elérő él eléri az eredeti csúcs ősét.

A harmadik esetben a fennmaradó élekkel van dolgunk, amelyek nem tartoznak az első vagy a második eset alá. Megmutatható, hogy egy él (u, v) egyenes, ha d [u]< d [v], и перекрестным, если d [u] >d[v]

___________________________________________________________________________

Topológiai rendezés

BAN BEN elsőbbségi oszlop minden él (u, v) azt jelenti, hogy u megelőzi v

Topológiai rendezés A gráf egy a sorozat felépítése, ahol minden a i és a j esetén teljesül a következő: $(a i ,a j) Þ i< j.

Egy orientált aciklikus gráf G = (V, E) topológiai rendezése az összes csúcsának lineáris rendezése úgy, hogy ha a G gráf tartalmaz egy élt (u,v), akkor u ebben a sorrendben v előtt helyezkedik el (ha a gráf nem aciklikus, ilyen rendezés nem lehetséges). A gráf topológiai rendezése úgy is felfogható, hogy csúcsait egy vízszintes vonal mentén rendezi el úgy, hogy minden él balról jobbra irányuljon.

Rendezett sorrend: C2, C6, C7, C1, C3, C4, C5, C8

minden(u V-ben) szín[u] = fehér; // inicializál

L = új linkelt_lista; // L egy üres linkelt lista

mindegyikhez (u az V-ben)

if (szín[u] == fehér) TopVisit(u);

visszatérés L; // L megadja a végső sorrendet

TopVisit(u) ( // keresés indítása az u címen

szín[u] = szürke; // jelölje meg, hogy meglátogatta

mindegyikhez (v az Adj(u)-ban)

if (szín[v] == fehér) TopVisit(v);

Add hozzá az u-t L elejéhez; // a befejezéskor hozzáadja a listához

T (n) = Θ(V + E)



Eljárások

Létrehozás – Beállítás (u)- hozzon létre egy halmazt egy csúcsból u.

Keresés – Beállítás (u)- keresse meg a halmazt, amelyhez a csúcs tartozik umilyen készletben tér vissza a megadott elem található. Valójában ez visszaadja a halmaz egyik elemét (az úgynevezett reprezentatív vagy vezető). Ezt a képviselőt minden halmazban maga az adatstruktúra választja ki (és idővel változhat, nevezetesen hívások után Unió).

Ha a Find - Set hívás néhány két elemre ugyanazt az értéket adta vissza, akkor ez azt jelenti, hogy ezek az elemek ugyanabban a halmazban vannak, ellenkező esetben pedig különböző halmazokban vannak.

Unió (u,v)- kombinálja a csúcsokat tartalmazó halmazokat uÉs v

Elemkészleteket fogunk tárolni az űrlapon fák: egy fa egy halmaznak felel meg. A fa gyökere a halmaz képviselője (vezetője).

Megvalósításkor ez azt jelenti, hogy létrehozunk egy tömböt szülő, amelyben minden elemhez eltárolunk egy hivatkozást a fában lévő őséhez. A fák gyökereinél feltételezzük, hogy az ősük ők maguk (azaz a link hurkok ezen a helyen).

Új elem létrehozásához (művelet Létrehozás – Beállítás), egyszerűen létrehozunk egy v csúcsban gyökerező fát, megjegyezve, hogy az őse ő maga.

Két készlet kombinálásához (művelet Unió(a,b)), először megkeressük annak a halmaznak a vezetőit, amelyben a található, és annak a halmaznak, amelyben b található. Ha a vezetők egybeesnek, akkor nem teszünk semmit - ez azt jelenti, hogy a készletek már egyesültek. Ellenkező esetben egyszerűen megadhatja, hogy a b csúcs őse egyenlő f-vel (vagy fordítva) - ezzel összekapcsolva az egyik fát a másikkal.

Végül a vezető keresési művelet végrehajtása ( Keresés – halmaz(v)) egyszerű: a v csúcsból mászzuk fel az ősöket, amíg el nem érjük a gyökért, i.e. míg az ősre való hivatkozás nem önmagához vezet. Kényelmesebb ezt a műveletet rekurzívan végrehajtani.

Útvonal-tömörítési heurisztika

Ez a heurisztika a dolgok felgyorsítására szolgál Keresés - Set() .

Abban rejlik, hogy amikor a hívás után Keresés – halmaz(v) megtaláljuk a keresett vezetőt pállítsa be, majd emlékezzen erre a csúcson vés az összes csúcs elhaladt az úton – ez a vezető p. Ennek legegyszerűbb módja, ha átirányítja őket szülő erre a csúcsra p .

Így az ősök tömbje szülő a jelentés némileg megváltozik: most az tömörített őstömb, azaz minden csúcsra nem a közvetlen ős tárolható ott, hanem az ős őse, az ős ősének őse stb.

Másrészt világos, hogy lehetetlen ezeket a mutatókat megtenni szülő mindig a vezetőre mutatott: különben művelet végrehajtása során Unió frissítenie kellene a vezetőket Tovább) elemeket.

Így a tömbhöz szülő pontosan az ősök tömbjeként kell megközelíteni, esetleg részben tömörítve.

Az útvonaltömörítési heurisztika alkalmazása lehetővé teszi a logaritmikus aszimptotika elérését: kérésenként átlagosan

Elemzés

Inicializálás – O(V)

A hurok V-szer fut, és minden kivonatmin művelet - O(logV) alkalommal fut, összesen O(V logV) alkalommal

A for hurok O(E)-szer fut, a downKey pedig O(logV) időt vesz igénybe.

Teljes futási idő – O(V log V +E logV)= O(E logV)



Legrövidebb út tulajdonság

Hadd p = (v 1 , v 2 ..... v k)- a legrövidebb út a v 1 csúcstól a csúcsig vk adott súlyozott irányított gráfban G = (U. E) súly funkcióval w: E → R a p ij = (v i , v i+1 .....v j)- a p út részleges útja, amely a csúcsból indul ki v i a csúcsra v jönkényesnek i és j (1 ≤ i< j ≤ k). Akkor p ij– a legrövidebb út a csúcstól v i Nak nek v i

Dijkstra(G, w, s) (

mindegyikhez (u V-ben) ( // inicializálás

d [u] = +végtelen

szín [u] = fehér

d[s] =0 // a forrás távolsága 0

Q = new PriQueue(V) // az összes csúcsot Q-ba helyezzük

while (Q.nonEmpty()) ( // amíg az összes csúcs fel nem dolgozott

u = Q.extractMin() // az s-hez legközelebb eső u kiválasztása

mindegyikhez (v az Adj[u]-ban) (

if (d[u] + w(u,v)< d[v]) { // Relax(u,v)

d [v] = d [u] + w(u,v)

Q.decreaseKey(v, d[v])

szín [u] = fekete


Nehézségi fokozat

A Bellman-Ford algoritmus egy időn belül leáll O(V*E), mivel az 1. sorban az inicializálás O(V) időt vesz igénybe, minden |V| - A 2-4. sorban az élek mentén 1 lépés O(E) időt vesz igénybe, az 5-7. sorban a for ciklus végrehajtása O(E) időt vesz igénybe. .

Az algoritmus aszimptotikus kiértékelése

Algoritmus elemzés – a számítógépes programok teljesítményének és az általuk felhasznált erőforrásoknak elméleti tanulmányozása.

Teljesítmény – az algoritmus működési ideje, a bemeneti adatok mennyiségétől függően.

A T(n) függvény határozza meg, ahol n a bemeneti adatok mennyisége

Az elemzés típusai

Legrosszabb esetben: T(n)– az n méretű bemeneti adatok maximális ideje.

Átlagos eset: T(n)– az n méretű bemeneti adatok várható ideje.

A legjobb eset a minimális működési idő.

Aszimptotikus becslés

O- jelölés: aszimptotikus felső határ

f (n) = O(g(n)) Þ

($c > 0, n 0 > 0 Þ 0 ≤ f (n) ≤ c g(n), n ≥ n 0)

O(g(n)) = (f (n) : $ c > 0, n 0 > 0 Þ 0 ≤ f (n) ≤ c g(n), n ≥ n 0)

Példa: 2n 2 = O(n 3)


Rekurzív algoritmusok, aszimptotikus becslés felépítése. Példa

Összevonás rendezés

sort(А,p,r) //p - a tömb eleje, r - a tömb vége T(n)

if(o< r) //1

q=(p + r)/2; //Számítsd ki az 1. tömb közepét

sort(A,p,q); //T(n/2) bal oldalának rendezése

rendez(A,q+1,r); //T(n/2) jobb oldalának rendezése

merge(p,q,r); //két tömböt egyesít egy n-be



Hasonló cikkek