Præsentation er lastning. Vent venligst

Præsentation er lastning. Vent venligst

1 Søgning II 2 Plan Søgning ved nøgletransformation (hashing) –Hashfunktioner –Kollisionsstrategier –Effektivitet –Hashing i Java ( class HashTable )

Lignende præsentationer


Præsentationer af emnet: "1 Søgning II 2 Plan Søgning ved nøgletransformation (hashing) –Hashfunktioner –Kollisionsstrategier –Effektivitet –Hashing i Java ( class HashTable )"— Præsentationens transcript:

1

2 1 Søgning II

3 2 Plan Søgning ved nøgletransformation (hashing) –Hashfunktioner –Kollisionsstrategier –Effektivitet –Hashing i Java ( class HashTable ) –Et eksempel på anvendelse ------------------------------------------------- Diagnostisk prøve

4 3 Hashing søgning ved nøgletransformation Med balancerede træer foretages O(log 2 N) sammenligninger af nøgler. Men er O(log 2 N) den bedst opnåelige kompleksitet? Nej. Hvordan opnås lavere kompleksitet? Med hashing, en metode, 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).

5 4 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. Grundlæggende ide

6 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

7 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).

8 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

9 8 Konstruktion af hashfunktioner (Korte nøgler) Korte nøgler (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]

10 9 Nøgler bestående af 4 ascii-tegn, tabelstørrelse 101. ascii a b c d hex 6 1 6 2 6 3 6 4 bin 01100001011000100110001101100100 Eksempel med korte nøgler 0x61626364 = 163383172416338831724 % 101 = 11 Nøglen “abcd” hasher til 11. 0x64636261 = 16842348491684234849 % 101 = 57 Nøglen “dcba” hasher til 57. 0x61626263 = 16338376671633837667 % 101 = 57 Nøglen “abbc” hasher også til 57. Kollision!

11 10 Tabelstørrelsen Vælg tabelstørrelsen som et primtal. Hvorfor? I eksemplet før havde vi “abcd” = 0x61626364 = 97*256 3 + 98*256 2 + 99*256 1 + 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.

12 11 Konstruktion af hashfunktioner (Lange nøgler) Lange nøgler (kan ikke 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.

13 12 Eksempel med lange nøgler Eksempel med 4 tegn. Men metoden virker for vilkårligt lange nøgler. Benyt Horners regel: 0x61626364 = 97*256 3 + 98*256 2 + 99*256 1 + 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

14 13 int hash(String key, int M) { int h = 0; for (int i = 0; i < key.length(); i++) h = (h*117 + key.charAt(i)) % M; return h; } For at sprede værdierne bedre er 256 her erstattet med 117. Hashfunktion i Java Java har en metode til hashkodning, hashCode, knyttet til klassen String. Metoden hash kunne derfor alternativt programmeres således: int hash(String key, int M) { return key.hashCode() % M; }

15 14 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; } 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; } Javas implementering af hashCode i String (Java 1.1)

16 15 Javas implementering af hashCode i String (Java 1.2) public int 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; }

17 16 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. Lad M være tabelstørrelsen. Hvor mange indsættelser kan i gennemsnit foretages, før der opstår en kollision? M 100 12 365 24 1000 40 10000 125 100000 396 1000000 2353

18 17 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). Mulighed 2 (åben adressering): Sørg for at N < M: læg kolliderende nøgler i tabellen.

19 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. succesfuld søgning: (7*1+6*2+1*3)/17 = 22/17 ≈ 1.29 mislykket søgning: (0+3+2+2+0+2+0+2+2+1+0)/11 = 14/11≈ 1.27

20 19 Indsættelse og søgning ved separat kædning class Node { String key, info; Node next; Node(String k, String i, Node n) { key = k; info = i; next = n; } } void insert(String key, String info) { int i = hash(key, M); a[i] = new Node(key, info, a[i]); } String search(String key) { for (Node n = a[hash(key, M)]; n != null; n = n.next) if (key.equals(n.key)) return n.info; return null; }

21 20 Effektivitet 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 21 Eksempel på åben adressering Lineæ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 22 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 0 1 2 3 4 5 6

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

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

