Datastructuren en algoritmen voor CKI Jeroen Bransen 1 2 oktober 2015 1 met dank aan Hans Bodlaender en Gerard Tel
Priority queue
Priority queue ADT insert(q, x): voeg element x toe aan de queue maximum(q): geeft element met maximum key extract-max(q): haalt element met maximum waarde uit de queue en geeft deze terug increase-key(q, x, k): verhoog de key van element x naar de nieuwe waarde k Efficiente implementatie met heap
Voorbeeldprobleem We lange plank die in stukken gezaagd moet worden De stukken moeten lengte a 1.. a n hebben Totale lengte van plank is precies n i=1 a i Zagerij rekent x euro voor het doorzagen van plank van lengte x (ongeacht waar de zaagsnede zit) Op welke volgorde moeten we onze plankjes laten zagen zodat het zo goedkoop mogelijk is?
Woordenboeken (dictionaries)
Woordenboeken (dictionaries) Dynamische verzameling Mapping van sleutel (key) naar waarde (value) Bijvoorbeeld: cijferlijst is mapping van studentnummer naar cijfer Sleutel en waarde kunnen vanalles zijn In Java heet dit Map, in C heet het Dictionary
Woordenboek ADT x is element met veld x.key insert(t, x): voeg element x toe aan woordenboek delete(t, x): verwijder element x uit woordenboek search(t, k): zoek element met sleutel k op
Directe addressering Stel onze sleutelwaardes zijn integers Dan kunnen we sleutel gebruiken als array-index Array element bevat dan bijbehorende waarde
Directe addressering Introduction to Algorithms, Fig 11.1
Directe addressering direct-address-insert(t, x) 1 T [x.key] = x direct-address-delete(t, x) 1 T [x.key] = nil direct-address-search(t, k) 1 return T [k]
Problemen met directe addressering Wat als keys heel groot kunnen worden? We hebben geen oneindig groot geheugen Veel gevallen maar een klein deel van de keys gebruikt
Hashtabellen
Hashtabel Stel onze sleutels komen uit verzameling U (universe) Hash functie h : U {0, 1,.., m 1} Hashtabel T [0.. m 1] Element met sleutel k slaan we op in T [h(k)]
Hashtabel Stel onze sleutels komen uit verzameling U (universe) Hash functie h : U {0, 1,.., m 1} Hashtabel T [0.. m 1] Element met sleutel k slaan we op in T [h(k)] Voorbeeld hashfunctie: h(k) = k mod m
Hashtabel Introduction to Algorithms, Fig 11.2
Botsingen (collisions) Wat als h(k a ) == h(k b )? We bespreken twee oplossingen: Een keten maken (chaining), met op elke index een linked list Op een andere plek zetten (open addressing)
Hashtabel met ketens (chaining)
Ketens (chaining) Introduction to Algorithms, Fig 11.3
Ketens (chaining) chained-hash-insert(t, x) 1 voeg x toe aan begin van lijst T [h(x.key)] chained-hash-delete(t, x) 1 verwijder x uit lijst T [h(x.key)] chained-hash-search(t, k) 1 zoek element met sleutel k in lijst T [h(k)]
Looptijd chained-hash-insert: O(1) chained-hash-delete en chained-hash-search: O(1 + lengte(t [h(k)]))
Looptijd chained-hash-insert: O(1) chained-hash-delete en chained-hash-search: O(1 + lengte(t [h(k)])) In het slechtste geval zijn alle h(k) gelijk: Θ(n)
Verwachte looptijd met ketens
Aanname We gaan uit van perfecte hashfunctie Kans op elk van de m waardes gelijk Kans dat sleutel k in specifieke waarde uitkomt is dus 1/m
Analyse Hashtabel met m posities waar n elementen in zitten Load factor noemen we α = n/m α geeft dus gemiddeld aantal elementen per positie
Lijstlengtes Lijst op positie i heeft lengte n i n = n 1 + n 2 + n 3 +.. + n m E[n i ] = α
Verwachte looptijd Opzoeken in linked list met n elementen kost O(n) Verwachte tijd voor delete/search in hashtabel is dus O(1 + α)
Hashfuncties
Perfecte hashfunctie Gelijke kans op elk van de m plekken Hangt af van verdeling van alle sleutels Weten we meestal niet Perfecte hashfunctie kunnen we dus bijna nooit maken
Perfecte hashfunctie Gelijke kans op elk van de m plekken Hangt af van verdeling van alle sleutels Weten we meestal niet Perfecte hashfunctie kunnen we dus bijna nooit maken Uitzondering: uniforme verdeling van 0 k < 1 h(k) = mk
Goede hashfuncties Verdeeld sleutels zo goed mogelijk Minimaliseert kans dat sleutels die op elkaar lijken gelijke hash hebben Bijvoorbeeld: variabelenamen uit een programma, zowel pt als pts Hangt niet af van patronen in invoer
Aannames Alle sleutels zijn natuurlijke getallen ({0, 1, 2,..}) Alle data kan worden uitgedrukt als een getal Bijvoorbeeld: de String pt Characters kun je uitdrukken als hun ASCII waarde (p = 112 en t = 116) String is een grondtal-128 getal pt = (112 128) + 116 = 14452
Division method Neem de rest na deling door m (modulo) h(k) = k mod m
Division method Neem de rest na deling door m (modulo) h(k) = k mod m Slechte waardes voor m: m = 2 p (dat geeft laatste p bits van k) m = 2 p 1 bij een String in grondtal 2 p (dan geeft andere volgorde van zelfde karakters zelfde waarde)
Division method Neem de rest na deling door m (modulo) h(k) = k mod m Slechte waardes voor m: m = 2 p (dat geeft laatste p bits van k) m = 2 p 1 bij een String in grondtal 2 p (dan geeft andere volgorde van zelfde karakters zelfde waarde) Goede waardes voor m: Priemgetal niet te dicht bij een 2-macht Bijvoorbeeld n = 2000, met 8-bit Strings m = 701 is goede keus Priemgetal, niet dichtbij 2-macht, en is ongeveer 2000/3 dus gemiddeld 3 stappen Hashfunctie is dan dus h(k) = k mod 701
Multiplication method Neem constante A (0 < A < 1) Vermenigvuldig de key met A Neem daarvan deel achter de komma En vermenigvuldig dat met m en rond naar beneden af
Multiplication method Neem constante A (0 < A < 1) Vermenigvuldig de key met A Neem daarvan deel achter de komma En vermenigvuldig dat met m en rond naar beneden af Dus: h(k) = m(ka mod 1) Waarbij ka mod 1 = ka ka
Multiplication method Voordeel: waarde van m maakt niet uit voor effectiviteit m = 2 p kan handig zijn voor implementatie Niet alle waardes van A werken even goed A ( 5 1)/2 = 0.6180339887.. werkt vaak goed