Stabil fajta

Az oldal jelenlegi verzióját még nem ellenőrizték tapasztalt közreműködők, és jelentősen eltérhet a 2020. január 8-án felülvizsgált verziótól ; az ellenőrzések 2 szerkesztést igényelnek .

Stabil (stabil) rendezés  - rendezés , amely nem változtatja meg az azonos kulcsokkal rendelkező rendezett elemek egymáshoz viszonyított sorrendjét, amely alapján a rendezés megtörténik.

A stabilitás nagyon fontos jellemzője egy rendezési algoritmusnak, de ennek ellenére szinte mindig elérhető az eredeti kulcsok meghosszabbításával az eredeti sorrendre vonatkozó további információkkal. Az elnevezésből adódó látszólagos szükségesség ellenére a stabilitás egyáltalán nem szükséges a helyes rendezéshez, és legtöbbször nem is figyelhető meg, mivel ennek biztosításához szinte mindig többletmemória és idő szükséges.

Példa

A [vezetéknév, keresztnév, apanév] formátumú rekordok vezetéknév szerinti rendezésekor az Ivanov Sergey és Ivanov Ivan kulcsértékei ugyanazok lesznek, így a stabil rendezés nem cseréli fel Szergejt és Ivant ( Python 3 , timsort rendezés [1] ):

records = [ { 'vezetéknév' : 'Ivanov' , 'utónév' : 'Sergej' , 'patronymic' : 'Mihajlovics' ,}, { 'vezetéknév' : 'Ivanov' , 'utónév' : 'Iván' , ' apanév' : 'Borisovics' ,}, { 'vezetéknév' : 'Abramov' , 'utónév' : 'Jurij' , 'patronymic' : 'Petrovics' ,}, ] rekordok . sort ( kulcs = lambda x : x [ 'vezetéknév' ]) # rendezés "vezetéknév" kulcs szerint a rekordokban lévő r számára : print ( ' %-12s %-12s %-12s ' % ( r [ 'vezetéknév' ] , r [ 'utónév' ], r [ 'apanév' ]))

Ennek eredményeként a rekordok csak vezetéknév szerint lesznek rendezve, miközben az azonos vezetéknévvel rendelkező rekordok között megmarad az eredeti sorrend:

Abramov Jurij Petrovics Ivanov Szergej Mihajlovics Ivanov Ivan Borisovics

Alkalmazás

A rendezésnek, amely a Burroughs-Wheeler transzformáció fő költségeleme , robusztusnak kell lennie.

Stabilrendezési algoritmusok

Az alábbiakban olyan robusztus rendezési algoritmusok leírása található, amelyek futási ideje nem rosszabb, mint O( n log 2 n ) (kivéve a stabil particionálást használó algoritmus legrosszabb eseteit). Minden pszeudokódban a // perjel pár megjegyzést jelent a sor végére, mint a C++-ban.

Egyesítés rendezés extra memóriával

Ezzel a rendezési algoritmussal a kezdeti tömb először rekurzívan fel van osztva részekre, amíg a tömb minden része egy vagy nulla elemet nem tartalmaz (a felezést minden rekurziós lépésnél végrehajtják). Ezt követően a kapott részek páronként "összeolvadnak". Az algoritmus általános összetettsége O( n log n ), és az algoritmus további O( n ) memóriát igényel az egyesített tömbök tárolásához.

Rendezés stabil particionálással

Ez az algoritmus hasonló a gyorsrendezési algoritmushoz . Csakúgy, mint a gyorsrendezési algoritmusban, ebben az algoritmusban is a kezdeti tömb két részre oszlik - az egyikben minden elem kisebb vagy egyenlő, mint valamelyik pivot elem, a másikban pedig nagyobb vagy egyenlő. Ezt követően a tömb elválasztott részeit ugyanígy rekurzív módon rendezzük. Az algoritmus és a gyorsrendezés közötti különbség az, hogy instabil partíció helyett stabil partíciót használ. Az alábbiakban bemutatjuk ennek az algoritmusnak a megvalósítását pszeudokódban:

Rendezés (a[1..n]) if(n > 1) akkor N ← a[ 1 ]; m ← StablePartition(a[1..n], N); Rendezés(a[1..m]); Rendezés(a[m+1..n]); Stabil partíció (a[1..n], N) ha( n > 1 ) akkor m ← ⌊ n/2 ⌋; m1 ← StablePartition(a[1..m], N); m2 ← m+StablePartition(a[m+1..n], N); Forgatás(a[m1..m2], m); return(m1 + (m2-m)); más if(a[1] < N) akkor return(1); más vissza (0)

