Alg. Dat Øvingsforelesning 3 Grafer, BFS, DFS og hashing Børge Rødsjø rodsjo@stud.ntnu.no
Dagens tema Grafer 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 a b c d e f Naboer c, d c, e d a 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 a 0 0 1 1 0 0 b 0 0 1 0 1 0 c 0 0 0 1 0 0 d 1 0 0 0 0 0 e 0 0 0 0 0 1 f 0 0 0 0 0 0
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) Må besøke alle noder (V), og sjekke alle kanter (E) Med nabomatrise: O(V 2 ) Må besøke alle noder (V), og sjekke alle kanter (V 2 )
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 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*i 2 ) 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, h 1 (k) og h 2 (k) Prøv først plass h 1 (k) i tabellen Hvis det oppstår kollisjon, prøv å hoppe h 2 (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