Hutscodering die leeg kunnen zijn, tabel T: abstract stockage middel met plaatsen elementen vd. vorm (K, I) K is de sleutel (of key) en I bijhorende informatie creatie van een lege tabel; een nieuw element aan een tabel toevoegen; een element verwijderen uit een tabel; een element opzoeken in de tabel (en eventueel de bijhorende I wijzigen); een lijst maken van alle elementen in de tabel, eventueel gesorteerd. Verschillende organisaties: een array, een lineaire lijst of een boom. Welke: afhankelijk van welke vd. operaties het meeste uitgevoerd worden Bijkomende manier : hashing + bijzonder geschikt indien toevoegen en opzoeken meest voorkomende operaties. + het maken van een gesorteerde lijst : heel wat moeilijker. een tabel met 7 plaatsen, genummerd van 0 tot en met 6. 0 1 2 3 4 5 6 een lege tabel met zeven plaatsen Plaats waar een element toegevoegd wordt: berekenen uit de sleutel: sleutelwaarde (= de index bij letter) delen door de lengte van de tabel (7) en de restwaarde gebruiken als nummer van de plaats in de tabel. + element J 10 berekening: plaats 3 (10 % 7) + indien plaats 3 nog leeg is, element J 10 op die plaats toevoegen + op dezelfde manier : B 2 op plaats 2 toevoegen + het element S 19 op plaats 5 (19 % 7) 0 1 2 3 4 5 6 B 2 J 10 S 19 na toevoegen van drie elementen De techniek: illustratie elementen met een sleutel K gelijk aan de letters van het alfabet met een index deze index : de positie van de letter in het alfabet bijvoorbeeld A 1, B 2, C 3, R 18 en Z 26. voor de eenvoud: geen bijhorende informatie I in element opgenomen deze elementen opslaan: een array van 26 plaatsen voorzien, voor elk mogelijk element één plaats echter mogelijk: het aantal reëel voorkomende elementen is veel kleiner dan het aantal mogelijke waarden voor de sleutel een enorme geheugenverspilling interessant: een tabel voorzien met slechts een beperkt aantal plaatsen, d.w.z. kleiner dan het totaal aantal mogelijke waarden voor K algemeen: plaats berekenen uit sleutelwaarde (L n ) m.b.v. hash functie: h(l n ) = n % 7. goede hash functie: sleutels zo uniform mogelijk verdelen over alle mogelijke plaatsen van de tabel. Verder toevoegen van elementen N 14, W 23 en X 24 problemen 0 1 2 3 4 5 6 Toevoegen van element N 14 N 14 B 2 J 10 S 19 geen probleem: plaats 0 is leeg volgende element met sleutel W 23 : plaats h(w 23 )23 % 7 = 2 op plaats 2 : reeds een element aanwezig, namelijk B 2 element W 23 kan dus op deze plaats niet meer toegevoegd worden botsing: de twee sleutels B 2 en W 23 komen op hetzelfde hash adres : 2 = h(b 2 ) = h(w 23 ).
+ mogelijke oplossing: naar lege plaatsen met lagere nummers gaan zoeken, te vertrekken vanaf de plaats waar de botsing plaats vond + open addressing: elementen toevoegen op lege plaatsen van de tabel + W 23 : lege plaats op index 1 (vertrekkend vanaf 2 ) 0 1 2 3 4 5 6 element W 23 : niet op plaats 2 N 14 W 23 B 2 J 10 S 19 plaats 1 is nog leeg + element met sleutel X 24 : hash adres 3, op deze plaats reeds een element + ook al elementen in alle plaatsen met lagere nummers + er wordt dan verder gezocht vanaf het laatste element in de tabel + dus, X 24 kan toegevoegd worden op plaats 6. 0 1 2 3 4 5 6 X 24 : niet op 3, 2, 1, 0 N 14 W 23 B 2 J 10 S 19 X 24 uiteindelijk op plaats 6 Er zijn verschillende politieken om een botsing op te lossen. linear probing: de peil sequentie van sleutel L n wordt gevormd door af te tellen vanaf het hash adres h(l n ) en na 0 wordt verdergegaan met het laatste element van de tabel. dus met een probe decrement van 1, omdat voor het vinden van de volgende peillocatie 1 afgetrokken wordt van de actuele peillocatie omdat de elementen op lege plaatsen toegevoegd worden, spreekt men van open addressing with linear probing wanneer de tabel bijna vol is, resulteert deze methode in een slechte performantie + niet-lineaire peilmethode gebruiken om voor verschillende sleutels L n verschillende probe decrements te berekenen + dubbel hashing: de probe decrement wordt berekend met behulp van een hash functie (p(l n )) de probe sequentie: de plaatsen die onderzocht worden wanneer een nieuw element met sleutel L n toegevoegd wordt elk van deze plaatsen wordt gepeild om te bepalen of ze nog leeg is de eerste plaats in deze sequentie is het hash adres, h(l n ) de volgende plaatsen worden bepaald door de collision resolution policy in bovenstaand voorbeeld start de peil-sequentie voor de sleutel W 23 op plaats 2, gevolgd door de plaatsen 1, 0, 6, 5, 4 en 3 Een peil-sequentie is zodanig georganiseerd dat elke plaats in de tabel precies eenmaal onderzocht wordt. steeds een lege plaats vinden bij elke peil-sequentie: een volle tabel gedefinieerd als een tabel met exact één vrije plaats. op die manier zal dus steeds tijdens het aflopen van de peil-sequentie een lege plaats gevonden worden niet nodig om tijdens het aflopen het aantal bezochte elementen te tellen om te kunnen bepalen wanneer er kan gestopt worden met zoeken mogelijke methode berekenen decrement: het quotiënt nemen van deling van n door 7; wanneer dit quotiënt echter gelijk is aan nul, wordt een decrement gelijk aan 1 genomen. p(l n ) = max(1, n/7) voorbeelden: W 23 : max(1, 23/7) of 3 B 2 : max(1, 2/7) of 1 + elementen J 10, B 2, S 19 en N 14 toevoegen aan een lege tabel + eerste botsing bij het toevoegen van element W 23 : h(w 23 ) = 2, dus toevoegen op plaats 2; deze plaats is echter reeds bezet. + probe decrement berekenen: p(w 23 ) = 3, dus wordt naar plaats (2-3) 6 gekeken: deze plaats is nog vrij, zodat het element kan toegevoegd worden. 0 1 2 3 4 5 6 hash adres: h(w 23 ) = 2 N 14 B 2 J 10 S 19 W 23 probe decrement p(w 23 ) = 3
+ element X 24 toevoegen : plaats 3 (h(x 24 )) is bezet + probe decrement p(x 24 ) = 3; plaats (3-3) 0 is ook bezet + verder kijken naar plaats (0-3) 4: hier kan het element toegevoegd worden. 0 1 2 3 4 5 6 h(x 24 ) = 3: niet leeg N 14 B 2 J 10 X 24 S 19 W 23 p(x 24 ) = 3, dus 0 en dan 4 voordeel van dubbel hashing: + bij een botsing van twee sleutels op een initieel hash adres zijn de peil sequenties voor deze twee sleutels meestal verschillend, omdat een verschillende probe decrement gebruikt wordt + bijvoorbeeld de sleutels J 10 en X 24 geven het zelfde hash adres, namelijk 3 + p(j 10 ) = 1 en p(x 24 ) = 3 : de twee peil sequenties zijn verschillend + effect: meestal zal sneller een lege plaats gevonden worden dan wanneer bij een botsing dezelfde peil sequenties gebruikt worden Parameter: tabellengte botsing: bij het toevoegen van twee verschillende sleutels, K en L, aan de tabel, hebben deze beide sleutels hetzelfde hash adres: h(k) = h(l). goede performantie: niet veel botsingen bij implementatie van hash techniek: enkele parameters goed kiezen + eerste parameter : de lengte van de tabel. + de tabel moet zeker groter zijn dan het te verwachte aantal op te slaan elementen + load factor λ van een hash tabel met lengte M en N plaatsen bezet: λ = N/M. + hoe kleiner de load factor, hoe minder kans op botsingen. + de lengte van de tabel: dikwijls een priemgetal: heeft te maken met een goede hashfunctie Collision resolution by separate chaining botsingen oplossen door gebruik te maken van gelinkte lijsten resultaat van het toevoegen van elementen J 10, B 2, S 19, N 14, W 23 en X 24 0 1 2 3 4 5 6 N 14 B 2 J 10 S 19 W 23 X 24 + de tabel bevat pointers naar gelinkte lijsten + op zo n gelinkte lijst zitten de elementen waarbij de hashfunctie toegepast op de sleutel een zelfde waarde geeft. Parameter: aard van de hash functie + tweede keuze: de aard van de hash functie + een goede hash functie: de verschillende sleutels op een randomachtige maar uniforme manier over de volledige tabel verdelen + de hash functie: snel te berekenen deling methode: hash adres: de rest bij deling van sleutel door tabellengte h(k) = K % M bij tabellengte M lengte een macht van 2: afzonderen vd. laagste bits vd. sleutel gevaar: bij sommige sleuteltypes vrij dikwijls dezelfde rest: dus botsingen. randomachtige hash functie : beter deling uitvoeren met een priemgetal Er bestaan nog verschillende andere vormen voor de hash functie, bijvoorbeeld folding, middle-squaring en truncation.
folding (vouwen): de sleutel verdelen en de verschillende delen optellen een 9-cijfer sleutel: drie delen maken en deze delen optellen K = 013 402 122 013 + 402 + 122 537 In plaats van optellen : vermenigvuldiging, aftrekking of deling mogelijk middle-squaring: middelste cijfers uit de sleutel halen en kwadratteren indien resultaat nog groter dan tabellengte: operatie herhalen K = 013 402 122 402 2 161604 16 2 256 truncation een deel van de sleutel verwijderen en de rest als adres overhouden K = 013402 122 drie laatste cijfers 122 weinig rekentijd maar sleutels meestal niet op een uniforme manier over de tabel verspreid Cluster een rij van aaneensluitende bezette plaatsen in een hash tabel In zo n rij zit dus geen enkele vrije plaats meer. lineaire peil methode geeft aanleiding tot primary clustering wanneer een aantal sleutels op dezelfde plaats botsen, worden deze botsende sleutels toegevoegd op plaatsen vlak voor de botsplaats op die manier kunnen op een aantal plaatsen in de tabel clusters ontstaan zo n cluster groeit steeds sneller en sneller omdat de kans op een botsing bij een nieuw toe te voegen element steeds groter wordt tijdens die groei komen sommige kleine clusters samen wat aanleiding geeft tot grote clusters: het proces versterkt dus zichzelf Dubbel hashing daarentegen geeft geen aanleiding tot primary clustering. Bij een botsing wordt het element niet noodzakelijk toegevoegd vlak naast de botsplaats. Er ontstaat dus niet altijd een cluster van twee elementen. Parameter: probe sequentie lineair peilen eenvoudig/slecht dubbel hashing tweede hash functie nodig deling methode: quotiënt (gereduceerd tot een bereik 1 : M 1) bij deling van sleutel door tabellengte, tenzij dit quotiënt nul is max(1, p(k)%m) met p(k) = max(1, K/M) bij tabellengte M + een peil sequentie moet alle plaatsen van de tabel nagaan + lineair peilen OK: bij M = 7 en begin hashadres gelijk aan 4: achtereenvolgens plaatsen 4, 3, 2, 1, 0 en dan 6 en 5 bekeken totdat een vrije plaats gevonden wordt + dubbel hashing niet zo duidelijk maar met tabellengte M en probe decrement p(k) relatief priem: peil sequentie doet volledige tabel aan + twee getallen zijn relatief priem wanneer ze alleen 1 als gemeenschappelijke deler hebben; wanneer tabellengte een priemgetal is, voor probe decrement gelijk welk getal groter dan of gelijk aan 1 te gebruiken. + een andere combinatie voor een goede peil sequentie geeft: een tabellengte gelijk aan een macht van twee en een oneven getal voor de probe decrement Voorbeeld Gegeven een aantal elementen waarvan de sleutel (K) telkens bestaat uit drie letters, bijv. MEC. Deze elementen moeten in een hash tabel met lengte M = 11 geplaatst worden. De hash functie h(k) wordt berekend door de sleutel te interpreteren als een basis 26 geheel getal. Bij een sleutel K = X 2 X 1 X 0, krijgt elk alfabetisch teken X i een geheeltallige waarde tussen 0 en 25. Voor A wordt de waarde 0 gebruikt, B de waarde 1 en zo verder tot Z met een waarde 25. drie-letter code omzetten van basis-26 getal naar decimaal getal: Basis26Waarde(K) = X 2 26 2 + X 1 26 1 + X 0 26 0
Op basis van deze basis-26 waarde van de sleutel kunnen de hash functies gedefinieerd worden: h(k) = Basis26Waarde(K) % 11 p(k) = max(1, (Basis26Waarde(K)/11) % 11) Bijvoorbeeld: Basis26Waarde(MEC) = 12 26 2 + 4 26 1 + 2 26 0 = 8112 + 104 + 2 = 8218 het hash adres van MEC is h(mec) = 8218%11 = 1 lineair peilen: probe p(mec) gelijk aan 1, zoals voor alle andere sleutels dubbel hashing: probe decrement p(mec) = max(1, (8218/11)%11) = 10 lineair peilen dubbel hashing 0 GEN 0 ZOL 1 MEC 1 MEC 2 KOR 2 KOR 3 ZOL 3 LEU 4 BRU 4 GEN 5 ANT 5 ANT 6 GEE 6 GEE 7 7 8 8 BRU 9 TUR 9 10 LEU 10 TUR 17 botsingen 3 botsingen tabel met de volgorde waarin de elementen toegevoegd worden : K basis26(k) h(k) p(k) p(k) (lineair) (dubbel) MEC 8218 1 1 10 ANT 357 5 1 10 KOR 7141 2 1 1 BRU 1138 5 1 4 ZOL 17275 5 1 8 GEN 4173 4 1 5 LEU 7560 3 1 5 GEE 4164 6 1 4 TUR 13381 5 1 6 Het programma: /* hash.c : sleutel op basis van basis26 waarde */ #include <stdio. h> #include <s tdlib. h> #define NRIJ 11 /* priemgetal! */ #define LEN 4 char invoer [ ] = MEC, ANT, KOR, BRU, ZOL, GEN, LEU, GEE, TUR, 0 ; void voegtoe ( char a [ ] [ LEN], int m, char inv [ ], int ( decfun )(char [ ], int ) int plinfun ( char inv [ ], int m ) ; int pdubfun ( char inv [ ], int m ) ; void druktab( char a [ ] [ LEN], int m ) ; int verbose = 0; int aantal botsingen = 0;
int main( int argc, char argv [ ] ) char tabel [NRIJ ] [LEN] ; int m = NRIJ; int i = 0; int c = 0; int ( decfun )(char inv [ ], int m) ; decfun = plinfun ; while ( ( c=getopt ( argc, argv, ldv ))!= EOF ) switch ( c ) case l : decfun = plinfun ; break ; case d : decfun = pdubfun ; return (( inv [0] A ) 26 26) + (( inv [1] A ) 26) + inv [2] A ; int hfun ( char inv [ ], int m ) /* deling methode */ i = basis26 ( inv ) ; return i%m ; int plinfun ( char inv [ ], int m ) /* linear probing */ return 1; int pdubfun ( char inv [ ], int m) /* dubbel hashing */ break ; case v : verbose++; break ; memset ( tabel, \0, NRIJ LEN) ; while ( invoer [ i ] ) voegtoe ( tabel, m, invoer [ i ], decfun ) ; druktab( tabel, m) ; i++; printf ( Aantal botsingen : %d\n, aantal botsingen ) ; int basis26 ( char inv [ ] ) i = basis26 ( inv ) ; i = ( i /m)%m; return i==0? 1 : i ; void druktab( char a [ ] [ LEN], int m ) for ( i =0; i<m; i++) printf ( %3d : % 4.4s \n, i, a [ i ] ) ; void voegtoe ( char a [ ] [ LEN], int m, char inv [ ], int ( decfun )() ) int adr, i ; int dec ; i = adr = hfun ( inv, m) ;
dec = ( decfun )( inv, m) ; i f ( verb ose ) printf ( % 4.4s : %3d : %3d\n, inv, adr, dec ) ; while ( a [ i ] ) /* zolang er botsing is */ if ( strncmp ( a [ i ], inv, LEN) == 0 ) /* element reeds aanwezig */ return ; aantal botsingen++ ; i = dec ; /* volg probe sequentie */ if ( i < 0 ) i += m; strncpy ( a [ i ], inv, LEN) ; /* vul lege plaats in */ Denktaak Veronderstel dat hashfunctie in het voorbeeld vervangen wordt door bijgevoegde functie. Bereken de index in de hashtabel voor de eerste drie elementen (MEC, ANT, KOR), met m gelijk aan 11. Treedt er bij deze drie elementen reeds een botsing op? int hfun ( char inv [ ], int m ) i = ( inv [0]<<2) ˆ ( inv [1] > >1); return i%m ; Opties en argumenten dit programma werkt met opties en argumenten (a.out -l bestand) verschillende opties in while lus verwerken met getopt eerste en tweede argument van deze functie: parameters van main functie; het derde argument is een string met de mogelijke opties terugkeerwaarde is telkens één van de mogelijke opties; door middel van een switch kan de desbetreffende optie geselecteerd worden in het derde argument na de optieletter een dubbelpunt (:): bij deze optie is er een bijkomende parameter deze parameter door getopt bereikbaar gesteld via variabele optarg (char ) indien de parameter een getal is, moet de bijhorende numerieke waarde berekend worden, bijv. met de functie atoi of atof. nadat alle opties behandeld zijn, geeft getopt de waarde EOF terug variabele optind heeft dan als waarde de index van het eerstvolgende element in de argv array (dat niet met een minteken begint)