Forgatási eljárás:

Forgatás (a[1..n], m) if( n > m > 1 ) //a tömbnek legalább két eleme van, és az eltolásnak van értelme műszak ← mn; //eltolandó pozíciók száma gcd ← GCD(m-1, n); //A GCD a legnagyobb közös osztó i ← 0-tól gcd-1-ig csináld fej ← i+1; fejVal ← a[fej]; áram ← fej; következő ← fej+váltás; while(next ≠ head) do a[aktuális] ← a[következő]; jelenlegi ← következő; next ← next+shift; if(next>n) next ← next-n; a[current] ← headVal;

O( n log n ) elemi műveletekre van szükség a tömb fenntartható particionálásához . Mivel a végrehajtott rekurzív süllyedés "mélysége" átlagosan O(log n ), az algoritmus összetettsége O( n log² n ). Ebben az esetben az algoritmusnak O(log n ) veremmemóriára lesz szüksége a rekurzió végrehajtásához, bár a legrosszabb esetben a teljes komplexitás O( n ² log n ) és O( n ) memória szükséges .

A rendezési algoritmusok egyesítése extra memória nélkül

Az alábbiakban ismertetett összes algoritmus a már rendezett tömbök összevonásán alapul, anélkül, hogy az O(log² n ) veremmemórián kívül további memóriát használna (ez az úgynevezett minimális memóriafeltétel [2] ), és csak az összevonási algoritmusban különböznek. A következő az algoritmusok pszeudokódja (az összevonási algoritmusokat - a Merge eljárást - mindegyik metódushoz külön adjuk meg):

Rendezés (a[1..n]) ha( n > 1 ) akkor m ← n/2 ; Rendezés(a[1..m]); Rendezés(a[m+1..n]); Merge(a[1..n], m); Pratt algoritmusa

A Pratt-algoritmusban két elem α és β található rendezett tömbökben , amelyek a mindkét tömb elemeiből álló tömb mediánjai. Ekkor az egész tömb aαbxβy -ként ábrázolható . Ezt követően az elemek ciklikus eltolását hajtjuk végre, aminek eredményeként a következő sorozatot kapjuk: axβαby . Továbbá az egyesítési algoritmus rekurzívan megismétlődik az ax és by tömbök esetében.

Egyesítés(a[1..n], m) if(m ≠ 1 és m ≠ n) //ez a feltétel azt jelenti, hogy a tömbnek legalább 2 elemből kell állnia if( n-1 > 2 ) //azt az esetet, amikor pontosan két elem van, külön kell figyelembe venni if( m-1 > nm ) leftBound←1; rightBound←m; while( leftBound < rightBound ) //keresse a mediánokat ebben a ciklusban m1 ← (leftBound+rightBound)/2; m2 ← FindFirstPosition(a[m..n], a[ m1 ]); //A FindFirstPosition eljárás megvalósítása lásd a következőt. bekezdés if( m1 + (m2-m) > n/2 ) akkor rightBound ← m1-1; más leftBound ← m1+1; Forgatás(a[m1..m2], m); Egyesítés(a[1..m1+(m2-m)], m1); Egyesítés(a[m1+(m2-m)+1..n], m2); más if(a[m] < a[1]) a[m]⟷a[1];

Az elemek eltolásához O ( n ) elemműveletekre és O(1) további memóriára van szükség, míg a mediánok megtalálásához két már rendezett tömbben O(log² n ) elemműveletekre és O(1) további memóriára van szükség. Mivel vannak O(log n ) rekurziós lépések, egy ilyen összevonási algoritmus bonyolultsága O( n log n ), a rendezési algoritmus összetettsége pedig O( n log² n ). Ebben az esetben az algoritmusnak O(log² n ) veremmemóriára lesz szüksége.

Algoritmus mediánok keresése nélkül

Az előző algoritmusban a mediánok keresésétől a következőképpen szabadulhat meg. A két tömb közül a nagyobbikban válassza ki a középső α elemet (a pivot elemet). Ezután a kisebb tömbben keresse meg egy α-nál nagyobb vagy azzal egyenlő elem első előfordulásának helyét. Nevezzük β-nak. Ezt követően az elemek ciklikusan eltolódnak, mint a Pratt-algoritmusban ( aαbxβy → axβαby ), és a kapott részeket rekurzívan egyesítik. Az összevonási algoritmus pszeudokódja alább látható.

