380 likes | 504 Views
Alg. Dat. Øvingsforelesning 3 Grafer, BFS, DFS og hashing. 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.
E N D
Alg. Dat Øvingsforelesning 3 Grafer, BFS, DFS og hashing
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).
Nabomatrise • En nxn matrise der en nabo er representert med en verdi • Nyttig hvis grafen er tett (dense graf)
Vektede nabolister 5 3 4 1 2 2 4 Hver node har en liste over sine naboer, med vekt som tilleggsinformasjon
Vektet nabomatrise 5 3 4 1 2 2 4 En nxn matrise der en nabo er representert med kostnaden til tilsvarende kant
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.pop() # 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 • 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(V2) • Må besøke alle noder (V), og sjekke alle mulige kanter (V2) (Den første blir O(V+E) fordi vi ikke sikkert kan si at hverken V>E eller E<V. For den andre kan vi helt sikkert si at V2>V, så derfor blir den O(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” før den trekker seg tilbake og fortsetter igjen ved første mulighet • 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 (ikke veldig) treg måte å gjøre det på • Også lettest å implementere :> • Med iterasjon har man nodene i en stakk • (Ikke mye) 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
Eksampel på hashing Vil kunne finne de ansattes navn ut fra telefonnummeret. Lagre navnet til hver person på posisjonen angitt av siste siffer: 73501293 --> Arne Hansen 73523450 --> ”Fins ikke!”
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
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.
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
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 • 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