Innehåll • Mängd • Lexikon • Hashtabell Föreläsning 7 Mängd, lexikon och hashtabell 175 Mängd 176 Specifikation • Modell: En påse, men den är inte riktigt bra eftersom man tex kan ha mängder med gemensamma element. • Organisation: – En oordnad samling av element som är av samma typ. – Grundmängden behöver inte vara ändlig (heltal) men dataobjekten är ändliga. – Kan inte innehålla två likadana värden. – En mängd kan inte innehålla mängder. 177 Specifikation abstract datatype Set(val) Empty()->Set(val) Single(v:val)->Set(val) Insert(v:val,s:Set(val))->Set(val) Union(s:Set(val),t:Set(val))->Set(val) Intersection(s:Set(val),t:Set(val))->Set(val) Difference(s:Set(val),t:Set(val))->Set(val) Isempty(s:Set(val))->Bool Member-of(v:val,s:Set(val))->Bool Choose(s:Set(val))->val Remove(v:val,s:Set(val))->Set(val) Equal(s:Set(val),t:Set(val))->Bool Subset(s:Set(val),t:Set(val))->Bool 178 Konstruktion av mängd • Alla metoder som finns i boken behövs inte. – Empty, IsEmpty, Insert, Choose och Remove skulle räcka. – Man har tagit med de vanliga matematiska mängdoperationerna. • Vill man kunna ha mängder av mängder måste man skapa en ny datatyp generaliserad mängd på samma sätt som generaliserad lista. 179 • Nästan oavsett konstruktion måste man kunna hantera det faktum att det inte får finnas dubbletter i en mängd. • Mängd som lista har två alternativ: – Se till att listan inte har dubbletter (krav på Set-Insert och Union) – Låt listan ha dubbletter (krav på Equal, Remove, Intersection, Difference) 180 Konstruktion av mängd som lista • Komplexitet: – Metoder som kräver sökningar i listan O(n) – Binära mängdoperationerna kräver (pga nästlad iteration) O (m*n) – Listan kan konstrueras på olika sätt. Sorterad lista tex är effektivare för de binära mängdoperationerna. + Grundmängden kan vara mycket stor + Delmängderna kan både vara mycket små och mycket stora utan utrymmesförluster (om man implementerar listan på rätt sätt) - Flera operationer blir slöa jämfört med andra val. Konstruktion av mängd som bitvektor • En bitvektor är en vektor med elementvärden av typen Bit = {0,1} – Ibland tolkas 0 och 1 som falskt resp. sant, dvs Bit identifieras som datatypen Boolean • Grundmängden måste ha en diskret linjär ordning av elementen. (Man kan numrera dem.) • Bit k i bitvektorn motsvarar det k:te elementet i grundmängden. Biten är 1 om elementet ingår i mängden. 181 182 Konstruktion av mängd som bitvektor • Om bitvektorn finns implementerad som ett eller flera ord i datorns primärminne kan man utnyttja maskinoperationer. – Dvs man gör operationen samtidigt på alla element i vektorn. Detta gör många metoder effektiva. + Relativt effektiv i allmänhet, mycket effektiv som ovan. - Grundmängden måste vara ändlig och i praktiken rätt så liten. Reserverar minne proportionellt mot grundmängdens storlek. - Om man har många små mängder i en tillämpning blir det dålig effektivitet i minnesutnyttjandet. Mängd konstruerad som boolesk funktion • En mängd kan representeras av sin karakteristiska funktion !s(x): ! ! ! !s(x) = 1 om x "S ! 0 annars 183 Lexikon 184 Lexikon • En förenklad mängd där man tagit bort union, intersection, difference, subset och equal. • Kvar är det man behöver för att kunna hålla ordning på en samling objekt: – empty – isempty(s) – insert(v, s) – remove(v, s) – memberOf(v, s) 185 • Ett lexikon är som en tabell utan tabellvärden. • Saknar alltså metoder för att kombinera lexikon till nya lexikon. • Lexikon är en solitär datatyp. Man kan bara göra modifieringar av isolerade dataobjekt. • En icke-solitär datatyp har stöd för att kombinera/bearbeta två eller flera dataobjekt. 186 Konstruktion av lexikon Hashtabell • Vi kan få betydligt effektivare konstruktioner när antalet och typen av metoder är begränsade. • Vi kommer titta på två konstruktioner av lexikon: – Lexikon som Hashtabell – Lexikon som Binärt sökträd (nästa föreläsning) • Eftersom Lexikon är så likt Tabell kan dessa konstruktioner med små ändringar bli effektiva konstruktioner av en tabell. • Problemen med att implementera en mängd/lexikon som en bitvektor är att grundmängden inte får bli för stor och att många små mängder slösar med minnet. – Vore bra att kunna komprimera bitvektorerna. • Vi behöver en hashfunktion som avbildar den större indextypen A till en mindre indextyp B. • Man kan också se det som att grundmängden är nycklarna i en tabell. – Eller att man vill omvandla nycklar i en tabell till siffror med hjälp av hashfunktionen. 187 188 Sluten hashing Hashfunktion • Funktionen f(x) avbildar en stor mängd nycklar, A, på en liten mängd tal, B. • Kollisioner är oundvikliga och en bra hashfunktion kännetecknas av: • Låt B vara en cirkulär vektor, dvs efter det sista elementet följer det första. • Linjär teknik för kollisioner – Om ett element kolliderar (platsen redan upptagen) sätter man in det på den första lediga platsen efter den upptagna. – Litet förväntat antal kollisioner • Ger upphov till klustring i tabellen. – Sprider elementen jämt över mängden. – Sökning efter ett element börjar på den plats dess hashvärde anger och fortsätter eventuellt framåt. Om det inte påträffats före nästa lediga plats så finns det inte i tabellen. – Om vi vid borttagning i tabellen bara lämnar platsen tom blir det fel vid sökning. – Bör påverkas av alla delar av nyckeln • Kollisioner kan hanteras med – Sluten hashing – Öppen hashing • Sätt in en ”borttagen” markör i tabellen. 189 Exempel: Sluten hashing, linjär teknik • Grundmängden 1, 2, …, 125 ska avbildas på värdena 0, 1, ..., 6. • Mängd = {1, 8, 27, 64, 125} Använd hashfunktionen f(x) = x % 7. 0 1 2 3 4 0 1 h(1) = 1 1 2 8 3 4 0 125 1 1 1 1 1 1 2 8 h(27) 2 = 3 6 4 8 h(64) 2 = 3 1 4 64 0 1 h(8) = 1 0 3 4 5 5 5 6 6 6 64 6 h(125) = 6 27 6 Sluten hashing, linjär teknik • Värstafallskomplexiteten för samtliga operationer är O(n) där n är antalet element som finns insatta i tabellen. – Är dock ytterst osannolikt. Alla element måste ligga i en följd. • Under förutsättning att tabellen inte fylls mer än till en viss del får man i medeltal O (1) för operationerna. 5 5 27 8 190 27 191 192 Hashtabeller - fyllnadsgrad • En hashtabells fyllnadsgrad (#) definieras som kvoten mellan antalet insatta element och antalet platser i tabellen. – En tom tabell har # = 0 och en full # = 1. • Man vet att Sluten hashing, linjär teknik • När fyllnadsgraden blir hög (>75%) blir operationerna för den linjära tekniken långsam. – Medelantalet platser som måste prövas vid en insättning och misslyckad sökning är uppåt begränsad av (1+1/(1- #)2 ) /2, (# = 0.5 ger 2.5) – Medelantalet platser för en lyckad sökning är uppåt begränsad av (1+1/(1- #))/2, (# = 0.5 ger 1.5) 193 Sluten hashing, kvadratisk teknik 194 Exempel: Sluten hashing, kvadratisk teknik • Sätt in talen 89, 18, 49, 58, 9 i en tabell med 10 platser. h(x) = x % 10 • Kvadratisk teknik kan användas istället: – Vid kollision tas först nästa element, sedan det fyra platser fram sedan nio fram etc, dvs H, H +1, H+4, …, H+i2, … där H är elementets hashvärde. 0 49 1 2 3 4 58 9 5 6 7 8 9 18 89 89 % 10 = 9 18 % 10 = 8 49 % 10 = 9, upptagen prova H+1 = 0 58 % 10 = 8, upptagen, H+1=9 upptagen, H+4=2 9 % 10 = 9, nästa lediga plats H+4=3 195 Sluten hashing – Kvadratisk teknik • Inte säkert att man hittar en ledig plats även om det finns! Exempel: – Man har en tabell som är 16 element stor och man använder hashfunktionen h(x) = x % 16 – Stoppar in elementen 0, 16, 32 och 64. Då finns det inte plats för några fler tal som hashas till 0. De enda platser som kommer prövas är de upptagna 0, 1, 4 , 9. • Men: – Om kvadratisk teknik används och tabellens storlek är ett primtal så kan ett nytt element alltid stoppas in om fyllnadsgraden < 0.5. – Fullständig komplexitetsanalys saknas men i praktiken ger den upphov till mindre klustring än linjär hashing. 197 196 Öppen hashing • Istället för cirkulär vektor används en vektor av lista. I lista nummer i ligger alla element med hashvärde i. • Värstafallskomplexitet: – O(n) för alla operationer (Alla element i samma lista). • Medelfallskomplexitet: – Insättning och misslyckad sökning blir n/tableSize – Lyckad sökning blir (n-1)/(2*tableSize)+1 • Tumregel: Inte fler än 2*tableSize element bör sättas in. 198 Från: http://www.ida.liu.se/labs/logpro/ulfni/dalg/resources/f8.pdf Exempel: Öppen hashing • Grundmängden 1, 2, …, 125 ska avbildas på värdena 0, 1, ..., 6. • Mängd = {1, 7, 27, 64, 125} Använd hashfunktionen f(x) = x % 7. 0 1 2 Linear prob = Sluten hashing med linjär teknik Chaining = Öppen hashing Double hash = Hashning med en teknik som liknar den slutna med kvadratisk teknik 7 1 64 27 125 3 4 5 6 199 Hashfunktionen • Vi har använt h(x) = x % m i exemplen. – Fungerar bra om m är ett primtal. – Annars kan periodicitet uppstå. • Om x tal så finns mer generella metoder, tex – h(x) = ((c1*x + c2) % p) % m • p stort primtal > m, tex 1048583 • c1 och c2 konstant heltal > 0 och < p • c2 ofta satt till 0 201 200