26 25 Indsættelse og søgning ved lineær prøvning String search(String key) { int i = hash(key, M); while (a[i].info != null && !key.equals(a[i].key)) i = (i+1) % M; return a[i].info; } void insert(String key, String info) { int i = hash(key, M); while (a[i].info != null) i = (i+1) % M; a[i].key = key; a[i].info = info; } Hvad sker der, hvis tabellen bliver fuld? Hvorledes foretages sletning?

27 26 Sletning ved lineær prøvning void remove(String key) { int i = hash(key, M); while (a[i].info != null && (a[i].info == deleted || !key.equals(a[i].key))) i = (i+1) % M; if (a[i].info != null) a[i].info = deleted; } static final String deleted = new String(); insert: while (a[i].info != null && a[i].info != deleted) i = (i+1) % M; search: while (a[i].info != null && (a[i].info == deleted || !key.equals(a[i].key))) i = (i+1) % M;

28 27 Effektivitet 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. De præcise udtryk er: forsøg ved mislykket søgning, og forsøg ved succesfuld søgning, hvor  = N/M betegner fyldningsgraden.

29 28 Effektivitetskurver for lineær prøvning Mislykket søgningSuccesfuld søgning  

30 29 Klyngedannelse Efterhånden som tabellen bliver fyldt op, opstår der klynger.

31 30 Antag at alle positioner [i:j] indeholder poster, mens i-1, j+1 og j+2 er tomme. Argumentation for tendens til klyngedannese i-1j+1j+2 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.

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

33 32 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.

34 33 String search(String key) { int i = hash(key, M); int u = hash2(key, M); while (a[i].info != null && !key.equals(a[i].key)) i = (i+u) % M; return a[i].info; } Dobbelt hashing i Java void insert(String key, String info) { int i = hash(key, M); int u = hash2(key, M); while (a[i].info != null) i = (i+u) % M; a[i].key = key; a[i].info = info; }

35 34 Krav til den anden hashfunktion 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 h 2 (k) < M for ethvert k. Den skal være forskellig fra den første. Forslag 1: h 2 (k) = 1 + (M-2-k) mod (M-2) Forslag 2 (simplere og hurtigere): h 2 (k) = 8 - (k % 8) (k % 8 er de sidste 3 bit af k)

36 35 Effektivitet Dobbelt hashing bruger gennemsnitligt færre forsøg end lineær prøvning. Metoden bruger i gennemsnit 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. De præcise udtryk er: forsøg ved mislykket søgning, og forsøg ved succesfuld søgning, hvor  er fyldningsgraden.

37 36 Dobbelt hashing contra lineær prøvning   Mislykket søgningSuccesfuld søgning dobbelt hashing Mislykket søgning Succesfuld søgning   lineær prøvning

38 37 Antag at en nøgle og en peger fylder det samme: 1 ord. Antag endvidere at der er 4M poster. Separat kædning contra dobbelt hashing Dobbelt hashing med samme tidsforbrug: 4M poster, gennemsnitlig søgetid: 2.5 Pladskrav: cirka 6.7M ord (idet 1/(1-4/6.7) ≈ 2.5) d.v.s. 26% mindre plads. Dobbelt hashing med samme pladsforbrug: 4M poster, tabelstørrelse: 9M ord (9 M poster) Gennemsnitlig søgetid: 1/(1-4/9) ≈ 1.8, d.v.s. 28% hurtigere Plads ved separat kædning og tabelstørrelse M: 4M nøgler, 4M hægter i knuder, M tabelindgange (pegere til knuder) Pladskrav: 9M ord til 4M poster Gennemsnitlig søgetid: (1+4)/2 = 2.5

39 38 Antag at en peger til postens information fylder 1 ord. Separat kædning contra dobbelt hashing Dobbelt hashing med samme tidsforbrug: 4M poster, gennemsnitlig søgetid: 2.5 Pladskrav: cirka 2*6.7M ord (idet 1/(1-4/6.7) ≈ 2.5) d.v.s. 3% mere plads. Dobbelt hashing med samme pladsforbrug: 4M poster, tabelstørrelse: 13 M ord (6.5 M poster) Gennemsnitlig søgetid: 1/(1-4/6.5) ≈ 2.6, d.v.s. 4% langsommere Plads ved separat kædning og tabelstørrelse M: 4M nøgler, 4M pegere til posternes information 4M hægter i knuder, M tabelindgange (pegere til knuder) Pladskrav: 13M ord til 4M poster Gennemsnitlig søgetid: (1+4)/2 = 2.5

