Alg. Dat Øvingsforelesning 3 Grafer, BFS, DFS og hashing Børge Rødsjø rodsjo@stud.ntnu.no
Dagens tema Grafer Hashing Øving 2: Redd Ratatosk Terminologi Representasjon av grafer Bredde først søk (BFS) Dybde først søk (DFS) Hashing Hashfunksjoner, hashtabeller Kollisjonshåndtering Øving 2: Redd Ratatosk Øving 3: Kobra lærer å stave
Terminologi: Grafer Node Kant Nabo Sykel Rettet graf DAGs Trær
Generelle grafer vs. trær Grafer er en overordnet, generell struktur Et tre er en graf som er sammenhengende, asyklisk og urettet I graftraversering er ”farging” nyttig I en graf kan man oppdage grå eller svarte noder på nytt Vi må huske hvilke noder vi har sett
Representasjon av grafer En graf består av noder og relasjoner G = (V, E). V er alle nodene, E er relasjoner mellom nodene (i dagligtale: kanter)
Representasjon av grafer En graf består av noder og relasjoner G = (V, E). V er alle nodene, E er relasjoner mellom nodene (i dagligtale: kanter)
Representasjon av grafer En graf består av noder og relasjoner G = (V, E). V er alle nodene, E er relasjoner mellom nodene (i dagligtale: kanter)
Representasjon av grafer En graf består av noder og relasjoner G = (V, E). V er alle nodene, E er relasjoner mellom nodene (i dagligtale: kanter)
Nabolister Hver node har en liste over sine naboer Nyttigst hvis det er få kanter i forhold til antall noder (en sparse graf). Node Naboer a c, d b c, e c d e f NIL
Nabomatrise En nxn matrise der en nabo er representert med en verdi Nyttig hvis grafen er tett (dense graf) a b c d e f 1
Bredde først søk (BFS) En enkel algoritme for å søke i en graf Har en kø over oppdagede (grå) elementer Vi har en mengde/liste av besøkte(ferdige) noder Se side 531 i Cormen
Bredde først søk (BFS) Begynn med å legge startnoden i kø Så lenge det finnes noder i køen: Plukk ut en node x fra starten av køen Legg alle naboer som ikke er besøkte eller oppdagede inn i køen (vi oppdager/gråfarger dem) Legg x inn i besøkt-mengden (farge den sort)
Kode for BFS def bfs(root): queue = Queue() queue.put(root) while len(queue) > 0: node = queue.get() # gjør noe fancy med noden her node.colour = Black for adj in node.adjacent: if adj.colour == White: adj.colour = Grey queue.add(adj)
Bruk av BFS Finne korteste vei fra en node til alle andre, i en uvektet graf Kan sjekke om en graf er bipartitt BFS er en viktig grunnstein for mer avanserte algoritmer
Kjøretid BFS Med naboliste: O(V + E) Med nabomatrise: O(V2) Må besøke alle noder (V), og sjekke alle kanter (E) Med nabomatrise: O(V2) Må besøke alle noder (V), og sjekke alle kanter (V2)
Dybde først søk (DFS) Også en enkel algoritme for å søke i en graf Starter i en node og søker i dybden så langt det går Se side 541 i Cormen
Implementere DFS Kan implementeres via både rekursjon og iterasjon Med rekursjon så kaller metoden seg selv Dette er en treg måte å gjøre det på Med iterasjon har man nodene i en stakk Kjappere, behøver ikke rekursive kall
Kode for rekursiv DFS def dfs(node): node.colour = Grey for adj in node.adjacent: if adj.colour == White: dfs(adj) # gjør noe fancy med noden her node.colour = Black
Iterativ DFS Tar vare på nodene i en stakk. Alle noder er hvite før man oppdager dem Når en node blir oppdaget blir den fargelagt grå, og lagt til i stakken. En node er ferdig når alle dens barn er ferdigbehandlet; da taes noden ut av stakken og fargelegges svart
Bruk av DFS Brukes i mange andre algoritmer Kjøretid: O(V + E) Strongly connected components (neste gang) Topologisk sortering (neste gang) Kjøretid: O(V + E) Alle noder(V) må besøkes, og alle kanter(E) må sjekkes
Hashing og hashtabeller Problemet vi søker en løsning på: Man har et lite/moderat antall elementer, i et stort verdiområde. Hvordan lagre og søke etter disse effektivt? Eks: Telefonnummer og navn på ansatte. Direkte-adressering vil kreve altfor stor plass.
Hvordan løser hashing dette? Hashing er en måte å konvertere verdier fra et stort utfallsrom til et som er mye mindre. Hashing gir en form for fingeravtrykk av en verdi. Vi kan bruke dette til å lagre og hente data effektivt fra en liten og kompakt tabell
Hashtabeller: Fordelene Oppslag i O(1) tid Innsetting i O(1) tid Sletting i O(1) tid O(1) betyr ”konstant tid” Dvs. at hastigheten på operasjonene ikke er avhengig av antall elementer i tabellen NB! Dette er average-case, ikke worst case
Hashtabeller En tabell hvor vi får en hash av dataene til å beskrive hvor vi lagrer dem.
Hashing Hashfunksjon: h(k) = x h er hashfunksjonen vi har valgt oss k er hashnøkkelen, hele eller deler av dataene x er hashen av nøkkelen, dvs. posisjonen der vi plasserer dataene i en hashtabell
Valg av hashfunksjon Mål: transformere potensielt store data til en indeks i en tabell Påkrevd egenskap: Deterministisk Ønsket egenskap: Uniform fordeling Ønsket egenskap: Kjapp å utføre
Valg av hashfunksjon Noen eksempler på enkle, gode funksjoner (Se Cormen kap. 11): Divisjonsmetoden (”modulo-metoden”) h(k) = k mod m Multiplikasjonsmetoden h(k) = m (k A mod 1)
Valg av hashfunksjon Noen eksempler på dårlige hashfunksjoner: En konstant funksjon: h(k) = 20 Java 1.1 (før 1998): java.lang.String.hashCode() benyttet kun de første 16 bokstavene i en string til å generere hashen.
Håndtering av kollisjoner Kjeding (”chaining”) Lagrer en lenket liste i hver hash-bøtte Hvis vi får mange kollisjoner tar det tid å lete etter elementene Fordel: Enkelt å implementere Ulempe: Kan bli tregt, og kan bli en del overhead
Håndtering av kollisjoner Lineær søking Hvis det er en kollisjon, prøv neste plass i tabellen Ulempe: ”Primary clustering” – yter enda dårligere enn kjeding hvis man har mange kollisjoner
Håndtering av kollisjoner Kvadratisk søking Hvis det er en kollisjon, prøv å hoppe videre slik: For hopp i: Posisjon = (h(k) + a*i + b*i2) mod m Fordel: Yter bedre enn de to forrige Ulempe: ”Secondary clustering” – kan fremdeles bli problemer hvis mange elementer hasher til samme posisjon
Håndtering av kollisjoner Dobbel hashing Bruker 2 hashfunksjoner, h1(k) og h2(k) Prøv først plass h1(k) i tabellen Hvis det oppstår kollisjon, prøv å hoppe h2(k) posisjoner videre helt til vi når en åpen plass Fordel: Enkel og kjapp å implementere
Øving 2: Redd Ratatosk Hvorfor vil BFS være bedre enn DFS her? Ratatosk har lik sjanse til å være på hvert nivå Færre noder per nivå nært roten av treet BFS sjekker da først de mest sannsynlige nodene Løsningsforslag ligger ute BFS bruker kø DFS bruker stakk
Øving 2: Redd Ratatosk Tweak-løsning Vi vet allerede hvilken node Ratatosk er i Trenger ikke å lese all input, og konstruere tre Husk koblinger ”barn->forelder”, fremfor andre veien La Ratatosk ”klatre ned” treet ved å følge koblingene Denne spesifikke løsningen er ikke eksamensrelevant
Øving 3: Kobra lærer å stave
Øving 3: Kobra lærer å stave def bygg(ordliste): Skal bygge et tre ut fra ei liste av (ord, posisjon) Skal returnere rot-noden def posisjoner(ord, indeks, node): Skal returnere ei liste av posisjoner der ”ord” matcher Hvis man møter spørsmålstegn, må man sjekke alle subtrær rekursivt, ved å spesifisere indeks og node i nye kall til posisjoner