Pass list
A kihagyási lista egy valószínűségi adatstruktúra , amely több párhuzamosan rendezett linkelt listán alapul , és a hatékonysága egy bináris fához hasonlítható (a legtöbb műveletnél O(log n) átlagos idő nagyságrendje).
A kihagyási lista egy rendezett linkelt lista kibővítésén alapul , további hivatkozásokkal, amelyeket véletlenszerű útvonalakon adnak hozzá geometriai /negatív binomiális eloszlással [1] , így a listakeresés gyorsan átugorhatja a lista egyes részeit. A beillesztés, a keresés és a törlés logaritmikus véletlenszerű időben történik.
Leírás
A kihagyási lista több rétegből áll. Az alsó réteg egy normál rendezett linkelt lista . Minden magasabb réteg egy "dedikált sávot" jelent az alábbi listák számára, ahol az i-edik réteg eleme az i+1-edik rétegben jelenik meg bizonyos p valószínűséggel (a p két leggyakrabban használt értéke 1 /2 és 1/ négy). Átlagosan minden elem 1/(1- p ) listákban fordul elő, a legfelső elem (általában a lista elején található speciális fejelem, hézagokkal) a listákban.
A kívánt elem keresése a felső lista head elemével kezdődik, és vízszintesen történik, amíg az aktuális elem nagyobb vagy egyenlő nem lesz, mint a cél. Ha az aktuális elem megegyezik a céllal, akkor a rendszer megtalálja. Ha az aktuális elem nagyobb, mint a cél, az eljárás megismétlődik, miután visszatért az előző elemhez, és függőlegesen ereszkedik le a következő alsó listára. Az egyes linkelt listákban a várható lépésszám 1/ p , ami a célelemtől visszanézve látható addig, amíg a következő magasabb listában megjelenő elemet el nem érjük. Így a keresés teljes várható költsége egyenlő p konstans esetén . A p különböző értékeinek kiválasztásával kiválasztható a szükséges kompromisszum a keresési idő költsége és a lista tárolásának memóriaköltsége között.
A megvalósítás részletei
A kihagyási listában használt elemek egynél több mutatót is tartalmazhatnak, így egynél több listában is szerepelhetnek.
A törlés és beszúrás műveletei nagyon hasonlóak a csatolt lista műveleteihez, azzal az eltéréssel, hogy a "high"-t egynél több csatolt listából kell beilleszteni vagy eltávolítani.
Véletlenszerűsítés nélkül azonban ezek a műveletek nagyon gyenge teljesítményt eredményeznének, mivel a listát teljesen meg kellene keverni egy új elem hozzáadásakor, hogy az átugrások száma állandó maradjon a legfelső szinten. William Pugh a következő algoritmust javasolta annak eldöntésére, hogy milyen magasra kell tolni egy új elemet. Ez az algoritmus csak helyi változtatásokat igényel a listán új elemek hozzáadásakor, és lehetővé teszi a "kifejezett vonalak" hatékonyságának mentését (l annak a szintnek a kapott értéke, amelyre az elemet el kívánja helyezni):
l = 1
míg véletlenszerű érték a [0,1] tartományban < p és l < maximum szint
l = l + 1
Ennek eredményeként az elem bekerül az l-edik szintre, és ennek megfelelően minden l-nél kisebb szintre.
Azok az Θ(n) műveletek, amelyekkel növekvő sorrendben meg kell látogatnunk az egyes csomópontokat (pl. a teljes lista kinyomtatása), lehetőséget adnak a hiányos lista szintstruktúrájának finom derandomizálására optimális módon, elérve a linkelt lista keresési idejét. . (az i-edik végcsomópont 1-es szint kiválasztása plusz ahányszor oszthatjuk i-t 2-vel, amíg páratlanná válik. Szintén i=0 negatívan végtelen fejléc esetén, a szokásos speciális eset, amikor a lehető legmagasabb szintet választjuk negatív és/vagy pozitív végtelen csomópontok.) Ez azonban lehetővé teszi, hogy valaki megtudja, hol van az összes 1-nél nagyobb szintű csomópont, majd eltávolíthatja őket.
Alternatív megoldásként a szintstruktúrát kvázi véletlenszerűvé tehetjük a következő módon:
hozza létre az összes 1. szintű csomópontot
j = 1
míg a j szinten lévő csomópontok száma > 1
minden i-edik csomópontra a j szinten
ha páratlan vagyok
ha i nem az utolsó csomópont a j szinten
véletlenszerűen válassza ki, hogy j+1 szintre emeli-e
másképp
ne reklámozzák
végfeltétel
különben, ha még az i-1 csomópont sem kerül előléptetésre
előrelép a j+1 szintre
végfeltétel
hurok vége for
j = j + 1
ciklus vége egyelőre
Csakúgy, mint a derandomizált változat, a kvázi-randomizálás csak akkor történik meg, ha valamilyen más ok van a Θ(n) műveletek végrehajtására (amelyek minden csomópontot meglátogatnak).
Megvalósítási példa
C++ osztálykód
std használatával :: swap ;
sablon < típusnév KEY_T , típusnév DATA_T >
class SkipList {
size_t MaxLevel ;
dupla SkipDivisor ;
struct pár {
KEY_T Kulcs ;
DATA_T Adatok ;
Pár * Előző ;
Tömb < Pár *> Következő ;
Pár ( const KEY_T & InKey , const DATA_T & InData , Pair * InPrevious , size_t InLevel );
Pár ( méret_t InLevel );
~ pár ();
Pair & operator = ( const Pair & InPair );
Pár * PreviousOnLevel ( size_t InLevel );
Pár * NextOnLevel ( size_t InLevel );
};
Pair Start ;
Pár * NewPrevious ( konst KEY_T & InKey );
Pair * PairByKey ( konst KEY_T & InKey );
size_tNewLevel ( );
nyilvános :
class Iterator {
SkipList * CurrentList ;
Pair * CurrentPair ;
barát osztály SkipList < KEY_T , DATA_T > ;
nyilvános :
Iterátor ( const Iterator & InIterator );
Iterátor ( SkipList & InSkipList );
operátor bool ();
Iterátor és operátor ++ ();
Iterátor és operátor -- ();
Iterátor operátor ++ ( int );
Iterátor operátor -- ( int );
Iterátor és operátor += ( méret_t n );
Iterátor és operátor -= ( méret_t n );
Iterátor & operátor = ( const Iterator & InIterator );
Iterátor & operátor = ( const KEY_T & InKey );
DATA_T & operátor * ();
void Törlés ();
};
SkipList ( size_t InNumberOfLanes = 3 , dupla InSkipDivisor = 1,0 / 4,0 );
~ SkipList ();
Iterátor GetBegin ();
Iterátor GetEnd ();
void Add ( const KEY_T & InKey , const DATA_T & InData );
void Delete ( const KEY_T & InKey );
DATA_T & operátor []( const KEY_T & InKey );
size_t Szám ( );
voidClear ( );
Iterátor keresés ( const DATA_T & Unknown );
bool IsEmpty ();
};
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Pair * SkipList < KEY_T , DATA_T >:: Pair :: PreviousOnLevel ( size_t InLevel ){
ha ( ! ez )
return NULL ;
Pár * Aktuális = Előző ;
for (; Jelenlegi && ! ( Aktuális -> Következő . Számlálás () >= InLevel + 1 ); Aktuális = Aktuális -> Előző ){}
visszatérés Áram ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Pair * SkipList < KEY_T , DATA_T >:: Pair :: NextOnLevel ( size_t InLevel ){
ha ( ! ez )
return NULL ;
Pár * Aktuális = Következő [ InLevel -1 ];
for (; Jelenlegi && ! ( Aktuális -> Következő . Számlálás () >= InLevel + 1 ); Aktuális = Aktuális -> Következő [ InLevel -1 ]){}
visszatérés Áram ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
SkipList < KEY_T , DATA_T >:: Pair :: Pair ( const KEY_T & InKey , const DATA_T & InData , Const DATA_T & InData , SkipList < KEY_T , DATA_T >:: Pair * InPrevious , size_t InLevel ) : Key ( InKey ), Data ( InusData ) ( Előző ){
Pár * Aktuális = Előző -> Következő [ 0 ];
Következő . AddLast ( Aktuális );
for ( méret_t Számláló = 1 ; Számláló <= InLevel ; Számláló ++ ){
Current = NextOnLevel ( Számláló );
Következő . AddLast ( Aktuális );
}
jelenlegi = előző ;
for ( méret_t Számláló = 0 ; Számláló <= InLevel ; Számláló ++ )
if ( Current = PreviousOnLevel ( Számláló ))
Aktuális -> Következő [ Számláló ] = ez ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
SkipList < KEY_T , DATA_T >:: Pair :: Pair ( size_t InLevel ) : Previous ( NULL ){
for ( méret_t Számláló = 0 ; Számláló <= InLevel ; Számláló ++ )
Következő . AddLast ( NULL );
}
sablon < típusnév KEY_T , típusnév DATA_T >
SkipList < KEY_T , DATA_T >:: Pair ::~ Pár (){
size_t OurLevel = Következő . Szám () -1 ;
Pár * Aktuális = ez ;
for ( méret_t Számláló = 0 ; Számláló <= Saját szint ; Számláló ++ )
if ( Current = PreviousOnLevel ( Számláló ))
Aktuális -> Következő [ Számláló ] = Következő [ Számláló ];
}
sablon < típusnév KEY_T , típusnév DATA_T >
SkipList < KEY_T , DATA_T >:: SkipList ( size_t InMaxLevel , double InSkipDivisor ) : MaxLevel ( InMaxLevel ), SkipDivisor ( InSkipDivisor ), Start ( MaxLevel ){}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Pair & SkipList < KEY_T , DATA_T >:: Pair :: operator = ( const SkipList < KEY_T , DATA_T >:: Pair & InPair ){
Kulcs = InPair . kulcs ;
Adatok = InPair . adatok ;
Előző = InPair . Előző ;
size_t max = Következő . számolni ();
for ( méret_t i ; i < max ; ++ i )
Következő . AddLast ( Következő [ i ]);
return * this ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
SkipList < KEY_T , DATA_T >::~ SkipList (){
tiszta ();
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Pair * SkipList < KEY_T , DATA_T >:: NewPrevious ( const KEY_T & InKey ){
Pár * Előző =& Start ;
Pár * Aktuális = Kezdés . Következő [ MaxLevel ];
for ( size_t Számláló = MaxLevel ; Számláló >= 0 ; Számláló -- ){
while ( Aktuális && InKey > Aktuális -> Kulcs ){
előző = aktuális ;
Aktuális = Aktuális -> Következő [ Számláló ];
}
ha ( ! Számláló )
szünet ;
jelenlegi = előző ;
};
visszatérés Előző ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
size_t SkipList < KEY_T , DATA_T >:: NewLevel (){
size_t Szint = 0 ;
while ( rand () % 1000 < SkipDivisor * 1000 && Level <= MaxLevel )
++ szint ;
returnLevel ; _
}
sablon < típusnév KEY_T , típusnév DATA_T >
void SkipList < KEY_T , DATA_T >:: Add ( const KEY_T & InKey , const DATA_T & InData ){
Pár * Előző = ÚjElőző ( InKey );
Pár * Következő = Előző -> Következő [ 0 ];
if ( Következő && Következő -> Kulcs == InKey )
throw String ( "Nem egyedi kulcs!" );
new Pair ( InKey , InData , Previous , New Level ());
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Pair * SkipList < KEY_T , DATA_T >:: PairByKey ( const KEY_T & InKey ){
Pár * Aktuális = ÚjElőző ( InKey );
if (( Aktuális = Aktuális -> Következő [ 0 ]) && Aktuális -> Kulcs == InKey )
visszatérés Áram ;
throw String ( "Nincs pár ehhez a kulcshoz!" );
}
sablon < típusnév KEY_T , típusnév DATA_T >
void SkipList < KEY_T , DATA_T >:: Törlés ( const KEY_T & InKey ){
ha ( Üres ())
throw String ( "Van egy üres lista!" );
törölje PairByKey ( InKey );
}
sablon < típusnév KEY_T , típusnév DATA_T >
DATA_T & SkipList < KEY_T , DATA_T >:: operátor []( const KEY_T & InKey ){
ha ( Üres ())
throw String ( "A lista üres!" );
return PairByKey ( InKey ) -> Data ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
size_t SkipList < KEY_T , DATA_T >:: Szám (){
ha ( Üres ())
return 0 ;
Pár * Következő = Kezdés . Következő [ 0 ];
méret_t n = 1 ;
while ( Következő = Következő -> Következő [ 0 ])
n ++ ;
return n ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
void SkipList < KEY_T , DATA_T >:: Törlés (){
Pár * Aktuális = Kezdés . Következő [ 1 ];
Pár * Előző = NULL ;
while ( Aktuális ){
Aktuális -> Előző = NULL ;
előző = aktuális ;
Aktuális = Aktuális -> Következő [ 0 ];
törlés Előző ;
}
for ( méret_t i = 0 ; i <= Kezdés . Következő . Számlálás ( ) -1 ; i ++ )
kezdeni . Következő [ i ] = NULL ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
SkipList < KEY_T , DATA_T >:: Iterator :: Iterator ( const SkipList < KEY_T , DATA_T >:: Iterator & InIterator ) : CurrentList ( InIterator . CurrentList ), CurrentPair ( InIterator . CurrentPair ){}
sablon < típusnév KEY_T , típusnév DATA_T >
SkipList < KEY_T , DATA_T >:: Iterator :: Iterator ( SkipList < KEY_T , DATA_T >& InSkipList ) : CurrentList ( & InSkipList ), CurrentPair ( InSkipList . Start . Next [ 0 ]){}
sablon < típusnév KEY_T , típusnév DATA_T >
SkipList < KEY_T , DATA_T >:: Iterator :: operator bool (){
return CurrentList && CurrentPair ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Iterator & SkipList < KEY_T , DATA_T >:: Iterator :: operator ++ (){
if ( CurrentPair )
CurrentPair = CurrentPair -> Next [ 0 ];
return * this ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Iterator & SkipList < KEY_T , DATA_T >:: Iterator :: operator -- (){
if ( CurrentPair && CurrentPair != CurrentList -> Start . Next [ 0 ])
CurrentPair = CurrentPair -> Previous ;
más
CurrentPair = NULL ;
return * this ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Iterator SkipList < KEY_T , DATA_T >:: Iterator :: operator ++ ( int ){
Iterator Old ( * this );
++* ez ;
return Old ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Iterator SkipList < KEY_T , DATA_T >:: Iterator :: operator -- ( int ){
Iterator Old ( * this );
--* ez ;
return Old ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Iterator & SkipList < KEY_T , DATA_T >:: Iterator :: operator = ( const SkipList < KEY_T , DATA_T >:: Iterator & InIterator ){
CurrentList = InIterator . CurrentList ;
CurrentPair = InIterator . Jelenlegi pár ;
return * this ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Iterator & SkipList < KEY_T , DATA_T >:: Iterator :: operator = ( const KEY_T & InKey ){
CurrentPair = CurrentList -> PairByKey ( InKey );
return * this ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
DATA_T & SkipList < KEY_T , DATA_T >:: Iterátor :: operátor * (){
ha ( !* ez )
throw String ( "Olvasás rossz iterátorból!" );
return CurrentPair -> Data ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
void SkipList < KEY_T , DATA_T >:: Iterator :: Törlés (){
ha ( !* ez )
throw String ( "Rossz iterátor adatainak törlése!" );
törölje CurrentPair ;
CurrentPair = NULL ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Iterator & SkipList < KEY_T , DATA_T >:: Iterator :: operator += ( size_t n ){
for ( méret_t Számláló = 0 ; Számláló < n && CurrentPair ; Számláló ++ )
CurrentPair = CurrentPair -> Next [ 0 ];
return * this ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Iterator & SkipList < KEY_T , DATA_T >:: Iterator :: operator -= ( size_t n ){
for ( méret_t Számláló = 0 ; Számláló < n && CurrentPair ; Számláló ++ )
CurrentPair = CurrentPair -> Previous ;
if ( CurrentPair ==& CurrentList -> Start )
return * this ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Iterator SkipList < KEY_T , DATA_T >:: GetBegin (){
return Iterator ( * this );
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Iterator SkipList < KEY_T , DATA_T >:: GetEnd (){
Iterátor ReturnValue ( * this );
ReturnValue += ReturnValue . CurrentList -> Count () -1 ;
return ReturnValue ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
típusnév SkipList < KEY_T , DATA_T >:: Iterator SkipList < KEY_T , DATA_T >:: Find ( const DATA_T & Unknown ){
Iterátor eredménye ( * this );
while ( Eredmény && ! ( * Eredmény == Ismeretlen ))
++ eredmény ;
vissza Eredmény ;
}
sablon < típusnév KEY_T , típusnév DATA_T >
bool SkipList < KEY_T , DATA_T >:: IsEmpty (){
típusnév Tömb < Pár *>:: Iterátor i ( Start . Következő );
míg ( i )
ha ( * i ++ )
return false ;
return true ;
}
Jegyzetek
- ↑ Pugh, William. Skip lists: a kiegyensúlyozott fák valószínűségi alternatívája // Communications of the ACM : Journal. - 1990. - június ( 33. évf. , 6. sz.). - P. 668-676 . doi : 10.1145 / 78973.78977 .
Irodalom
- William Pugh. Kihagyó listák: A kiegyensúlyozott fák valószínűségi alternatívája / Algoritmusokról és adatstruktúrákról szóló workshop. Springer Berlin Heidelberg, 1989; Az ACM CACM honlapja archívumának közleményei, 33. évfolyam, 6. szám, 1990. június, 668-676 oldal doi:10.1145/78973.78977 - eredeti mű
- Manning K., Raghavan P., Schütze H. Bevezetés az információkeresésbe. - Williams , 2011. - 512 p. - ISBN 978-5-8459-1623-5 .
Linkek