320 likes | 429 Views
Datastructuren Skiplists. Skiplists. Vrij eenvoudige datastructuur “Makkelijker” dan gebalanceerde bomen Kunnen hetzelfde als gebalanceerde bomen Makkelijkste versie: “verwachtte tijd even snel als gebalanceerde bomen” Hier: een “informele” inleiding Hoe werken ze?
E N D
Skiplists • Vrij eenvoudige datastructuur • “Makkelijker” dan gebalanceerde bomen • Kunnen hetzelfde als gebalanceerde bomen • Makkelijkste versie: “verwachtte tijd even snel als gebalanceerde bomen” • Hier: een “informele” inleiding • Hoe werken ze? • Een enkel bewijsje over de tijdgrens
Operaties: • Zoek-Element • Minimum • Maximum • Volgende • Vorige • Insert • Delete • Kunnen allemaal goed gedaan worden met skiplists • Maar ook dynamische order statistics (en andere queries)
Eerst maar eens:“een gewone dubbelgelinkte lijst” 2 3 5 7 11 13 Alles in O(n) in het slechtste geval…
Toevoegen van “snelweg” 2 7 13 23 2 3 5 7 11 13 17 23
Zoeken met de snelweg • “Loop eerst over de snelweg, totdat je te ver zou gaan, en ga dan door de gewone lijst”
Hoeveel stappen maximaal? • Aantal stappen in laag 1: maximaal tot eind • Aantal stappen in laag 2: maximaal stuk tussen twee afritten • Slechtste geval zit tussen: • Maximum van geval 1 en geval 2 • Twee keer ‘t maximum van geval 1 en geval 2 • Als je nu de afritten op afstand n1/2zet dan krijg je ‘t best mogelijke (op factor 2 na)
Meer dan twee lagen • Algoritme om x te zoeken: • Begin links in de bovenste laag • Herhaal tot gevonden of helemaal rechts-onderaan: • Zitten we in x? Ja: klaar • Is de volgende in mijn laag < x? • Ja: ga een stap verder in deze laag • Nee: ga naar dezelfde key in de laag eronder (dit geval nemen we ook als er geen volgende in mijn laag is) • Checks op einde nog nodig
19 7 2 19 7 2 13 2 3 7 13 19 2 3 5 7 11 13 17 19
Implementatie: stootblokken aan eind • Twee rijen objecten: een “kleiner” dan alles, en een “groter” dan alles • Kan handig zijn in implementatie • Variaties mogelijk
R 19 7 2 L R 19 7 2 13 L R 2 L 3 7 13 19 2 L 3 R 5 7 11 13 17 19 Neem aan dat L een key bevat kleiner dan elke keyen R een key bevat groter dan elke key
Zoeken • Search(SkipList S, key x) • y = eersteElementBovensteLaag(S); • While true do • If key(y)==x then return y • If key(next(y)) £ x then y = next(y) • Else if (y zit in onderste laag) then return false • Else y = down(y) • Variatie: als je element gevonden hebt, ga dan omlaag totdat je op de onderste laag zit: alleen daar sateliet-data (of neem pointer overal naar “echte” object)
Hoe grote stappen per laag? • Als je k lagen hebt, en elke laag r stappen doet voordat je naar de volgende laag gaat: • Maximaal iets van kr stappen • Totaal kan je zo ongeveer rkknopen hebben op de onderste laag • n £rk • Als je k = log n neemt en r=2 heb je een “verstopte gebalanceerde binaire boom”: ‘t kost ook O(log n) tijd
Probabilistische aanpak • Vgl. Quicksort / Randomized Quicksort • Door gebruik te maken van kansen krijg je een datastructuur met algoritmen die • In het slechtste geval veel tijd gebruiken • Maar een goede verwachte gemiddelde tijd hebben • Hier: • In O-notatie net zo goed als rood-zwart-bomen (maar alleen verwacht en niet langer slechtste geval) • Maar veel eenvoudiger
Weglaten • Deletions zijn heel makkelijk: • Zoek het element • Laat het element weg in alle lagen waar hij voorkomt • Zet alle pointers goed: • Op elke laag worden zijn linker en rechterbuur aan elkaar gelinkt • Tijd: O(zoeken van element + aantal lagen waar element in voorkomt)
Hoe in te voegen? • Invoegen: • In elk geval invoegen in de onderste laag • Tijd: zoeken + O(aantal lagen waar ie in voorkomt) • Maar: in hoeveel lagen gaan we eigenlijk het element invoegen???
Kansen • Stop met bepaalde kans (bijvoorbeeld ½) de knoop in de laag erboven • Herhaal totdat “munt” • Gooi een munt op. • Is ‘t kop, stop dan de knoop in nog een laagje meer • “Gemiddeld” heb je O(log n) lagen • “Gemiddeld” zitten er steeds twee knopen tussen elke afrit
Voorganger op laag erboven predecessorUp(x) y = previous(x); While (up(y)==NIL and y!= L) do y = previous(x); Return up(y) Subroutine 7 2 2 3 7
Invoegen • Vind plek op onderste laag om x in te voegen • Voeg x in op onderste laag (pointers tussen x en voorganger en opvolger • While (randomBit() == true) do • Maak een object z met key(z) gelijk aan key(x) • y = predecessorUp(x); • If (y != L) then • Voeg z in tussen y en next(y) • Down(z) = x • Up(x)=z • Else • Ongeveer net zo (implementatiedetails) Kans 1/2
Voorbeeld • Op bord
Opmerkingen • Slechtste geval van algoritme: oneindig! • Gemiddeld: prima • Je kan ook bewijzen dat de kans dat “alles” in O(log n) gaat heel groot is (naar 1 gaat voor n naar oneindig) • “Variatie”: in plaats van kans ½ misschien 1/3 of … • Constantes kunnen verschillen…
Een paar bewijzen • Aanname: kans steeds ½ van een laag meer • Lemma: • Voor elke key x in de datastructuur: het verwachte aantal lagen dat x bevat is 2 • Bewijs • … • Gevolg: de verwachte hoeveel geheugen voor een skiplist is O(n)
Tijd van predecessorUp • Lemma • De verwachte tijd van predecessorUp is O(1) • Elke knoop heeft met kans ½ : • up(x)!= NIL
Grote kans op O(log n) lagen Stelling • De kans dat er meer dan c log n lagen zijn is hooguit 1/nc-1. • Voor 1 element x is de kans dat x in meer dan c log n lagen zit • (1/2)c log n = ((1/2)log n)c = (1/n)c = 1/ nc • Want je moet c log n keer beslissen om een laag meer te nemen • Kans dat er minstens 1 element met c log n lagen < n * kans voor 1 element dat hij c log n lagen heeft = n * 1/nc = 1/nc-1
Verwachte aantal lagen is O(log n) • Afschatting: • Som over alle c (van 1 tot oneindig) van • c log n * kans dat het aantal lagen ligt tussen(c-1)log n en c log n • £ som over alle c (van 1 tot oneindig) van • log n * kans dat aantal lagen hooguit c log n is • £ som over alle c (van 1 tot oneindig) van • log n * 1/nc-1 • = log n (1 + 1/n + 1/n2 + … ) = O(log n)
Tijd van operaties • Verwachte tijd van zoeken, invoegen, weglaten was O(aantal lagen) • Dus is O(log n)
Andere operaties kunnen ook • Bijvoorbeeld Dynamische Order Statistics • Houd bij elke knoop x bij het aantal elementen dat zit • In de onderste laag • Tussen x en de opvolger van x in deze laag
Totaal = 0; y = eerste element bovenste laag; While (y != x) do If key(next(y)) £ x then Totaal += aantal(y) Y = next(y) If key(y) == x then Totaal ++ (voor x zelf) Return x Else y = down(y); Hier neem ik even aan dat we zeker weten dat x een bestaande key is Hoeveel elementen zijn er £x Query: rang
191 74 L0 23 R0 191 72 L0 23 132 R0 L0 21 R0 32 72 132 191 L0 21 31 R0 51 71 111 131 171 191
Bijhouden van totaal • Weglaten: • “Omhooglopen” (eerst met up, en daarna met variant op predecessorUp) en steeds 1 aftrekken van totoaal • O(hoogte) tijd: verwacht O(log n) • Invoegen: • Zoek element en tel overal op zoekpad 1 op… • Als element op hogere lagen wordt ingevoegd: • Bereken zijn getal: loop op laag eronder tot je de volgende knoop met up(x)!= NIL tegenkomt en tel hun getallen op • Verminder zijn voorganger met dat getal
Skip lists • Worden soms gebruikt in plaats van binaire bomen • Wat makkelijker • Operaties als minimum, maximum, successor, predecessor: snel • Successor: O(?) • In eerste instantie O(log n), maar de tweede successor O(1) • Gemiddeld: O(1)
Tenslotte • Volgende keer vragenuur en misschien nog wat “niet-tentamenstof”