1 / 84

Hashing

Hashing. Plan. Hashing Hashfunktioner Kollisionsstrategier Effektivitet Hashing i Javas biblioteker Prioritetskøer Binær hob Heapsort Ekstern sortering. Hashing søgning ved nøgletransformation.

buck
Download Presentation

Hashing

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Hashing

  2. Plan • Hashing Hashfunktioner Kollisionsstrategier Effektivitet Hashing i Javas biblioteker • Prioritetskøer Binær hob Heapsort Ekstern sortering

  3. Hashingsøgning ved nøgletransformation • Med balancerede træer foretages O(log2N) sammenligninger af nøgler. • Men er O(log2N) den bedst opnåelige kompleksitet? • Nej. • Hvordan opnås lavere kompleksitet? • Med hashing, en teknik, der benytter transformationer af nøgler til direkte at kunne referere til poster i en tabel. • Med hashing opnås under gunstige omstændigheder kompleksitet O(1).

  4. Grundlæggende ide • Gem poster i en tabel på en plads, der er bestemt af deres nøgle. • En hashfunktion er en metode til beregning af et tabelindeks ud fra en nøgle. • Matematisk udtrykt: en hashfunktion er en afbildning af en mængde af nøgler på et indeksinterval. • Ideelt burde to forskellige nøgler afbildes på to forskellige indices. At to eller flere nøgler afbildes på samme indeks kaldes en kollision. • En kollisionsstrategi er en algoritme til håndtering af kollisioner.

  5. Tid/plads-opvejning(tradeoff) • Ingen pladsbegrænsninger: • benyt nøglen som indeks (triviel hashfunktion) • Ingen tidsbegrænsninger: • benyt sekventiel søgning • Hvis der er begrænsninger på både plads og tid: • benyt hashing

  6. Hashingteknikken • Lad h betegne hashfunktionen. • Indsættelse: En post med nøgle K placeres på indeks h(K), med mindre der i forvejen er en post på dette indeks. Så må posten placeres på anden måde (hvordan - afhænger af kollisionsstrategien). • Søgning: Ved søgning efter en post med nøgle K, undersøges først posten på indeks h(K). Hvis denne indeholder K, afsluttes søgningen med succes. Ellers fortsætter søgningen (hvordan - afhænger af kollisionsstrategien).

  7. “Gode” hashfunktioner • Kollisioner bør så vidt muligt undgås. Hashfunktionen bør sprede funktionsværdierne jævnt på hele indeksintervallet. • Hashfunktionen bør være beregningsmæssigt billig

  8. Konstruktion af hashfunktioner(Korte nøgler) • Korte nøgler(nøgler, der kan være i et maskinord): • betragt nøglen som et heltal og beregn • h(K) = K mod M (i Java: K % M) • hvor M er tabelstørrelsen. • h(K) [0;M-1]

  9. Eksempel med korte nøgler • Nøgler bestående af 4 ascii-tegn, tabelstørrelse 101. • ascii ab c d • hex 6 16 2 6 3 6 4 • bin 01100001011000100110001101100100 0x61626364 = 1633831724 16338831724 % 101 = 11 Nøglen "abcd" hasher til 11. 0x64636261 = 1684234849 1684234849 % 101 = 57 Nøglen "dcba" hasher til 57. 0x61626263 = 1633837667 1633837667 % 101 = 57Nøglen "abbc" hasher også til 57. Kollision!

  10. Tabelstørrelsen • Vælg tabelstørrelsen som et primtal. Hvorfor? • I eksemplet før havde vi "abcd" = 0x61626364 = • 97*2563 + 98*2562 + 99*2561 + 100 • Hvis tabelstørrelsen vælges til 256, vil kun det sidste tegn have betydning ved beregning af h. • En simpel måde at sikre sig, at alle tegn bidrager, er at vælge tabelstørrelsen som et primtal.

  11. Konstruktion af hashfunktioner(Lange nøgler) • Lange nøgler(nøgler, der ikke kan være i et maskinord): • betragt nøglen som et langt heltal og beregn • h(K) = K mod M • hvor M er tabelstørrelsen. • Altså i princippet som for korte nøgler.

  12. Eksempel med lange nøgler • Eksempel med 4 tegn. Men metoden virker også for vilkårligt lange nøgler. • Benyt Horners regel: • 0x61626364 = • 97*2563 + 98*2562 + 99*2561 + 100= • ((97*256 + 98)*256 + 99)*256 + 100 • Tag modulo efter hver addition for at undgå aritmetisk overløb: • (97*256 + 98 = 24930) % 101 = 84 • (84*256 + 99 = 21603) % 101 = 90 • (90*256 + 100 = 23140) % 101 = 11

  13. int hash(String key, int tableSize) { • int h = 0; • for (int i = 0; i < key.length(); i++) • h = h*37 + key.charAt(i); • h %= tableSize; • return h < 0 ? h + tableSize : h; • } Eksempel på hashfunktion • int hash(String key, int tableSize) { • int h = 0; • for (int i = 0; i < key.length(); i++) • h = (h*37 + key.charAt(i)) % tableSize; • return h; • } For at sprede værdierne bedre er 256 erstattet med 37. Modulo-beregningerne undervejs kan undværes:

  14. Javas implementering af hashCode i String(Java 1.1) public int hashCode() { int h = 0; int off = offset; char val[] = value; int len = count; if (len < 16) { for (int i = len; i > 0; i--) h = (h * 37) + val[off++]; } else { // only sample some characters int skip = len / 8; for (int i = len; i > 0; i -= skip, off += skip) h = (h * 39) + val[off]; } return h; }

  15. publicint hashCode() { • int h = 0; • int off = offset; • char val[] = value; • int len = count; • for (int i = 0; i < len; i++) • h = 31*h + val[off++]; • return h; • } Javas implementering af hashCode i String(Java 1.2)

  16. Hvis M være tabelstørrelsen, hvor mange indsættelser kan da i gennemsnit foretages, før der opstår en kollision? M 100 12 365 24 1000 40 10000 125 100000 396 1000000 2353 Hyppigheden af kollisioner • Fødselsdagsparadokset: Hvor mange personer skal være forsamlet i et selskab, for at der er mere en 50% sandsynlighed for at mindst to personer har fødselsdag på samme dag? • Svar: 24.

  17. Mulighed 2 (åben adressering): Sørg for at N < M: læg kolliderende nøgler i tabellen. Kollisionstrategier • Antal poster: N • Tabelstørrelse: M • Mulighed 1 (separat kædning): • Tillad N > M: • læg nøgler, der hasher til samme indeks, ind i en liste (med cirka N/M nøgler per liste).

  18. Separat kædning • Simpel, praktisk og meget udbredt metode. • Metode: M hægtede lister - en for hver tabelindgang. • 0: * • 1: L A W * • 2: M X * • 3: N C * • 4: * • 5: E P * (M = 11) • 6: * (N = 14) • 7: G R * • 8: H S * • 9: I * • 10: * Nedbringer den gennemsnitlige søgetid med en faktor M i forhold til sekventiel søgning.

  19. interface Hashable { int hash(int tableSize); } public interface HashTable { void insert(Hashable x); Hashable find(Hashable x) throws ItemNotFound; void remove(Hashable x) throws ItemNotFound; void makeEmpty(); } public class SeparateChainingHashTable implements HahTable { protected HashEntry[] array; ... } Implementering af separat kædning

  20. void insert(Hashable x) { int i = x.hash(array.length); array[i] = new HashEntry(x, array[i]); } Hashable find(Hashable x) throws ItemNotFound { for (HashEntry e = array[x.hash(a.length)]; e != null; e = e.next) if (x.equals(e.element)) return e.element; throw new ItemNotFound("HashTable find"); } class HashEntry { HashEntry(Hashable e, HashEntry n) { element = e; next = n; } Hashable element; HashEntry next; }

  21. Effektivitet af separat kædning • Indsættelse: 1 • Mislykket søgning: N/M (i gennemsnit) • Succesfuld søgning: N/M/2 (i gennemsnit) • Værste tilfælde for søgning: N. • Hvis listerne holdes sorteret: Tid for indsættelse øges til N/M/2 Tid for mislykket søgning mindskes til N/M/2

  22. Åben adresseringLineær prøvning • Åben adressering: Ingen hægter. Alle poster opbevares i tabellen. • Lineær prøvning: Start lineær søgning fra hashpositionen, og stands ved den søgte post eller en tom position. • Stadig konstant søgetid, hvis M er tilstrækkelig stor.

  23. 0 1 2 3 4 5 6 Et simpelt eksempel • Mængden af nøgler er alfabetets store bogstaver. Der er ingen information tilknyttet nøglerne. Tabelstørrelsen er 7. • h(K) = (K’s nummer i alfabetet) mod 7 = (K - ‘A’ + 1) % 7

  24. N 0 1 2 C 3 K 4 S 5 6 • Tabel efter indsættelse af nøglerne • C, K, N, S h(N) = 14 % 7 = 0 h(C) = 3 % 7 = 3 h(K) = 11 % 7 = 4 h(S) = 19 % 7 = 5

  25. N 0 1 2 C 3 h(Y) = 25 % 7 = 4 K 4 S 5 6 N 0 1 2 C 3 K 4 S 5 Y 6 Placering efter 3 forsøg Indsættelse af Y giver kollision

  26. N 0 1 2 C 3 h(D) = 4 % 7 = 4 K 4 S 5 Y 6 N 0 D 1 2 C 3 K 4 S 5 Y 6 Placering efter 5 forsøg (med “wrap around”) Indsættelse af D Bemærk: tabellen må ikke blive fuld!

  27. Implementering af lineær prøvning abstract class ProbingHashTable implements HashTable { ProbingHashTable() { allocateArray(DEFAULT_TABLE_SIZE); } void insert(Hashable x) { ... } Hashable find(Hashable x) throws ItemNotFound { ... } void remove(Hashable x) throws ItemNotFound { ... } void makeEmpty() { ... } abstract protected findPos(Hashable x); private allocateArray(int size) { array = new HashEntry[size]; } protected HashEntry[] array; private int currentSize; }

  28. class HashEntry { HashEntry(Hashable e) { element = e; isActive = true; } Hashable element; boolean isActive; } Hashable find(Hashable x) throws ItemNotFound { int pos = findPos(x); if (array[pos] == null || !array[pos].isActive) throw new ItemNotFound("HashTable find"); return array[pos].element; } void remove(Hashable x) throws ItemNotFound { int pos = findPos(x); if (array[pos] == null || !array[pos].isActive) throw new ItemNotFound("HashTable remove"); array[pos].isActive = false; }

  29. void insert(Hashable x) { int pos = findPos(x); array[pos] = new HashEntry(x); if (++currentSize < array.length/2) return; // rehash HashEntry[] oldArray = array; allocateArray(nextPrime(2*oldArray.length)); currentSize = 0; for (int i = 0; i < oldArray.length; i++) if (oldArray[i] != null && oldArray[i].IsActive) insert(oldArray[i].element); } Tidsforbruget for nextPrime er O(√N * logN) Ved rehash er simpel kopiering utilstrækkelig

  30. class LinearProbingHashTable extends ProbingHashTable { protected int findPos(Hashable x) { int pos = x.hash(array.length); while (array[pos] != null && !array[pos].element.equals(x)) pos = (pos + 1) % array.length; return pos; } } Klassen LinearProbingHashTable

  31. De præcise udtryk er: forsøg ved mislykket søgning, og forsøg ved succesfuld søgning, hvor  = N/M betegner fyldningsgraden. Effektivitet af lineær prøvning • Tyndt besat tabel: ligesom separat kædning. • Lineær prøvning bruger gennemsnitligt færre end 5 forsøg for en hashtabel, der er mindre end 2/3 fuld.

  32. a a Mislykket søgning Succesfuld søgning Effektivitetskurver for lineær prøvning

  33. i-1 j+1 j+2 Argumentation for tendens til klyngedannelse • Antag at alle positioner [i:j] indeholder poster, mens i-1, j+1 og j+2 er tomme. Så vil chancen for, at en ny post placeres på position j+1 være lig med chancen for, at en ny post skal placeres i intervallet [i:j+1]. For at den nye post placeres på j+2, skal dens hashværdi derimod være præcis j+2.

  34. Klyngedannelse • Uheldigt fænomen. • Lange klynger har en tendens til at blive længere. • Søgelængen vokser drastisk, efterhånden som tabellen fyldes. • Lineær prøvning er for langsom, når tabellen bliver 70-80% fuld.

  35. Kvadratisk prøvning(reducerer risikoen for klyngedannelse) Prøvningssekvens: Lineær prøvning: pos, pos + 1, pos + 2, pos + 3, ... Kvadratisk prøvning: pos, pos + 12, pos + 22, pos + 32, ... Lad Hi betegne den i’te position (H0 er startpositionen). Idet Hi-1 = pos + (i - 1) 2 = pos + i2 - 2i + 1 = Hi - 2i + 1 fås Hi = Hi-1 + 2i - 1 Kvadrering kan undgås

  36. class QuadraticProbingTable extends ProbingHashTable { protected int findPos(Hashable x) { int pos = x.hash(array.length); int i = 0; while (array[pos] != null && !array[pos].element.equals(x)) pos = (pos + 2 * ++i - 1) % array.length; return pos; } } Implementering af kvadratisk prøvning Det kan bevises, at Hvis fyldningsgraden altid er mindre end 0.5, er indsættelse mulig, og under indsættelsen vil ingen indgang blive prøvet mere end én gang.

  37. Dobbelt hashing • Undgå klyngedannelse ved at bruge en ekstra hashfunktion. Derved øges sandsynligheden for at finde tomme indgange ved indsættelse. • I stedet for som i lineær prøvning at prøve successive indgange, foretages prøvningen med en fast afstand bestemt af den anden hashfunktion.

  38. class DoubleHashTable extends ProbingHashTable { protected int findPos(Hashable x) { int pos = x.hash(array.length); while (array[pos] != null && !array[pos].element.equals(x)) pos = (pos + x.hash2(array.length)) % array.length; return pos; } } Implementering af dobbelt hashing

  39. Krav til den andenhashfunktion • Den bør ikke returne 0. • Den skal altid returnere værdier, der er primiske med M. Kan opnås ved at vælge M som et primtal og lade h2(k) < M for ethvert k. • Den skal være forskellig fra den første. • Forslag 1: h2(k) = 1 + (M-2-k) mod (M-2) • Forslag 2 (simplere og hurtigere): h2(k) = 8 - (k % 8) (k % 8 er de sidste 3 bit af k)

  40. De præcise udtryk er: forsøg ved mislykket søgning, og forsøg ved succesfuld søgning, hvor  er fyldningsgraden. Effektivitet af dobbelt hashing • Dobbelt hashing bruger gennemsnitligt færre forsøg end lineær prøvning. Færre end 5 forsøg ved en søgning, når tabellen højst er 80% fuld, og færre end 5 forsøg ved en succesfuld søgning, når tabellen højst er 99% fuld.

  41. dobbelt hashing Succesfuld søgning Mislykket søgning lineær prøvning a a Mislykket søgning Succesfuld søgning a a Dobbelt hashing contra lineær prøvning

  42. Fordele ved separat kædning • Idiotsikker metode (bryder ikke sammen) • Antallet af poster behøver ikke at være kendt på forhånd • Sletning er simpel • Tillader ens nøgler

  43. public abstract class Dictionary { abstract public Object put(Object key, Object value); abstract public Object get(Object key); abstract public Object remove(Object key); abstract public int size(); abstract public boolean isEmpty(); abstract public Enumeration keys(); abstract public Enumeration elements(); } Hashing i Javaclass Dictionary

  44. public class Hashtable extends Dictionary implements Cloneable { public Hashtable(int initialCapacity, float loadFactor); public Hashtable(int initialCapacity); public Hashtable() { this(101, 0.75f); } public synchronized Object put(Object key, Object value); public synchronized Object get(Object key); public synchronized Object remove(Object key); public int size(); public boolean isEmpty(); public synchronized Enumeration keys(); public synchronized Enumeration elements(); public synchronized boolean contains(Object value); public synchronized boolean containsKey(Object key); public synchronized void clear(); public synchronized Object clone(); public synchronized String toString(); protected void rehash(); } class Hashtable

  45. class HashtableEntry { int hash; Object key; Object value; HashtableEntry next; } public synchronized Object get(Object key) { HashtableEntry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (HashtableEntry e = tab[index]; e != null; e = e.next) if (e.hash == hash && e.key.equals(key)) return e.value; return null; } Metoden get(benytter separat kædning) private HashtableEntry table[]; private int count; private int threshold; private float loadFactor;

  46. public synchronized Object put(Object key, Object value) { if (value == null) throw new NullPointerException(); HashtableEntry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (HashtableEntry e = tab[index]; e != null; e = e.next) if (e.hash == hash && e.key.equals(key)) { Object old = e.value; e.value = value; return old; } if (count >= threshold) { rehash(); return put(key, value); } HashtableEntry e = new HashtableEntry(); e.hash = hash; e.key = key; e.value = value; e.next = tab[index]; tab[index] = e; count++; return null; } Metoden put

  47. protected void rehash() { int oldCapacity = table.length; HashtableEntry oldTable[] = table; int newCapacity = oldCapacity*2 + 1; HashtableEntry newTable[] = new HashtableEntry[newCapacity]; threshold = (int)(newCapacity * loadFactor); table = newTable; for (int i = oldCapacity; i-- > 0; ) { for (HashtableEntry old = oldTable[i]; old != null; ) { HashtableEntry e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = newTable[index]; newTable[index] = e; } } } Metoden rehash

  48. public synchronized Object remove(Object key) { HashtableEntry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (HashtableEntry e = tab[index], prev = null; e != null; prev = e, e = e.next) { if (e.hash == hash && e.key.equals(key)) { if (prev != null) prev.next = e.next; else tab[index] = e.next; count--; return e.value; } } return null; } Metoden remove

  49. Hashing i Java 1.2 I Java 1.2 anbefales det at bruge interface Map i stedet for class Dictionary. class Dictionary er forældet (deprecated). Udover class Hashtable findes class HashMap. Begge implementerer interfaceMap. De har meget tilfælles, men i modsætning til Hashtable er HashMapikke synkroniseret. Desuden kan det nævnes, at class TreeMap implementerer interface SortedMap ved hjælp af rød-sort-træer.

  50. Grunde til ikke at bruge hashing • Hvorfor bruge andre metoder? • Der er ingen effektivitetsgaranti • Hvis nøglerne er lange, kan hashfunktionen være for kostbar at beregne • Bruger ekstra plads • Understøtter ikke sortering

More Related