Programkonstruktion och Datastrukturer Repetitionskurs, sommaren 2011 Datastrukturer (hash-tabeller och heapar) Elias Castegren elias.castegren.7381@student.uu.se
Arrayer igen En array är en linjär datastruktur med konstant tidskomplexitet för åtkomst i hela strukturen.... 0 1 2 3 n-3 n-2 n-1 Nycklarna måste vara i intervallet 0..(n-1). Kräver att man allokerar minne för n celler. Leder till onödig minnesanvändning om man inte använder alla nycklar: Oanvänt utrymme! 0 0 v1 0... 0 v2 v3 0 1 2 3 997 998 999 Vad vi ville lagra
Arrayer igen Vi skulle vilja kunna använda vilka nycklar som helst, utan att behöva anpassa storleken på arrayen till nycklarnas värden... Idé: Om vi kunde hitta på en funktion som översatte ett godtyckligt heltal till ett heltal i intervallet 0..(n-1) så skulle man kunna låtsas att man har en array som tillåter hur stora nycklar som helst......och därifrån är steget inte så långt till att använda nycklar som inte är heltal, bara vi kan översätta dem till ett giltigt index i arrayen!
Hash-funktioner En hash-funktion är en (matematisk) funktion från en godtycklig mängd till en mängd av heltal (i vårt fall ett index i en array) (alltså typen på ) kan i teorin vara vilken mängd som helst, t.ex. Heltal Strängar Konstruerade datatyper...
Hash-funktioner Två väldigt enkla hash-funktioner: där är ascii-värdet av det :te tecknet i I allmänhet någon omvandling från till ett heltal, modulo
Hash-tabeller En hash-tabell är en array med celler som indexeras med värden ur en mängd med hjälp av en hash-funktion Lagra värde på plats...... 0 1 2 h(k) n-2 n-1
Hash-tabeller En Hash-tabell fungerar alltså som en array som vi kan indexera med vilka värden vi vill! Men... Antalet möjliga nycklar ( ) är i allmänhet mycket större än antalet faktiska celler ( ) i hash-tabellens inneboende array Vad händer om, alltså om två olika nycklar hashas till samma cell i arrayen? Vi behöver något sätt att hantera kollisioner!
Chaining Chaining innebär att vi låter varje cell i hashtabellen innehålla en lista. Om en nyckel hashas till en upptagen cell lägger man bara in det nya värdet i cellens lista....... 0 1 2 h(k1) = h(k2) n-2 n-1
Chaining Löser kollisionsproblemet genom att tillåta ett godtyckligt antal värden i samma cell. En hashtabell med chaining har inte längre konstant minnesanvändning. En lista i en cell kan ju bli hur lång som helst. Med en välkonstruerad hash-funktion (se senare slides) som undviker nyckel-krockar kommer dock ingen lista att växa mycket snabbare än någon annan.
Öppen adressering En annan lösning skulle vara att på ett bestämt sätt leta efter en ledig cell i listan när hashfunktionen råkar föreslå en upptagen cell. Detta kallas öppen adressering. Den nya hash-funktionen blir där är en vanlig hash-funktion och anger vilken gång i ordningen man söker en ledig cell. Sekvensen måste innehålla alla värden mellan 0 och n-1, vilket betyder att man måste leta efter en ledig plats bland alla celler i tabellen.
Öppen adressering kallas tabellens probfunktion Linjär probning, h(k,0) h(k,1) h(k,2) h(k,3) h(k,4)... v1 v2 v3 v4... Kvadratisk probning, h(k,0) h(k,1) h(k,2)... v1 v2 v3 v4...
Öppen adressering Löser kollisioner genom att hitta en ledig cell någon annanstans i tabellen. Håller minnesanvändningen konstant. I en välfylld tabell kommer det krävas mycket probande, även om det går att undvika till viss del med en smart probfunktion. När det blir trångt i hash-tabellen kan man rehasha, det vill säga skapa en ny, större hash-tabell och sätta in alla gamla element i den, för att på så sätt hålla nere antalet kollisioner.
Krav på hash-funktionen Eftersom array-operationerna är snabba så bör inte hash-funktionen dra ner prestandan. Hash-funktionen måste vara deterministisk, alltså alltid ge samma värde för två identiska nycklar. För att undvika kollisioner (i hash-tabeller med chaining eller öppen adressering) vill man ha en hash-funktion där varje index 0..(n-1) är lika sannolikt, oavsett om nycklarna i inte är lika sannolika.
Prioritetsköer En prioritetskö är en datastruktur där varje nods nyckel är dess prioritet. Syftet med en prioritetskö är att snabbt (logaritmisk tidskomplexitet eller snabbare) kunna lägga till nya element och snabbt kunna plocka ut (och ta bort) det element som har högst prioritet. Priset man betalar är att man tillåter sökning att ha linjär tidskomplexitet. En viss typ av prioritetsköer kallas heapar.
Heapar En heap är en prioritetskö med (som mest) logaritmisk tidskomplexitet för att sätta in nya element och för att hitta (och ta bort) elementet med högst prioritet. Gemensamt för alla heapar är den så kallade heap-egenskapen: Om B är en undernod till A så är Bs nyckel mindre eller lika med As nyckel. Nodernas prioritet sjunker när man går längre ner i datastrukturen. En sådan heap är den binomiala heapen.
Binomiala träd För att kunna konstruera en binomial heap behöver vi först defi niera binomiala träd: Rekursiv defi nition: Ett binomialt träd av rang 0 består av en nod. Ett binomialt träd av rang k består av två binomiala träd av rang (k-1). Rang: 0 1 2 3
Binomiala träd Ett binomialt träd av rang har: nivåer noder totalt noder på nivå * binomiala träd av rang 0..(k-1) som barn till rotnoden *(Binomialkoeffi cienten har gett strukturen dess namn)
Binomial heap En binomial heap är en lista med binomiala träd som alla uppfyller heap-egenskapen (en nods nyckel överstiger inte dess förälders nyckel). Listan är sorterad i strikt stigande rangordning (två träd får inte ha samma rang). En binomial max-heap (större nyckel => högre prioritet): 5 4 9 3 7 8 4 5 1 6 8
Binomial heap Heap-egenskapen gör att noden med högst prioritet i ett binomialträd alltid är roten, och därmed att man kan hitta noden som har heapens högsta prioritet genom att bara undersöka varje träds rotnod. En binomial heap med k binomiala träd har som mest noder eller annorlunda uttryckt: En binomial heap med n noder innehåller som mest lg(n+1) binomiala träd. Alltså kan man hitta noden med högst prioritet med tidskomplexiteten O(lg(n)).
Binomial heap För att lägga in en ny nod i heapen lägger man in ett nytt binomialt träd av rang 0 innehållande den nya noden. Om två träd har samma rang r slår man ihop dessa till ett nytt träd av rang r+1 (och bevarar heap-egenskapen).
Binomial heap 6 5 4 3 7 8 9 4 Lägg in noden 6 5 6 8 1 6 4 9 5 3 7 8 4 Slå ihop och 6 5 5 6 8 1 4 6 5 7 8 9 4 Slå ihop 6 4 och 5 3 3 5 6 8 1
Binomial heap När man tar bort det minsta elementet i heapen (en rotnod ur ett träd med rang r) får man r binomiala träd av rang 0 till (r-1) över. Dessa läggs in som nya träd på rätt plats i heapen: 6 9 4 5 7 8 4 3 5 6 8 1 4 8 7 8 6 5 6 4 3 5 1
Binomial heap När man lägger in ett nytt träd måste man som mest slå ihop alla träd, alltså lg(n+1) stycken (där n är antalet noder i heapen). Tidskomplexiteten för att sätta in ett nytt element blir alltså O(lg(n)). Att hitta elementet med högst prioritet har tidskomplexiteten O(lg(n)), så tillsammans med sammanslagningarna blir den totala tidskomplexiteten för att hitta och ta bort noden med högst prioritet också O(lg(n)).