40 39 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

41 40 Hashing i Java class Dictionary 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(); }

42 41 class Hashtable 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() {... } }

43 42 Metoden get (benytter separat kædning) class HashtableEntry { int hash; Object key; Object value; HashtableEntry next; } private HashtableEntry table[]; private int count; private int threshold; private float loadFactor; 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; }

44 43 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

45 44 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

46 45 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

47 46 I Java 1.2 anbefales det at bruge interface Map i stedet for class Dictionary. class Dictionary er forældet (eng.: obsulete). Udover class HashTable findes class HashMap. Begge implementerer Map. De har meget tilfælles, men i modsætning til HashTable er HashMap ikke synkroniseret. Desuden kan det nævnes, at class TreeMap implementerer interfaceSortedMap ved hjælp af rød- sort-træer. Hashing i Java 1.2

48 47 Et avanceret eksempel på anvendelse af hashing (Den rejsende sælgers problem) En sælger skal besøge n byer. Find den korteste tur, som starter og ender i en given by. A B C D E F G Eksempel med 7 byer:

49 48 Heuristisk algoritme: (1) Start med en tilfældig tur. (2) Så længe 2 kanter kan ombytttes med 2 andre kanter, således at turen forkortes, så foretag ombytningen. Udfør denne algoritme et givet antal gange og vælg den korteste af de fundne ture. Effektivitetsproblem: Tiden til undersøgelse af, at ingen forbedring er mulig udgør 30-50% af køretiden. Denne tid (checkout time) kan mindskes, hvis det kan konstateres, at den aktuelle tur er blevet undersøgt før. 2-opt swap

50 49 (1) h er invariant overfor cyklisk permutering: h(c 1,c 2,..., c n-1,c n ) = h(c 2,c 3,..., c n,c 1 ) =... = h(c n-1,c n,..., c n-3,c n-2 ) = h(c n,c 1,..., c n-2,c n-1 ) (2) h er invariant overfor spejling: h(c 1,c 2,..., c n-1,c n ) = h(c n,c n-1,..., c 2,c 1 ) (3) h er beregningsmæssigt billig. 0 1 1 0 0 1 1 0 xor xor i Java: ^ En mulig hashfunktion: h(c 1,c 2,..., c n-1,c n ) = (c 1 *c 2 ) xor (c 2 *c 3 ) xor... xor (c n-1 *c n ) xor (c n *c 1 ) Problem: Bestem en hashfunktion h(c 1,c 2,..., c n-1,c n ) hvor c 1,..., c n angiver byernes numre, således at

51 50 Opdatering ved hver ombytning: h ^= r[c1]*r[c2] ^ r[c3]*r[c4] ^ r[c2]*r[c4] ^ r[c3]*r[c1]; 2-opt swap c3c3 c4c4 c2c2 c1c1 c3c3 c1c1 c2c2 c4c4 idet (a ^ b) ^ b = a Forbedret løsning: h(c 1,c 2,..., c n-1,c n ) = (r[c 1 ]*r[c 2 ]) xor (r[c 2 ]*r[c 3 ]) xor... xor (r[c n ]*r[c 1 ) hvor r[1..n] er en tabel af tilfældige heltal.

52 51 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

53 52 Ugeseddel 7 28. oktober - 3. november Læs kapitel 19 (side 277-292) og side 305-311 i kapitel 21 i lærebogen Løs følgende opgaver 1. Opgave 16.3 og 16.5.


Download ppt "1 Søgning II 2 Plan Søgning ved nøgletransformation (hashing) –Hashfunktioner –Kollisionsstrategier –Effektivitet –Hashing i Java ( class HashTable )"

Lignende præsentationer


Annoncer fra Google