Graphen in Haskell Algorithmen und Datenstrukturen II Graphen in Haskell D. Rösner Institut für Wissens- und Sprachverarbeitung Fakultät für Informatik Otto-von-Guericke Universität Magdeburg c Sommer 2009, 17. April 2009, 2009 D.Rösner D. Rösner AuD II 2009 . . . 1 Graphen in Haskell Gliederung 1 Graphen in Haskell ADT Graph Implementierung Adjazenzlisten Adjazenzmatrix Suche Tiefensuche Breitensuche Topologisches Sortieren D. Rösner AuD II 2009 . . . 2 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell im folgenden wird ein abstrakter Datentyp (ADT) für gewichtete Graphen definiert Anpassung an ungewichtete Graphen durch Ignorieren der Gewichte Anforderungen an den Typ der Elemente eines Graphen: Typ der Knoten n: damit Knoten als Indizes von Arrays genutzt werden können, sollten sie zur Klasse Ix gehören Typ der Gewichte w: Gewichte sind Zahlen ( class Num) m.a.W. Typdefinition für Graph als type Graph n w s. [RL99], Ch. 7.2 D. Rösner AuD II 2009 . . . 4 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell im ADT Graph sollen Funktionen für die folgenden Aufgaben realisiert werden: eine Funktion zum Erstellen eines Graphen aus der Liste seiner (gewichteten) Kanten (mkGraph) zu einem beliebigen Knoten wird die Liste adjazenter Knoten geliefert (adjacent) alle Knoten des Graphen werden geliefert (nodes) alle Kanten des Graphen werden geliefert (edgesD, falls gerichtet, edgesU, falls ungerichtet) Test, ob eine bestimmte Kante im Graph existiert (edgeIn) das Gewicht der Kante zwischen zwei Knoten wird geliefert (weight) s. [RL99], Ch. 7.2 D. Rösner AuD II 2009 . . . 5 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell Moduldefinition: module Graph(Graph,mkGraph,adjacent,nodes,edgesU,edgesD,edgeIn, weight) where import Array ... die Signaturen des ADT Graph ergeben sich wie folgt: mkGraph :: (Ix n,Num w) => Bool -> (n,n) -> [(n,n,w)] -> (Graph n w) adjacent :: (Ix n,Num w) => (Graph n w) -> n -> [n] nodes :: (Ix n,Num w) => (Graph n w) -> [n] edgesU :: (Ix n,Num w) => (Graph n w) -> [(n,n,w)] edgesD :: (Ix n,Num w) => (Graph n w) -> [(n,n,w)] edgeIn :: (Ix n,Num w) => (Graph n w) -> (n,n) -> Bool weight :: (Ix n,Num w) => n -> n -> (Graph n w) -> w s. [RL99], Ch. 7.2 D. Rösner AuD II 2009 . . . 6 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell Bemerkungen: mkGraph :: (Ix n,Num w) => Bool -> (n,n) -> [(n,n,w)] -> (Graph n w) das erste Argument gibt an, ob der Graph gerichtet ist; ist er ungerichtet, dann werden angegebene Kanten in beiden Richtungen verwendet, sonst nur in der angegebenen Form das zweite Argument gibt die Grenzen des Bereichs der Knoten an das dritte Argument enthält die Kanteninformation als Tripel aus den beiden Knoten und dem Gewicht s. [RL99], Ch. 7.2 D. Rösner AuD II 2009 . . . 7 ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell Graphen 12 1 2 78 34 32 5 44 55 93 3 4 61 Abbildung: Beispiele für Graphen: gewichteter ungerichteter Graph (vgl. [RL99], Fig. 7.1 (c)) D. Rösner AuD II 2009 . . . 8 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell der gewichtete Graph aus [RL99], Fig. 7.1 (c) (s.o.) lässt sich also wie folgt kreieren: mkGraph False (1,5) [(1,2,12),(1,3,34),(1,5,78), (2,4,55),(2,5,32), (3,4,61),(3,5,44), (4,5,93)] s. [RL99], Ch. 7.2 D. Rösner AuD II 2009 . . . 9 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell Implementierung durch Adjazenzlisten falls Liste verwendet wird, um die Knoteninfo zu verwalten: type Graph n w = [(n, [(n,w)])] der Aufwand für den Zugriff auf die Adjazenzliste eines Knotens n ist dann O(|V|) für konstanten Aufwand für den Zugriff auf die Adjazenzliste eines Knotens empfiehlt sich als Alternative ein Array, um die Knoteninfo zu verwalten: type Graph n w = Array n [(n,w)] s. [RL99], Ch. 7.2.2 D. Rösner AuD II 2009 . . . 11 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell Implementierung durch Adjazenzlisten der Graph aus [RL99], Fig. 7.1 (c) (s.o.) lässt sich mit Array-Funktionen direkt wie folgt kreieren: graphAL = array (1,5) [(1,[(2,12),(3,34),(5,78)]), (2,[(1,12),(4,55),(5,32)]), (3,[(1,34),(4,61),(5,44)]), (4,[(2,55),(3,61),(5,93)]), (5,[(1,78),(2,32),(3,44),(4,93)])] weniger aufwändig ist die (ohnehin empfohlene) Nutzung von mkGraph aus dem ADT (s.o.) s. [RL99], Ch. 7.2.2 D. Rösner AuD II 2009 . . . 12 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell Implementierung der Funktionen des ADT Graph durch Adjazenzlisten mkGraph: mkGraph dir bnds es = accumArray (\xs x -> x:xs) [] bnds ([(x1,(x2,w)) | (x1,x2,w) <- es] ++ if dir then [] else [(x2,(x1,w))|(x1,x2,w)<-es,x1/=x2]) s. [RL99], Ch. 7.2.2 D. Rösner AuD II 2009 . . . 13 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell Implementierung der Funktionen des ADT Graph durch Adjazenzlisten adjacent g v = map fst (g!v) nodes g = indices g edgeIn g (x,y) = elem y (adjacent g x) weight x y g = head [ c | (a,c)<-g!x , a==y] edgesD g = [(v1,v2,w)| v1<- nodes g, (v2,w) <-g!v1] edgesU g = [(v1,v2,w)| v1<- nodes g, (v2,w) <-g!v1, v1 < v2] s. [RL99], Ch. 7.2.2 D. Rösner AuD II 2009 . . . 14 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell Implementierung der Funktionen des ADT Graph mit einer Adjazenzmatrix Erinnerung: ob zwischen zwei Knoten eines Graphen eine Kante vorliegt, wird durch einen Eintrag in einer zweidimensionalen quadratischen Matrix repräsentiert die Koordinaten repräsentieren die Knoten und es gilt, dass im Matrixelement (i, j) genau dann ein Eintrag vorliegt, wenn zwischen den Knoten vi und vj eine Kante existiert möglicher Typ: type Graph n w = Array (n,n) w s. [RL99], Ch. 7.2.3 D. Rösner AuD II 2009 . . . 15 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell Implementierung der Funktionen des ADT Graph mit einer Adjazenzmatrix möglicher Typ: type Graph n w = Array (n,n) w zu klären ist, mit welchem Eintrag ausgedrückt wird, dass keine Kante zwischen zwei Knoten vorliegt um die möglichen Werte für Gewichte nicht zu sehr einzuschränken, empfiehlt sich hier die Verwendung des algebraischen Typs Maybe s. [RL99], Ch. 7.2.3 D. Rösner AuD II 2009 . . . 16 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Adjazenzmatrix Einschub: der Typ Maybe a besteht aus den möglichen Werten Nothing und Just a in unserem Fall repräsentiert Nothing dann, dass keine Kante vorhandene Gewichte sind in der Form Just a in der Matrix eingetragen vorliegt damit modifizierter Typ: type Graph n w = Array (n,n) (Maybe w) s. [RL99], Ch. 7.2.3 D. Rösner AuD II 2009 . . . 17 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Adjazenzmatrix damit Implementierung von mkGraph zunächst wird die Matrix mit Nothing vorbelegt (emptyArray) dann werden – mit Unterscheidung zwischen gerichtetem und ungerichtetem Fall – Kantengewichte als Werte der Form Just w eingefügt der Code: mkGraph dir bnds@(l,u) es = emptyArray // ([((x1,x2),Just w) |(x1,x2,w)<-es] ++ if dir then [] else [((x2,x1),Just w) |(x1,x2,w)<-es,x1/=x2]) where emptyArray = array ((l,l),(u,u)) [((x1,x2),Nothing) | x1 <- range bnds, x2 <- range bnds] s. [RL99], Ch. 7.2.3 D. Rösner AuD II 2009 . . . 18 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Adjazenzmatrix Implementierung weiterer Funktionen des ADT Graph adjacent g v1 = [ v2 | v2 <-nodes g,(g!(v1,v2))/= Nothing] nodes g = range (l,u) where ((l,_),(u,_)) = bounds g edgeIn g (x,y)= (g!(x,y)) /= Nothing weight x y g = w where (Just w) = g!(x,y) s. [RL99], Ch. 7.2.3 D. Rösner AuD II 2009 . . . 19 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Adjazenzmatrix Implementierung weiterer Funktionen des ADT Graph edgesD g = [(v1,v2,unwrap(g!(v1,v2))) | v1 <-nodes g, v2 <- nodes g, edgeIn g (v1,v2)] where unwrap (Just w) = w edgesU g = [(v1,v2,unwrap(g!(v1,v2))) | v1 <-nodes g, v2 <- range (v1,u), edgeIn g (v1,v2)] where (_,(u,_)) = bounds g unwrap (Just w) = w s. [RL99], Ch. 7.2.3 D. Rösner AuD II 2009 . . . 20 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Suche die Funktion depthFirstSearch nimmt einen Startknoten und einen Graphen und liefert die Liste der bei einer Tiefensuche erreichten Knoten zurück Variante 1: depthFirstSearch start g = dfs [start] [] where dfs [] vis = vis dfs (c:cs) vis | elem c vis = dfs cs vis | otherwise = dfs ((adjacent g c)++cs) (vis++[c]) s. [RL99], Ch. 7.3 D. Rösner AuD II 2009 . . . 22 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Suche Variante: statt in jedem Schritt die Liste besuchter Knoten zu durchlaufen, um den aktuellen Knoten anzuhängen (also: vis++[c]), wird stattdessen einmal an Ende die Liste besuchter Knoten umgedreht zugehöriger Code: depthFirstSearch’ start g = reverse (dfs [start] []) where dfs [] vis = vis dfs (c:cs) vis | elem c vis = dfs cs vis | otherwise = dfs ((adjacent g c)++cs) (c:vis) s. [RL99], Ch. 7.3 D. Rösner AuD II 2009 . . . 23 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Suche Variante: die Verwaltung der Kandidatenknoten erfolgt mit einem Stack zugehöriger Code: depthFirstSearch’’ start g = reverse (dfs (push start emptyStack) []) where dfs s vis | (stackEmpty s) = vis | elem (top s) vis = dfs (pop s) vis | otherwise = let c = top s in dfs (foldr push (pop s) (adjacent g c)) (c:vis) s. [RL99], Ch. 7.3 D. Rösner AuD II 2009 . . . 24 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Suche bei Breitensuche erfolgt die Verwaltung der Kandidatenknoten mit einer Queue (nach dem FIFO-Prinzip) zugehöriger Code: breadthFirstSearch start g = reverse (bfs (enqueue start emptyQueue) []) where bfs q vis | (queueEmpty q) = vis | elem (front q) vis = bfs (dequeue q) vis | otherwise = let c = front q in bfs (foldr enqueue (dequeue q) (adjacent g c)) (c:vis) s. [RL99], Ch. 7.3 D. Rösner AuD II 2009 . . . 25 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Topologisches Sortieren Vorgehen: der Algorithmus zum topologischen Sortieren arbeitet – wie Tiefensuche und Breitensuche – mit den beiden Listen der Kandidatenknoten und der besuchten Knoten die Aktualisierung geschieht wie folgt: Kandidaten: (c:cs) → cs besucht: vis → c:(tsort (adjacent g c) vis) m.a.W.: wenn ein Knoten als besucht eingereiht wird, geschieht dies vor seinen (rekursiv) topologisch sortierten adjazenten Knoten die initiale Liste von Kandidaten ergibt sich aus den Knoten ohne vorausgehende Knoten s. [RL99], Ch. 7.4 D. Rösner AuD II 2009 . . . 27 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Topologisches Sortieren das komplette Programm inDegree g n = length [t | v<-nodes g, t<-adjacent g v, (n==t)] topologicalSort g = tsort [n | n<-nodes g , (inDegree g n == 0)] [] where tsort [] r = r tsort (c:cs) vis | elem c vis = tsort cs vis | otherwise = tsort cs (c:(tsort (adjacent g c) vis)) s. [RL99], Ch. 7.4 D. Rösner AuD II 2009 . . . 28 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Topologisches Sortieren Anwendung auf einen Graph mit Information zu Lehrveranstaltungen data Courses = Maths | Theory | Languages | Programming | Concurrency| Architecture | Parallelism deriving (Eq,Ord,Enum,Ix,Show) cg = mkGraph True (Maths,Parallelism) [(Maths,Theory,1), (Languages,Theory,1), (Programming,Languages,1), (Programming,Concurrency,1), (Concurrency,Parallelism,1), (Architecture,Parallelism,1)] s. [RL99], Ch. 7.4 D. Rösner AuD II 2009 . . . 29 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Graphen in Haskell: Topologisches Sortieren Anwendung auf einen Graph mit Information zu Lehrveranstaltungen ? topologicalSort cg [Architecture, Programming, Concurrency, Parallelism, Languages, Maths, Theory] s. [RL99], Ch. 7.4 D. Rösner AuD II 2009 . . . 30 Graphen in Haskell ADT Graph Implementierung Suche Topologisches Sortieren Literatur: I Fethi Rabhi and Guy Lapalme. Algorithms – A Functional Programming Approach. Pearson Education Ltd., Essex, 1999. 2nd edition, ISBN 0-201-59604-0. D. Rösner AuD II 2009 . . . 31