Datastrukturer föreläsning 6 Maps 1
Avbildningar och lexika Maps 2
Vad är ett lexikon? Namn Telefonnummer Peter 031-405937 Peter 0736-341482 Paul 031-405937 Paul 0737-305459 Hannah 031-405937 Hannah 0730-732100 3
Vad är en avbildning och vad är ett lexikon? Matematisk definition: (G&T) Ett lexikon är en ändlig mängd av par av nycklar och värden, dvs en relation mellan nycklar och värden: {k 1 v 1,k 2 v 2,...,k n v n } = {(k 1,v 1 ),(k 2,v 2 ),...,(k n,v n )} Lexikonet är en avbildning om den beskriver en ändlig partiell funktion från nycklar till värden, f.v.s. det finns högst ett värde för varje nyckel. När man talar om relationsdatabaser säger man att man har ett funktionellt beroende Maps 4
Effektiva implementeringar av lexika Hashtabeller: HashMap i Java Skipplistor (, splay Sökträd (binära, AVL, rödsvarta, Dessa implementeringsmetoder kan användas för att implementera mängder också: HashSet En relation är en mängd av par! I bilder visar vi ofta mängder I stället för lexika. Maps 5
Hashtabeller 0 1 2 3 025-612-0001 4 451-229-0004 981-101-0004 Maps 6
Lexika och avbildningar som fält - hashtabeller Avbildningar (och lexika) kan lagras i fält om vi har en funktion h som översätter nycklarna till tal i ett intervall [0,...,N-1] och vi lagrar nyckel-värde paret k -> v på position h(k) i fältet. En avbildning lagrad på sådant sätt kallas en hashtabell! N är storleken på hashtabellen h kallas hashfunktion Om h är injektiv kallas hashfunktionen perfekt Maps 7
Hashfunktioner och hashtabeller Exempel på hashfunktion om nycklarna är heltal: h(k) = k mod N Talet h(k) kallas hashvärdet för nyckeln k Om h(k 1 ) = h(k 2 ) för två olika nycklar k 1, k 2 så har vi en kollision Vi måste lagra par av nycklar och värden k v i cellen med index h(k) Man bör använda en hashfunktion som sprider nycklarna väl Maps 8
Kollisioner vad gör man? Maps 9
Kollisioner vad gör man? Använder hinkar ( hashing in buckets, chaining ) Cellen med index i innehåller ett litet lexikon med alla nycklar med hashvärde i. Vi kallar detta en hink. Den implementeras ofta som en osorterad lista. Öppen adressering ( open addressing ): Lagra i annan ledig cell (t ex nästa lediga, men det finns andra sätt att välja den nya lediga cellen) Maps 10
Open addressing: linear probing Handskas med kollisioner genom att använda nästa lediga cell (cirkulärt). Problem: kolliderande element klumpar ofta ihop sig och orsakar fler kollisioner. Exampel: h(x) = x mod 13 Sätt in nycklarna 18, 41, 22, 44, 59, 32, 31, 73 i ordning: 0 1 2 3 4 5 6 7 8 9 10 11 12 41 18 44 59 32 22 31 73 0 1 2 3 4 5 6 7 8 9 10 11 12 Maps 11
Sökning med linear probing Sök efter nyckel k! Leta först i cell h(k)! Om den inte finns där, leta i cell h(k) + 1, h(k) + 2, h(k) + 3, (cirkulärt) tills man antingen hittar k eller tills man hittar en tom cell I värsta fall får man alltså leta genom hela hashtabellen Vad händer om vi tar bort element? Ta t ex bort 18 och sök sedan efter 44 på föregående bild! Maps 12
Linear probing: gravstenar När man tar bort ett element ersätter man det med ett särskilt element AVAILABLE (kallas också gravsten) som markerar att cellen är fri, men har varit använf. När man sätter in ett element kan man använda både tomma celler eller celler markerade AVAILABLE När man söker efter ett element får man inte avsluta sökningen när man kommer till AVAILABLE Se G&T sid 384-386 för en Java-implementering av hashtabell med linear probing! Maps 13
Alternativ till linear probing Quadratic probing. Försök med cellerna h(k) mod N, h(k) + 1 mod N, h(k) + 4 mod N, h(k) + 9 mod N, Double hashing Försök med cellerna h(k) mod N h(k) + h (k) mod N, h(k) + 2h (k) mod N, h(k) + 3h (k) mod N, h kallas sekundär hashfunktion Maps 14
Omhashning Lexikonoperationerna blir ineffektiva när hashtabellen börjar bli full Belastningsfaktorn α = n/n är ett mått på hur full hashtabellen är. När den är stor α 0,5 för öppen adressering α 0,9 om man använder hinkar skapar man en ny tabell omhashning ( rehashing ) med ny hashfunktion! Maps 15
Vilket är bäst hinkar eller öppen adressering? enklare att använda hinkar öppen adressering sparar utrymme öppen adressering mer känslig för hög belastningsfaktor både experiment och teoretiska studier har visat att tidskomplexiteten oftast är lika bra eller bättre med hinkar öppen adressering är bra om man kan lagra informationen direkt i cellen Maps 16
Hashfunktioner Man brukar konstruera en hashfunktion h genom att sätta samman två funktioner: h = h 2 o h 1 Hashkodningen: h 1 : keys integers Kompressionsfunktionen: h 2 : integers [0, N 1] Maps 17
Hashkodningar Många olika möjligheter tänk på att få god spridning! Några enkla metoder: Typkonvertering till heltal Polynomiella hashkoder Använda minnesadressen? Maps 18
Hashkodning av tal byte, short, char: typkonvertering ( cast ) till heltal long: dela t ex upp de 64 bitarna i 2 x 32 bitar och summera: (int) (i > > 32) + (int) i Float: tolka t ex de 32 bitarna som heltal: Float.floatToIntBits(x) Maps 19
Hashkodning av strängar En sträng är en lista av tecken. Varför inte bara summera hashkoderna för tecknen? Många kollisioner! T ex temp01 och temp10 får samma hashkod och stop, tops, pots, spot får också samma hashkof. Maps 20
Polynomiella hashkoder Koda strängen c 0 c 1 c n Typkonvertera tecknen till heltal vi får i 0 i 1 i n Beräkna polynomet i 0 +i 1 a+ + i n a n för lämpligt a (t ex 33, 37, 39, 41) Maps 21
Hashkoder för objekt Javas klass Object har en metod hashcode() I många Javaimplementeringar ger detta en heltalstolkning av objektets minnesadress! Detta är ej så bra, likadana objekt lagrade på olika ställen får olika hashkoder! Det är bättre att implementera egna hashkoder! Maps 22
Komprimering Division: h 2 (y) = y mod N Hashtabellens storlek N vanligen ett primtal. Multiplicera, addera och dividera (MAD): h 2 (y) = (ay + b) mod N a och b är slumpvis valda naturliga tal så att a mod N 0 N primtal Maps 23
Tidskomplexitet Om vi har perfekt hashfunktion (inga kollisioner) är sökning, insättning och borttagning O(1) I värsta fall är dessa operationer O(n), utom insättning för hashning med hinkar som är O(1). Varför? Belastningsfaktorn α = n/n påverkar effektiviteten hos operationerna Maps 24
Hashtabeller: fördelar och nackdelar Fördelar: - Snabb: O(1) i praktiken vid låg belastningsfaktor - Särskilt bra för stort lexikon - Fördel om max storlek känd i förväg Nackdelar: - Hashfunktionen kan ta signifikant tid att beräkna - Risk för dålig hashfunktion och kollisioner (säkerhetsattack) - Svårt att räkna upp elementen Maps 25
Skipplistor S 3 S 2 S 1 S 0 15 15 23 10 15 23 36 Maps 26
Vad är en skipplista? en lista av sorterade listor S 0, S 1,, S h. Varje S i innehåller och (särskilda största och minsta element) Varje lista är en dellista av den efterföljande, S 0 S 1 S h Listan S h innehåller bara och. S h-1 innehåller minst ett riktigt element! S 3 S 2 31 S 1 23 31 34 64 S 0 12 23 26 31 34 44 56 64 78 Maps 27
Implementering av skipplistor (enligt G&T) Använd länkad struktur! Varje nod innehåller: element länkar till noderna efter och före länkar till noderna under och över (som innehåller samma element!) Obs. Figuren på föregående bild visar bara länkar till noderna före och efter. Det ska även finnas länkar vertikalt. Maps 28
Sökning i skipplista Sök efter nyckel k! Börja med i översta listan Antag att vi har kommit till en nod med nyckeln x k = x: vi har hittat nyckeln! k > x: gå åt höger! k < x: gå tillbaka och nedåt! Vi har gått för långt! Om vi försöker gå nedåt från understa listan finns inte nyckeln. Exempel: leta efter 78! Leta efter 77! S 3 S 2 31 S 1 23 31 34 64 S 0 12 23 26 31 34 44 56 64 78 Maps 29
Att skapa en skipplista Vi skapar en skiplista som representerar den sorterade listan 12, 23, 26, 31, 34, 44, 56, 64, 78. Denna lista inkl -, blir den understa listan S 0. Varje element i S i har 50% chans att bli medlem i S i+1. - vi gör slumpmässiga val. S 3 S 2 31 S 1 23 31 34 64 S 0 12 23 26 31 34 44 56 64 78 Maps 30
Algoritmer med slumpval Innehåller instruktioner b random() Javainstruktion: Math.random() ger slumpgenererade flyttal mellan 0 och 1 Exekveringstiden beror på slumpen och är ofta dålig i värsta fall, men sannolikheten för värsta fallet brukar vara låg. När man skapar skipplistor och gör insättning i skipplistor använder man slumpmässiga val. Maps 31
Insättning i skipplista Samma princip som för att skapa en skiplista: vi gör slumpmässiga val (kastar mynt) för att bestämma hur många extra kopior av elementet vi ska göra. Vi gör först en sökning (som innan) för att hitta rätt plats för elementet i den understa listan. Sedan kastar vi ett mynt för att avgöra om elementet ska vara med i nästa lista. Om så är fallet sätter vi in det ovanför, osv. Obs att vi eventuellt kommer att behöva öka antalet nivåer i skipplistan. Maps 32
Insättning i skipplista Exempel: sätt in nyckeln 15, med 2 extra kopior. S 2 15 S 3 S 2 S 1 23 S 1 15 23 S 0 10 23 36 S 0 10 15 23 36 Maps 33
Insättning, ett exempel till Sätt in nyckeln 27 med 1 extra kopia. S 3 S 2 15 S 1 S 0 15 23 10 15 23 36 S 3 S 2 15 S 1 15 23 27 S 0 10 15 23 27 36 Maps 34
Borttagning ur skipplista Ta bort elementet k: Vi söker efter k som i sökalgoritmen och hittar noderna p 0, p 1,, p i som ligger just före noden med k, i listorna S 0, S 1,, S i Vi tar bort noderna k från S 0, S 1,, S i Vi tar bort alla utom en av de listor som bara innehåller och Exempel: ta bort 34! S 3 S 2 p 2 34 S 2 S 1 p 1 23 34 S 1 23 S 0 p 0 12 23 34 45 S 0 12 23 45 Maps 35
Sammanfattning En skipplista byggs upp med slumpmässiga val. En skipplista som representerar en mängd med n element använder O(n) utrymme i medeltal (varför?) Medeltiden för sökning, insättning och borttagning är O(log n) Sannolikheten för god tidskomplexitet är hög (avancerad sannolikhetsanalys behövs dock för att visa detta, se kurs i matematisk statistik) En effektiv datastruktur som är besläktad med sökträd - mer om dessa senare! Maps 36
Implementeringar av abstrakta datatyper Specifikationer och korrekthet Maps 37
Axiom för avbildningar i Haskell get :: Eq k => k -> Map k v -> Maybe v put :: Eq k => k -> v -> Map k v -> Map k v get k (put k v f) == Just v k!= k ==> get k (put k v f) == get k f remove :: Eq k => k -> Map k v -> Map k v get k (remove k f) == Nothing k!= k ==> get k (remove Maps k f) == get k 38 f
Java-gränssnitt och axiom public interface Map<K,V> { } V get(k k); void put(k k, V v); void remove(k k); f.put(k,v).get(k) = v f.put(k,v).get(k) = f.get(k) if k!= k 39
Enhetstestning Skriv ner testfall för den abstrakta datatypen innan den implementeras ( black-box testing ) Maps 40
Hur övertygar man sig om att en implementering är korrekt? Testa varje enhet! Testa hela programmet! Granska koden och försök att bevisa att den är korrekt! Se kursen Testing, Debugging, and Verification (lp 2) Maps 41