Egyesítés(a[1..n], m) if(m ≠ 1 és m ≠ n) //ez a feltétel azt jelenti, hogy a tömbnek legalább 2 elemből kell állnia if( n-1 > 2 ) //az az eset, amikor pontosan két elemet kell külön-külön figyelembe venni if( m-1 > nm ) m1 ← (m+1)/2 ; m2 ← FindFirstPosition(a[m..n], a[ m1 ]); más m2 ← (n+m)/2 ; m1 ← Find LastPosition(a[1..m], a[ m2 ]); Forgatás(a[m1..m2], m); Egyesítés(a[1..m1+(m2-m)], m1); Egyesítés(a[m1+(m2-m)+1..n], m2); más if(a[ m ] < a[ 1 ]) a[ m ]⟷a[ 1 ];

A FindFirstPosition és FindLastPosition eljárások szinte azonosak a bináris keresési eljárással:

FindFirstPosition(a[1..n], x) leftBound ← 1; rightBound ← n; áram ← 1; while(leftBound <rightBound) do aktuális ← ⌊(baloldali határ+jobb határ)/2⌋; if(a[ aktuális ] < x) akkor balra határos ← áram+1 más rightBound ← áram; visszatérés(aktuális); Utolsó pozíció keresése(a[1..n], x) leftBound ← 1; rightBound ← n; áram ← 1; while(leftBound <rightBound) do aktuális ← ⌊(baloldali határ+jobb határ)/2⌋; if( x < a[ jelenlegi ] ) akkor rightBound ← áram; más balra határos ← áram+1 visszatérés(aktuális);

A Pratt algoritmussal ellentétben ebben az algoritmusban a particionálás lényegében egyenlőtlen lehet. De még a legrosszabb esetben is az algoritmus a teljes tartományt [ a .. y ] felosztja 3:1 arányban (ha a második tartomány minden eleme nagyobb vagy kisebb, mint a referencia, és mindkét tartomány azonos számú elemek). Ez garantálja a rekurziós lépések logaritmikus számát bármely tömb összevonásakor. Így azt kapjuk, hogy a Pratt-algoritmushoz hasonlóan az egyesítési algoritmus bonyolultsága O( n log n ), a rendezési algoritmus összetettsége O( n log² n ), a szükséges memória pedig O(log² n ). ).

Stabil rendezés extra memória nélkül O( n log n ) időben

Az algoritmusok javításának módjai

  • Az összes fenti algoritmusban az eredeti tömb részekre bontásakor a rekurzív felosztás leállítható, ha a felosztó tömb mérete elég kicsi lesz. Ekkor bármelyik egyszerű rendezési algoritmus alkalmazható (pl . beszúrás rendezés ), amelyekről ismert, hogy gyorsabbak, mint az összetettek, ha a bemeneti tömb mérete kicsi. Valójában ez a technika nemcsak az itt bemutatott algoritmusokra alkalmazható, hanem bármely más olyan algoritmusra is, amely az eredeti tömb rekurzív particionálását használja (például a szokásos instabil gyorsrendezés ). A bemeneti elemek konkrét száma, amelynél át kell váltani egy egyszerű rendezési algoritmusra, a használt számítógéptől függ.
  • A Pratt-algoritmusban, a nem-mediáns algoritmusban és a robusztus partíciós algoritmusban ahelyett, hogy minden alkalommal rekurzívan hívná meg az egyesítést, dinamikusan lefoglalhat ideiglenes változók tömbjét. Ekkor csak addig lehet folytatni a tartomány felosztását, amíg a nagyobb tartományban lévő elemek száma nem éri el vagy egyenlő az ideiglenes tömb kapacitásával. Valójában a rekurzió egy lépésében a Pratt-algoritmus és a mediánok keresése nélküli algoritmus egyszerű összevonási algoritmussá alakul. Hogy. ha a rendszernek elegendő dinamikus memóriája van, akkor az aszimptotikus futási idő O( n log² n ) helyett O( n log n ) értékre hozható .

Jegyzetek

  1. Tim Sort, c2.com . Letöltve: 2012. november 18. Az eredetiből archiválva : 2013. június 14.
  2. Knut D., A programozás művészete. v. 3, Sorting and searching, M., Williams, 2000