Föreläsning 7: Prioritetskö, trappa heapsort, hashning

Relevanta dokument
DD1320 Tillämpad datalogi. Lösning (skiss) till tenta 20 okt 2011

Hashtabeller! (& kanske lite sortering)

Tildatenta Lösningsskiss

Grundläggande datalogi - Övning 4

3. Toppkvinnor på hög Låt lådan och de två kvinnornas famnar utgöra stackarna L, K1 respektive K2. Från början finns alla kort i L.

ADT Prioritetskö. Föreläsning 13 Innehåll. Prioritetskö vs FIFO-kö. Prioritetskö Exempel på användning. Prioritetsköer och heapar

Övning 4. Hashning, sortering, prioritetskö, bästaförstsökning. Hitta på en perfekt hashfunktion för atomer. Hur stor blir hashtabellen?

ADT Prioritetskö. Föreläsning 12 Innehåll. Prioritetskö. Interface för Prioritetskö. Prioritetsköer och heapar

Avbildningar och hashtabeller. Koffman & Wolfgang kapitel 7, mestadels avsnitt 2 4

Datastrukturer. föreläsning 6. Maps 1

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 9 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Fredag 10 juni 2016 kl 8 12

Seminarium 13 Innehåll

Föreläsning 10 Innehåll. Prioritetsköer och heapar. ADT Prioritetskö. Interface för Prioritetskö. Exempel på vad du ska kunna

Föreläsning 13 Innehåll

Hitta k största bland n element. Föreläsning 13 Innehåll. Histogramproblemet

Lösningar Datastrukturer TDA

Föreläsning 10: Prioritetskö, trappa (heap), heapsort, Reguljära uttryck

KTH, NADA, Vahid Mosavat. 1. Flervalsfrågor (5p)

Träd Hierarkiska strukturer

Tentamen i Algoritmer & Datastrukturer i Java

DD1321, Tentamen i tillämpad programmering och datalogi Lördagen den 18 dexember 2010 kl 13 18

Föreläsning 4 Datastrukturer (DAT037)

Föreläsning 14 Innehåll

Föreläsning 5 Datastrukturer (DAT037)

Föreläsning 13 och 14: Binära träd

Tentamen kl Uppgift 4. Uppgift 5

Tentamen Datastrukturer D DAT 035/INN960

Grundläggande Datalogi för F

Innehåll. Föreläsning 12. Binärt sökträd. Binära sökträd. Flervägs sökträd. Balanserade binära sökträd. Sökträd Sökning. Sökning och Sökträd

Föreläsning 6 Datastrukturer (DAT037)

Tommy Färnqvist, IDA, Linköpings universitet. 1 ADT Map/Dictionary Definitioner Implementation... 2

Magnus Nielsen, IDA, Linköpings universitet

Tentamen Datastrukturer D DAT 035/INN960 (med mycket kortfattade lösningsförslag)

Grundläggande Datalogi för F

Datastrukturer och algoritmer. Föreläsning 15 Inför tentamen

Tentamen Datastrukturer D DAT 035/INN960

Sätt att skriva ut binärträd

Datalogiövning 31/7 2007

13 Prioritetsköer, heapar

Prov i DAT 312: Algoritmer och datastrukturer för systemvetare

Tommy Färnqvist, IDA, Linköpings universitet. 1 ADT Map/Dictionary Definitioner Implementation... 2

Översikt. Stegvis förfining. Stegvis förfining. Dekomposition. Algoritmer. Metod för att skapa ett program från ett analyserat problem

Datastrukturer och algoritmer. Innehåll. Tabell. Tabell - exempel. Gränsyta till Tabell. Tabell. Modell. Hashtabell Relation, lexikon.

Sökning. Översikt. Binärt sökträd. Linjär sökning. Binär sökning. Sorterad array. Linjär sökning. Binär sökning Hashtabeller

Abstrakta datatyper. Primitiva vektorer. Deklarera en vektor

Föreläsning 9: Prioritetskö, trappa (heap), heapsort, Reguljära uttryck

DAI2 (TIDAL) + I2 (TKIEK)

Tentamen, Algoritmer och datastrukturer

Föreläsning 4 Datastrukturer (DAT037)

Övning 3 - Tillämpad datalogi 2012

Lösningsförslag till tentamen Datastrukturer, DAT037,

