© Anders Broberg, Lena Kallin Westin, 2007 Datastrukturer och algoritmer Föreläsning 10
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Innehåll Mängd Lexikon Hashtabell Kapitel 13 i boken
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Mängd 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. oVill 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.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Specifikation 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. Konstruktorer Empty – är den tomma mängden. Single (v) – bildar mängden med v som enda element. Insert (v, s) – sätter in elementet v i s. Union (s, t) – har som värde unionsmängden av s och t. Intersection (s, t) – har som värde snittmängden av s och t. Difference (s, t) – har som värde mängddifferensen mellan s och t.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Specifikation Inspektorer Isempty (s) – testar om mängden är tom. Member–of (v, s) – testar om v tillhör mängden s. Choose (s) – väljer ut ett godtyckligt element ur s. Notera att det är odefinierat vilket. Det krävs inte heller att det blir samma element varje gång för samma mängd. Modifikatorer Remove (v, s) – tar bort v ur s. Komparatorer Equal (s, t) – testar om mängderna s och t är lika. Subset (s, t) – testar om s är en delmängd av t.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Konstruktion av mängd Nästan oavsett konstruktion måste man hantera att det inte får finnas dubbletter. 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)
DoA VT -07 © Anders Broberg, Lena Kallin Westin, 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.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, 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. Dvs, 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.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, 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.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, 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 Läs mer i boken om funktion som datatyp och diskussionen kring det.
Implementation av heltalsmängd som boolesk vektor (1) #include #define MAXLENGTH 100 typedef struct intSet_s{ int s[MAXLENGTH]; }intSet_t; /* "Interface" */ intSet_t *intSet_empty(); intSet_t *intSet_single(int num); intSet_t *intSet_insert(int num, intSet_t *set); intSet_t *intSet_union(intSet_t *t, intSet_t *set); intSet_t *intSet_intersection(intSet_t *t, intSet_t *set); intSet_t *intSet_difference(intSet_t *t, intSet_t *set) int intSet_isEmpty(intSet_t *set); int intSet_memberOf(int num, intSet_t *set); int intSet_choose(intSet_t *set); intSet_t *intSet_remove(int num, intSet_t *set); int intSet_equal(intSet_t *t, intSet_t *set); int intSet_subSet(intSet_t *t, intSet_t *set); void intSet_printOut(intSet_t *set);
Implementation (2) intSet_t *intSet_empty(){ intSet_t *set = malloc(sizeof(intSet_t)); return set; } intSet_t *intSet_single(int num){ intSet_t *set = malloc(sizeof(intSet_t)); set->s[num] = 1; return set; } intSet_t *intSet_insert(int num, intSet_t *set){ set->s[num] = 1; return set; } intSet_t *intSet_union(intSet_t *t, intSet_t *set){ intSet_t *newSet = intSet_empty(); int i; for(i=0;i<MAXLENGTH;i++){ if (set->s[i] || t->s[i]) newSet = intSet_insert(i, newSet); } return newSet; }
Implementation (3) intSet_t *intSet_intersection(intSet_t *t, intSet_t *set){ intSet_t *newSet = intSet_empty(); int i; for(i=0;i<MAXLENGTH;i++){ if (set->s[i] && t->s[i]) newSet = intSet_insert(i, newSet); } return newSet; } intSet_t *intSet_difference(intSet_t *t, intSet_t *set){ intSet_t *newSet = intSet_empty(); int i; for(i=0;i<MAXLENGTH;i++){ if (set->s[i] && !t->s[i]) newSet = intSet_insert(i, newSet); } return newSet; }
Implementation (4) int intSet_isEmpty(intSet_t *set){ int i; for(i=0;i<MAXLENGTH;i++){ if (set->s[i]) return 0; } return 1; } int intSet_memberOf(int num, intSet_t *set){ return set->s[num]; } int intSet_choose(intSet_t *set){ int i; for(i=0;i<MAXLENGTH;i++){ if (set->s[i]) return i; } return -1; } ”Dopingvarning!”
”Doping” For- eller while- loopar med break/return Svåröverskådlig kod Inte entydigt när metoden avslutas int isEmpty(intSet_t *set) { int i; for(i=0;i<MAXLENGTH;i++) { if (set->s[i]) return 0; } return 1; } int isEmpty(intSet_t *set) { int i=0; int tmp = 1; while(tmp==1 && i < MAXLENGTH) { if (set->s[i]) tmp = 0; i = i+1; } return tmp; }
Implementation (5) intSet_t *intSet_remove(int num, intSet_t *set){ set->s[num] = 0; return set; } int intSet_equal(intSet_t *t, intSet_t *set){ int i; for(i=0;i<MAXLENGTH;i++){ if (set->s[i] != t->s[i]) return 0; } return 1; } int intSet_subSet(intSet_t *t, intSet_t *set){ int i; for(i=0;i<MAXLENGTH;i++){ if (t->s[i] && !set->s[i]) return 0; } return 1; }
Implementation (6) void intSet_printOut(intSet_t *set){ int i; printf("{"); for (i=0; i<MAXLENGTH; i++){ if(set->s[i]==1) printf("%d, ", i); } printf("}"); } int main(int argc, char *argv[]){ intSet_t *testSet = intSet_empty(); printf("Isempty? %d\n", intSet_isEmpty(testSet)); testSet = intSet_insert(4, testSet); testSet = intSet_insert(7, testSet); testSet = intSet_insert(2, testSet); printf("Isempty? %d\n", intSet_isEmpty(testSet)); intSet_printOut(testSet); return 0; }
DoA VT -07 © Anders Broberg, Lena Kallin Westin, 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)
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Lexikon Ett lexikon är som en tabell utan tabellvärden. Saknar 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.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Konstruktion av lexikon 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.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Hashtabell Implementera en mängd/lexikon som en bitvektor 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 funktion som avbildar den större indextypen A till en mindre indextyp B, dvs en hashfunktion.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Hashtabell 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.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Hashfunktion Funktionen f(x) avbildar en stor mängd nycklar, A, på en liten mängd tal, B. Kollisioner är oundvikliga! En bra hashfunktion kännetecknas av: Litet förväntat antal kollisioner Sprider elementen jämt över mängden. Bör påverkas av alla delar av nyckeln Kollisioner kan hanteras med Sluten hashing Öppen hashing
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Sluten hashing 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. oGer upphov till klustring i tabellen. 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. oSätt in en ”borttagen” markör i tabellen.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, 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 % h(1) = 1 h(8) = 1 h(27) = 6 h(64) = 1 h(125) = 6
DoA VT -07 © Anders Broberg, Lena Kallin Westin, 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.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, 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 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)
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Sluten hashing, kvadratisk teknik När fyllnadsgraden blir hög (>75%) blir operationerna för den linjära tekniken långsam. 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+i 2, … där H är elementets hashvärde.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Ex: Sluten hashing, kvadratisk teknik Sätt in talen 89, 18, 49, 58, 9 i en tabell med 10 platser. h(x) = x % % 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
DoA VT -07 © Anders Broberg, Lena Kallin Westin, 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.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Ö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.
DoA VT -07 © Anders Broberg, Lena Kallin Westin, 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 %
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Från: Linear prob = Sluten hashing med linjär teknik Chaining = Öppen hashing Double hash = Sluten hashing med kvadratisk teknik
DoA VT -07 © Anders Broberg, Lena Kallin Westin, Hashfunktionen Vi har använt h(x) = x % m i exemplen. Fungerar bra om m är ett primtal. Annars kan periodicitet uppstå. Finns mer generella metoder, tex h(x) = ((c 1 *x + c 2 ) % p) % m op stort primtal > m, tex oc 1 och c 2 konstant heltal > 0 och < p oc 2 ofta satt till 0