Dies ist ein Follow-up zu meiner previous question über die Verarbeitung einer Vektordarstellung eines 5,1 m Kanten gerichteten Graphen. Ich versuche, den Grafikalgorithmus von Kosaraju zu implementieren und muss daher meinen Vector in der Reihenfolge der Endzeiten einer Tiefensuche (DFS) an den umgekehrten Kanten neu anordnen. Ich habe Code, der auf kleinen Datensätzen läuft, aber nicht in 10 Minuten auf den gesamten Datensatz zurückkehrt. (Ich kann nicht ausschließen, dass eine Schleife aus dem großen Graphen entsteht, aber es gibt keine Anzeichen dafür auf meinen Testdaten.)Optimierung der Manipulation von großen Vektoren
DFS muss vermeiden, Knoten erneut zu besuchen, also brauche ich eine Art "Zustand" für die Suche (derzeit ein Tupel, sollte ich ein State Monad verwenden?). Die erste Suche sollte einen neu geordneten Vektor zurückgeben, aber ich behalte die Dinge derzeit einfach, indem ich eine Liste der neu angeordneten Knotenindizes zurückgebe, so dass ich den Vektor anschließend in einem Durchgang verarbeiten kann.
Ich nehme an, das Problem liegt in dfsInner
. Der Code unter "merkt sich" die besuchten Knoten, indem er das erkundete Feld jedes Knotens aktualisiert (dritter Wächter). Obwohl ich versuchte, es rekursiv zu machen, scheint der Code den Speicherverbrauch ziemlich schnell zu erhöhen. Muss ich einige Strenge erzwingen und wenn ja, wie? (Ich habe eine andere Version, die ich bei einer einzigen Suchsuche verwende, die nach früheren Besuchen sucht, indem ich die Startknoten der unerforschten Kanten auf dem Stapel und die Liste der Knoten, die abgeschlossen wurden, betrachte. Dies wächst nicht so schnell, aber kommt für keinen gut verbundenen Knoten zurück.)
Es könnte aber auch die foldr'
sein, aber wie kann ich das erkennen?
Dies ist angeblich Coursera Hausaufgaben, aber ich bin nicht mehr sicher, ob ich den Ehrenkode Button ankreuzen kann! Das Lernen ist jedoch wichtiger, daher möchte ich keine Copy/Paste-Antwort. Was ich habe, ist nicht sehr elegant - es hat auch ein imperatives Gefühl, das von dem Problem getrieben wird, eine Art Staat zu halten - siehe dritte Wache. Ich würde gerne Kommentare zu Entwurfsmustern begrüßen.
type NodeName = Int
type Edges = [NodeName]
type Explored = Bool
type Stack = [(Int, Int)]
data Node = Node NodeName Explored Edges Edges deriving (Eq, Show)
type Graph = Vector Node
main = do
edges <- V.fromList `fmap` getEdges "SCC.txt"
let
maxIndex = fst $ V.last edges
gr = createGraph maxIndex edges
res = dfsOuter gr
--return gr
putStrLn $ show res
dfsOuter gr =
let tmp = V.foldr' callInner (gr,[]) gr
in snd tmp
callInner :: Node -> (Graph, Stack) -> (Graph, Stack)
callInner (Node idx _ fwd bwd) (gr,acc) =
let (Node _ explored _ _) = gr V.! idx
in case explored of
True -> (gr, acc)
False ->
let
initialStack = map (\l -> (idx, l)) bwd
gr' = gr V.// [(idx, Node idx True fwd bwd)]
(gr'', newScc) = dfsInner idx initialStack (length acc) (gr', [])
in (gr'', newScc++acc)
dfsInner :: NodeName -> Stack -> Int -> (Graph, [(Int, Int)]) -> (Graph, [(Int, Int)])
dfsInner start [] finishCounter (gr, acc) = (gr, (start, finishCounter):acc)
dfsInner start stack finishCounter (gr, acc)
| nextStart /= start = -- no more places to go from this node
dfsInner nextStart stack (finishCounter + 1) $ (gr, (start, finishCounter):acc)
| nextExplored =
-- nextExplored || any (\(y,_) -> y == stack0Head) stack || any (\(x,_) -> x == stack0Head) acc =
dfsInner start (tail stack) finishCounter (gr, acc)
| otherwise =
dfsInner nextEnd (add2Stack++stack) finishCounter (gr V.// [(nextEnd, Node idx True nextLHS nextRHS)], acc)
-- dfsInner gr stack0Head (add2Stack++stack) finishCounter acc
where
(nextStart, nextEnd) = head stack
(Node idx nextExplored nextLHS nextRHS) = gr V.! nextEnd
add2Stack = map (\l -> (nextEnd, l)) nextRHS
Vielleicht ist es keine gute Idee, das besuchte Set im Graphen selbst zu speichern. Ich bezweifle, dass Vektoraktualisierungen während des Markierens des Knotens während der Rekursion zusammengefügt werden, da Sie bei jedem Schritt einen zufälligen Zugriff vornehmen, so dass er tatsächlich konstruiert werden musste. – Piezoid
Ich machte mir darüber Sorgen, aber der einzige andere Weg, den ich finden konnte, um die besuchten Knoten zu identifizieren, sah ebenfalls sehr langsam aus (siehe Zeilen in den Kommentaren, die gerade hinzugefügt wurden) –
Sie können versuchen, veränderbare Vektoren zu verwenden. In diesem Fall würde ich eine Codierung wie 'data Graph = Graph (Vektorkanten) (Vector Edges)' und 'Context s = MVector Explored' empfehlen (siehe [" Struktur von Arrays "vs." Array von Strukturen "] (https://www.fpcomplete.com/user/edwardk/revisiting-matrix-multiplication/part-3#unboxed-vectors)). Die andere Möglichkeit besteht darin, eine 'Set'-ähnliche Struktur mit State-Monade zu verwenden, wie in [Strukturierungstiefe-erste Suchalgorithmen in Haskell] (http://www.researchgate.net/publication/2252048_Structuring_Depth-First_Search_Algorithms_in_Haskell/file/50463523c7a64b12d4). pdf) (Abschnitt 5) – Piezoid