Kungl. Tekn. Högskolan Förel 1, bild 1 Föreläsning 1: Introduktion ffl Kursinnehåll ffl Javarepetition ffl Referenser ffl Nyckelordet static ffl Klass

Hashing Bakom kulisserna på Pythons dictionary

Hashing Bakom kulisserna på Pythons dictionary. Leta i listor Osorterade listor. Leta i listor Sorterade listor

Objektorienterad programmering E. Algoritmer. Telefonboken, påminnelse (och litet tillägg), 1. Telefonboken, påminnelse (och litet tillägg), 2

Föreläsning 8. Mängd, Avbildning, Hashtabell

Sortering. Brute-force. Sortering Ordna element enligt relation mellan nyckelvärden

Övning 4 - Tillämpad datalogi 2013

Hashing Bakom kulisserna på Pythons dictionary

Tentamen, EDA690 Algoritmer och Datastrukturer, Helsingborg

Länkade listor, stackar och köer

Datastrukturer i kursen. Föreläsning 8 Innehåll. Träd rekursiv definition. Träd

Inom datalogin brukar man använda träd för att beskriva vissa typer av problem. Om man begränsar sig till träd där varje nod förgrenar sig högst två

Föreläsning 8. Mängd, Avbildning, Hashtabell

DD1320 Tillämpad datalogi. Lösnings-skiss till tentamen

Lösningsförslag för tentamen i Datastrukturer (DAT037) från

Exempeltenta GruDat 2002/2003

Föreläsning 9 Innehåll

Datastrukturer, algoritmer och programkonstruktion (DVA104, VT 2015) Föreläsning 6

Föreläsning Datastrukturer (DAT036)

Övning 2. (Länkade) Listor, noder

Föreläsning 3: Abstrakta datastrukturer, kö, stack, lista

Föreläsningsanteckningar, Introduktion till datavetenskap HT S4 Datastrukturer. Tobias Wrigstad

Övning 7 - Tillämpad datalogi DD1320, TENTAMEN I TILLÄMPAD DATALOGI Tisdagen den 12 januari 2010 kl 14 18

Tentamen i Algoritmer & Datastrukturer i Java

Algoritmer, datastrukturer och komplexitet

Föreläsning 9 Innehåll

Lösningsförslag till tentamen Datastrukturer, DAT037 (DAT036), Tiden det tar att utföra en iteration av loopen är oberoende av värdet på

Tentamen'('Datastrukturer,'algoritmer'och'programkonstruktion.'

Föreläsning Datastrukturer (DAT036)

Föreläsning Datastrukturer (DAT037)

Föreläsning 5: Grafer Del 1

Algoritmer. Två gränssnitt

Föreläsning 9 Datastrukturer (DAT037)

Algoritmer och datastrukturer 2012, fo rela sning 8

Grundläggande Datalogi

Algoritmer och datastrukturer

Tentamen (del 2) (4 högskolepoäng) i Programkonstruktion och datastrukturer (1DL201)

Linjärt minne. Sammanhängande minne är ej flexibelt. Effektivt

Sökning och sortering

Föreläsning 13 Datastrukturer (DAT037)

Innehåll. Sökning och hashtabeller. En bilsamling att söka i. En bil-klass att söka efter. Hur hittar vi alla bilar som uppfyller ett annat villkor

Lösningsförslag till tentamen i EDA690 Algoritmer och Datastrukturer, Helsingborg

Trädstrukturer och grafer

Programmering för Språkteknologer II. Innehåll. Associativa datastrukturer. Associativa datastrukturer. Binär sökning.

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 8 Erik Nilsson, Institutionen för Datavetenskap, LiU

Programmering för språkteknologer II, HT2014. Rum

Programmering fortsättningskurs

Teoretisk del. Facit Tentamen TDDC (6)

Transkript:

Föreläsning 7: Prioritetskö, trappa heapsort, hashning Prioritetskö, Trappa Heapsort Bästaförstsökning Implementation hashning Prioritetskö När man poppar en stack får man ut det senast inpushade. När man tar ut något ur en vanlig kö får man tvärtom ut det som legat längst tid i kön. Man skulle kunna se det som att det som stoppas in tidsstämplas och att det påstämplade talet ger prioriteten för uthämtning. I en prioritetskö stämplas en prioritet på varje objekt som stoppas in och vid uthämtning får man objektet med högst prioritet. En abstrakt prioritetskö kan ha följande anrop. pri_queue.put(x) Stoppa in x med påstämplad prioritet p. x= pri_queue.get(); Hämta det som har högst prioritet. pri_queue.isempty(); Undersök om prioritetskön är tom. Om det man vill stoppa in i prioritetskön är ett tal kan man använda talet självt som prioritet och bara skriva put(x). Hur den då skiljer sej från en stack och från en vanlig kö ser man av följande exempel. pq.put(1); pq.put(3); pq.put(2); x = pq.get(); // x blir 3 En kö hade skickat tillbaka det först instoppade talet 1; en stack hade skickat tillbaka det senast instoppade talet, 2; prioritetskön skickar tillbaka det bästa talet, 3. I denna prioritetskö betraktar vi största talet som bäst - vi har en så kallad maxprioritetskö. Det finns förstås också minprioritetsköer, där det minsta talet betraktas som bäst. Prioritetsköer har många användningar. Man kan tänka sej en auktion där budgivarna stoppar in sina bud i en maxprioritetskö och auktionsförrättaren efter "första, andra, tredje" gör pq.get() för att få reda på det vinnande budet. För att han ska veta vem som lagt detta bud behöver förstås fler uppgifter lagras. pq.put(person) # person är ett objekt med bud, budgivarens # namn och adress m.m.

winner = pq.get() # budgivaren med högst bud Trappa Den bästa implementationen av en prioritetskö är en trappa, (eng heap), som är en hakvektor trappsteg tolkad som binärträd. Roten är trappsteg[1] (vi använder inte trappsteg[0]), dess båda söner är trappsteg[2] och trappsteg[3] osv. Allmänt gäller att trappsteg[i] har sönerna trappsteg[2*i] och trappsteg[2*i+1]. Trappvillkoret är att pappa är bäst, dvs varje tal ligger på två sämre tal. Ett nytt tal läggs alltid in sist i trappan. Om trappvillkoret inte blir uppfyllt, dvs om det är större än sin far, byter far och son plats och så fortgår det tills villkoret uppfyllts. Det här kallas upptrappning och kan i värsta fall föra det nya talet hela vägen upp till toppen, alltså trappsteg[1]. Man plockar alltid ut det översta talet ur trappan och fyller igen tomrummet med det sista talet i trappan. Då är inte trappvillkoret uppfyllt, så man får byta talet och dess störste son. Denna nedtrappning upprepas till villkoret åter gäller. Både put och get har komplexitet log N om trappan har N element. Nackdelen med trappan är man måste bestämma hakvektorns storlek från början. Sortering med prioritetskö Om man stoppar in N tal i en trappa och sedan hämtar ut dom ett efter ett får man dom sorterade. Komplexiteten för denna heapsort blir O(N log N), alltså av lika god storleksordning som quicksort. Visserligen är quicksort lite snabbare, men heapsort har inte quicksorts dåliga värstafallsbeteende, och så kan ju en heap användas till andra saker än sortering också. Kom ihåg att hela datamängden är inte inbördes sorterad. Det enda vi vet är att föräldern i varje nod är mer prioriterad än sina barn. (Detta är en enkel koll man kan göra innan man lämnar in ett tentasvar)

Bästaförstsökning Labb 5 behandlar problemet att finna kortaste vägen från FAN till GUD. Man har då ett problemträd med FAN som stamfar, på nivån därunder sönerna MAN, FIN, FAT osv, på nästa nivå fans sonsöner osv. Om man lägger sönerna i en kö kommer man att gå igenom problemträdet nivå för nivå, alltså breddenförst. Om man byter kön mot en stack blir sökningen djupetförst. Med en prioritetskö får man bästaförstsökning, dvs den mest lovande sonen prioriteras och får föda söner. Exempel 1: Sök billigaste transport från Teknis till Honolulu. All världens resprislistor finns tillgängliga. Problemträdets poster innehåller en plats, ett pris och en faderspekare. Överst i trädet står Teknis med priset noll. Sönerna är alla platser man kan komma till med en transport och priset, till exempel T-centralen, 9.50. Man söker en Honolulupost i problemträdet. Med breddenförstsökning får man den resa som har så få transportsteg som möjligt. Med bästaförstsökning får man den billigaste resan. Exempel 2: Sök effektivaste processen för att framställa en önskad substans från en given substans. All världens kemiska reaktioner finns tillgängliga med uppgift om utbytet i procent. Problemträdets poster innehåller substansnamn och procenttal. Överst i trädet står utgångssubstansen med procenttalet 100. Sönerna är alla substanser man kan framställa med en reaktion och utbytet, till exempel C2H5OH, 96%. Med en max-prioritetskö får man fram den effektivaste process som leder till målet. Körexempel a = PriorityQueue() infil = file("numbers.txt") for number in infil: print "put %2d into a ->" % int(number), a.put(int(number.strip())); print a.size, a.elements[1:a.size + 1] print "========================================================" while not a.isempty() : x = a.get(); print "got %2d a -> %s" % (x, a.elements[1:a.size + 1]) utskrift put 3 into a -> [3] put 43 into a -> [43, 3] put 6 into a -> [43, 3, 6] put 12 into a -> [43, 12, 6, 3] put 52 into a -> [52, 43, 6, 3, 12] put 7 into a -> [52, 43, 7, 3, 12, 6] put 75 into a -> [75, 43, 52, 3, 12, 6, 7] put 65 into a -> [75, 65, 52, 43, 12, 6, 7, 3] put 29 into a -> [75, 65, 52, 43, 12, 6, 7, 3, 29] ======================================================== got 75 a -> [65, 43, 52, 29, 12, 6, 7, 3] got 65 a -> [52, 43, 7, 29, 12, 6, 3] got 52 a -> [43, 29, 7, 3, 12, 6] got 43 a -> [29, 12, 7, 3, 6] got 29 a -> [12, 6, 7, 3] got 12 a -> [7, 6, 3] got 7 a -> [6, 3] got 6 a -> [3] got 3 a -> []

Implementation class PriorityQueue: def init (self) : self.size = 0 self.elements = ["empty"] # index noll används inte self.cmp = cmp def isempty(self) : return self.size < 1 def put(self, data) : self.size += 1 self.elements.append(data) i = self.size while (i > 1 and self.cmp(self.elements[i/2], self.elements[i]) < 1) : (self.elements[i/2], self.elements[i]) = \ (self.elements[i], self.elements[i/2]) i = i / 2 def get(self) : if not self.isempty() : data = self.elements[1] self.elements[1] = self.elements[self.size] self.size -= 1 i = 1 while i <= self.size / 2 : j = self.biggestchild(i) if self.cmp(self.elements[i], self.elements[j]) < 1 : (self.elements[i], self.elements[j]) = \ (self.elements[j], self.elements[i]) i = j return data else: return None def biggestchild(self, i) : if (2 * i + 1 > self.size) : return 2 * i if self.cmp(self.elements[2 * i], self.elements[2 * i + 1]) > 0 : return 2 * i else : return 2 * i + 1 med a.cmp = lambda a,b : a < b får man en annan utskrift put 3 into a -> [3] put 43 into a -> [3, 43] put 6 into a -> [3, 43, 6] put 12 into a -> [3, 12, 6, 43] put 52 into a -> [3, 12, 6, 43, 52] put 7 into a -> [3, 12, 6, 43, 52, 7] put 75 into a -> [3, 12, 6, 43, 52, 7, 75] put 65 into a -> [3, 12, 6, 43, 52, 7, 75, 65] put 29 into a -> [3, 12, 6, 29, 52, 7, 75, 65, 43]

Hashning Binärsökning i en ordnad vektor går visserligen snabbt, men sökning i en hashtabell är oöverträffat snabbt. Och ändå är tabellen helt oordnad (hash betyder ju hackmat, röra). Låt oss säga att vi söker efter Kalas i en hashtabell av längd 10000. Då räknar vi först fram hashfunktionen för söknyckeln Kalas och det ger detta resultat. "Kalas".hashCode() -> 72260712 Hashvärdets rest vid division med 10000 beräknas nu 72260712 % 10000 -> 712 och när vi kollar hashtabellens index 712 hittar vi Kalas just där! Hur kan detta vara möjligt? Ja, det är inte så konstigt egentligen. När Kalas skulle läggas in i hashtabellen gjordes samma beräkning och det är därför ordet lagts in just på 712. Hur hashfunktionen räknar fram sitt stora tal spelar just ingen roll. Huvudsaken är att det går fort, så att inte den tid man vinner på inbesparade jämförelser äts upp av beräkningstiden för hashfunktionen. Komplexiteten för sökning Linjär sökning i en oordnad vektor av längd N tar i genomsnitt N/2 jämförelser, binär sökning i en ordnad vektor log N men hashning går direkt på målet och kräver bara drygt en jämförelse. Varför drygt? Det beror på att man aldrig helt kan undvika krockar, där två olika namn hamnar på samma index. Dimensionering av hashtabellen Ju större hashtabell man har, desto mindre blir risken för krockar. En tumregel är att man bör ha femtio procents luft i vektorn. Då kommer krockarna att bli få. En annan regel är att tabellstorleken bör vara ett primtal. Då minskar också krockrisken, som vi ska se nedan. Hashfunktionen Egentligen skulle man vilja ha en perfekt hashfunktion, dvs en funktion som ger olika värden för olika söknycklar. I regel är dock detta inte praktiskt möjligt, eftersom det kräver 1. en hashtabell som har minst lika många platser som det finns söknycklar, 2. en perfekt hashfunktion som inte är för tidskrävande att beräkna, 3. att man lyckas hitta en perfekt hashfunktion (dom är sällsynta). I vissa specialfall (t ex när man vill skapa en tabell över reserverade ord) kan det finnas anledning att försöka hitta en perfekt hashfunktion och det finns algoritmer för detta, t.ex. Cichelli's metod och FHCD-algoritmen i Drozdeks bok. Men vi begränsar oss här till enklare metoder. Ofta gäller det först att räkna om en String till ett stort tal. I Java gör man ingen skillnad på en bokstav och dess nummer i UNICODE-alfabetet, därför kan ABC uppfattas som 656667. Det man då gör är att multiplicera den första bokstaven med 10000, den andra med 100, den tredje med 1 och slutligen addera talen. På liknande sätt gör metoden hashcode() men den använder 31 i stället för 100. Ur javadoc för String.hashcode():

int hashcode() Returns a hashcode for this string. The hashcode for a String object is computed as s[0]*31^(n-1) + s[1]*31^(n-2) +... + s[n-1] using int arithmetic, where s[i] is the ith character of the string, n is the length of the string, and ^ indicates exponentiation ( ) Om man vill söka på datum eller personnummer kan man använda det som stort heltal utan särskild hashfunktion. Exempel: sexsiffriga datum kan hashas in i hashvektorn med 990323 % size. En olämplig storlek är 10000, ty 990323 % 10000 --> 323 och vi ser att endast 366 av de 10 000 platserna kommer att utnyttjas. Det säkraste sättet att undvika sådan snedfördelning är att byta 10000 mot ett närliggande primtal, till exempel 10007. Det visar sej nämligen att primtalsstorlek ger bäst spridning. Alla objekt i Java får automatiskt en hashcode()-metod (ärvd från klassen Object). Men i regel returnerar metoden bara objektets minnesadress omvandlat till ett heltal, vilket vi inte har någon större nytta av. Krockhantering Det naturliga är att lägga alla namn som hashar till ett visst index som en länkad krocklista. Om man har femtio procents luft i sin vektor blir krocklistorna i regel mycket korta. Krocklistorna bör behandlas som stackarna, och hashtabellen innehåller då bara toppekarna top till stackarna. Den andra idén är att vid krock lägga posten på första lediga plats (linear probing). En fördel är att man slipper alla pekare. En nackdel blir att man sedan inte enkelt kan ta bort poster utan att förstöra hela systemet, vilket man kan lösa genom att markera poster som borttagna istället för att ta bort dom. Ett annat problem man brukar råka ut för här är klustring, man får stora klumpar med poster, vilket gör att det kan ta lång tid att hitta nästa lediga plats. Om man vid en krock på plats n istället väljer att titta på plats n+1 2, n+2 2, n+3 2 osv (quadratic probing) får man bättre fördelning av posterna. Python dictionary I python sker allt det här automatiskt med dictionary. I lab2 fanns följande rader. data[key] = (XXX4, XXX2, XXX3, XXX1); if todo.has_key('update'): Hashtabell eller Binära sökträd Sökning sker med O(1) respektive O(log n). Det är viss skillnad men inte jättestor. Binära träd är känsligt för sorterad indata. Binära sökträd har ett par fördelar, t.ex. att man kan skriva ut alla element i ordning. Man kan dessutom skriva ut flera element i ett visst intervall. Detta går inte med en hashtabell.