Copyright | (c) Andrey Mokhov 2016-2022 |
---|---|
License | MIT (see the file LICENSE) |
Maintainer | andrey.mokhov@gmail.com |
Stability | unstable |
Safe Haskell | None |
Language | Haskell2010 |
Alga is a library for algebraic construction and manipulation of graphs in Haskell. See this paper for the motivation behind the library, the underlying theory, and implementation details.
This module provides basic graph algorithms, such as depth-first search, implemented for the Algebra.Graph.AdjacencyMap data type.
Synopsis
- bfsForest :: Ord a => AdjacencyMap a -> [a] -> Forest a
- bfs :: Ord a => AdjacencyMap a -> [a] -> [[a]]
- dfsForest :: Ord a => AdjacencyMap a -> Forest a
- dfsForestFrom :: Ord a => AdjacencyMap a -> [a] -> Forest a
- dfs :: Ord a => AdjacencyMap a -> [a] -> [a]
- reachable :: Ord a => AdjacencyMap a -> a -> [a]
- topSort :: Ord a => AdjacencyMap a -> Either (Cycle a) [a]
- isAcyclic :: Ord a => AdjacencyMap a -> Bool
- scc :: Ord a => AdjacencyMap a -> AdjacencyMap (AdjacencyMap a)
- isDfsForestOf :: Ord a => Forest a -> AdjacencyMap a -> Bool
- isTopSortOf :: Ord a => [a] -> AdjacencyMap a -> Bool
- type Cycle = NonEmpty
Algorithms
bfsForest :: Ord a => AdjacencyMap a -> [a] -> Forest a Source #
Compute the breadth-first search forest of a graph, such that adjacent
vertices are explored in increasing order according to their Ord
instance.
The search is seeded by a list of vertices that will become the roots of the
resulting forest. Duplicates in the list will have their first occurrence
explored and subsequent ones ignored. The seed vertices that do not belong to
the graph are also ignored.
Complexity: O((L + m) * log n) time and O(n) space, where L is the number of seed vertices.
forest
$ bfsForest (edge
1 2) [0] ==empty
forest
$ bfsForest (edge
1 2) [1] ==edge
1 2forest
$ bfsForest (edge
1 2) [2] ==vertex
2forest
$ bfsForest (edge
1 2) [0,1,2] ==vertices
[1,2]forest
$ bfsForest (edge
1 2) [2,1,0] ==vertices
[1,2]forest
$ bfsForest (edge
1 1) [1] ==vertex
1isSubgraphOf
(forest
$ bfsForest x vs) x == True bfsForest x (vertexList
x) ==map
(\v -> Node v []) (nub
$vertexList
x) bfsForest x [] == [] bfsForestempty
vs == [] bfsForest (3 * (1 + 4) * (1 + 5)) [1,4] == [ Node { rootLabel = 1 , subForest = [ Node { rootLabel = 5 , subForest = [] }]} , Node { rootLabel = 4 , subForest = [] }]forest
$ bfsForest (circuit
[1..5] +circuit
[5,4..1]) [3] ==path
[3,2,1] +path
[3,4,5]
bfs :: Ord a => AdjacencyMap a -> [a] -> [[a]] Source #
A version of bfsForest
where the resulting forest is converted to a level
structure. Adjacent vertices are explored in the increasing order according
to their Ord
instance. Flattening the result via concat
.
bfs
x
gives an enumeration of reachable vertices in the breadth-first search order.
Complexity: O((L + m) * min(n,W)) time and O(n) space, where L is the number of seed vertices.
bfs (edge
1 2) [0] == [] bfs (edge
1 2) [1] == [[1], [2]] bfs (edge
1 2) [2] == [[2]] bfs (edge
1 2) [1,2] == [[1,2]] bfs (edge
1 2) [2,1] == [[2,1]] bfs (edge
1 1) [1] == [[1]] bfsempty
vs == [] bfs x [] == [] bfs (1 * 2 + 3 * 4 + 5 * 6) [1,2] == [[1,2]] bfs (1 * 2 + 3 * 4 + 5 * 6) [1,3] == [[1,3], [2,4]] bfs (3 * (1 + 4) * (1 + 5)) [3] == [[3], [1,4,5]] bfs (circuit
[1..5] +circuit
[5,4..1]) [3] == [[2], [1,3], [5,4]]concat
$ bfs (circuit
[1..5] +circuit
[5,4..1]) [3] == [3,2,4,1,5]map
concat
.transpose
.map
levels
.bfsForest
x == bfs x
dfsForest :: Ord a => AdjacencyMap a -> Forest a Source #
Compute the depth-first search forest of a graph, where adjacent vertices
are explored in the increasing order according to their Ord
instance.
Complexity: O((n + m) * min(n,W)) time and O(n) space.
forest
$ dfsForestempty
==empty
forest
$ dfsForest (edge
1 1) ==vertex
1forest
$ dfsForest (edge
1 2) ==edge
1 2forest
$ dfsForest (edge
2 1) ==vertices
[1,2]isSubgraphOf
(forest
$ dfsForest x) x == TrueisDfsForestOf
(dfsForest x) x == True dfsForest .forest
. dfsForest == dfsForest dfsForest (vertices
vs) ==map
(\v -> Node v []) (nub
$sort
vs) dfsForest $ 3 * (1 + 4) * (1 + 5) == [ Node { rootLabel = 1 , subForest = [ Node { rootLabel = 5 , subForest = [] }]} , Node { rootLabel = 3 , subForest = [ Node { rootLabel = 4 , subForest = [] }]}]forest
(dfsForest $circuit
[1..5] +circuit
[5,4..1]) ==path
[1,2,3,4,5]
dfsForestFrom :: Ord a => AdjacencyMap a -> [a] -> Forest a Source #
Compute the depth-first search forest of a graph starting from the given
seed vertices, where adjacent vertices are explored in the increasing order
according to their Ord
instance. Note that the resulting forest does not
necessarily span the whole graph, as some vertices may be unreachable. The
seed vertices which do not belong to the graph are ignored.
Complexity: O((L + m) * log n) time and O(n) space, where L is the number of seed vertices.
forest
$ dfsForestFromempty
vs ==empty
forest
$ dfsForestFrom (edge
1 1) [1] ==vertex
1forest
$ dfsForestFrom (edge
1 2) [0] ==empty
forest
$ dfsForestFrom (edge
1 2) [1] ==edge
1 2forest
$ dfsForestFrom (edge
1 2) [2] ==vertex
2forest
$ dfsForestFrom (edge
1 2) [1,2] ==edge
1 2forest
$ dfsForestFrom (edge
1 2) [2,1] ==vertices
[1,2]isSubgraphOf
(forest
$ dfsForestFrom x vs) x == TrueisDfsForestOf
(dfsForestFrom x (vertexList
x)) x == True dfsForestFrom x (vertexList
x) ==dfsForest
x dfsForestFrom x [] == [] dfsForestFrom (3 * (1 + 4) * (1 + 5)) [1,4] == [ Node { rootLabel = 1 , subForest = [ Node { rootLabel = 5 , subForest = [] } , Node { rootLabel = 4 , subForest = [] }]forest
$ dfsForestFrom (circuit
[1..5] +circuit
[5,4..1]) [3] ==path
[3,2,1,5,4]
dfs :: Ord a => AdjacencyMap a -> [a] -> [a] Source #
Return the list vertices visited by the depth-first search in a graph,
starting from the given seed vertices. Adjacent vertices are explored in the
increasing order according to their Ord
instance.
Complexity: O((L + m) * log n) time and O(n) space, where L is the number of seed vertices.
dfsempty
vs == [] dfs (edge
1 1) [1] == [1] dfs (edge
1 2) [0] == [] dfs (edge
1 2) [1] == [1,2] dfs (edge
1 2) [2] == [2] dfs (edge
1 2) [1,2] == [1,2] dfs (edge
1 2) [2,1] == [2,1] dfs x [] == []and
[hasVertex
v x | v <- dfs x vs ] == True dfs (3 * (1 + 4) * (1 + 5)) [1,4] == [1,5,4] dfs (circuit
[1..5] +circuit
[5,4..1]) [3] == [3,2,1,5,4]
reachable :: Ord a => AdjacencyMap a -> a -> [a] Source #
Return the list of vertices reachable from a source vertex in a graph. The vertices in the resulting list appear in the depth-first search order.
Complexity: O(m * log n) time and O(n) space.
reachableempty
x == [] reachable (vertex
1) 1 == [1] reachable (edge
1 1) 1 == [1] reachable (edge
1 2) 0 == [] reachable (edge
1 2) 1 == [1,2] reachable (edge
1 2) 2 == [2] reachable (path
[1..8] ) 4 == [4..8] reachable (circuit
[1..8] ) 4 == [4..8] ++ [1..3] reachable (clique
[8,7..1]) 8 == [8] ++ [1..7]and
[hasVertex
v x | v <- reachable x y ] == True
topSort :: Ord a => AdjacencyMap a -> Either (Cycle a) [a] Source #
Compute a topological sort of a graph or discover a cycle.
Vertices are explored in the decreasing order according to their Ord
instance. This gives the lexicographically smallest topological ordering in
the case of success. In the case of failure, the cycle is characterized by
being the lexicographically smallest up to rotation with respect to
Ord
(Dual
Int)
in the first connected component of the graph containing
a cycle, where the connected components are ordered by their largest vertex
with respect to Ord a
.
Complexity: O((n + m) * min(n,W)) time and O(n) space.
topSort (1 * 2 + 3 * 1) == Right [3,1,2] topSort (path
[1..5]) == Right [1..5] topSort (3 * (1 * 4 + 2 * 5)) == Right [3,1,2,4,5] topSort (1 * 2 + 2 * 1) == Left (2:|
[1]) topSort (path
[5,4..1] +edge
2 4) == Left (4:|
[3,2]) topSort (circuit
[1..3]) == Left (3:|
[1,2]) topSort (circuit
[1..3] +circuit
[3,2,1]) == Left (3:|
[2]) topSort (1 * 2 + (5 + 2) * 1 + 3 * 4 * 3) == Left (1:|
[2]) fmap (flip
isTopSortOf
x) (topSort x) /= Right FalseisRight
. topSort ==isAcyclic
topSort .vertices
== Right .nub
.sort
scc :: Ord a => AdjacencyMap a -> AdjacencyMap (AdjacencyMap a) Source #
Compute the condensation of a graph, where each vertex corresponds to a strongly-connected component of the original graph. Note that component graphs are non-empty, and are therefore of type Algebra.Graph.NonEmpty.AdjacencyMap.
Details about the implementation can be found at gabow-notes.
Complexity: O((n+m)*log n) time and O(n+m) space.
sccempty
==empty
scc (vertex
x) ==vertex
(NonEmpty.vertex
x) scc (vertices
xs) ==vertices
(map
vertex
xs) scc (edge
1 1) ==vertex
(NonEmpty.edge
1 1) scc (edge
1 2) ==edge
(NonEmpty.vertex
1) (NonEmpty.vertex
2) scc (circuit
(1:xs)) ==vertex
(NonEmpty.circuit1
(1:|
xs)) scc (3 * 1 * 4 * 1 * 5) ==edges
[ (NonEmpty.vertex
3 , NonEmpty.vertex
5 ) , (NonEmpty.vertex
3 , NonEmpty.clique1
[1,4,1]) , (NonEmpty.clique1
[1,4,1], NonEmpty.vertex
5 ) ]isAcyclic
. scc ==const
TrueisAcyclic
x == (scc x ==gmap
NonEmpty.vertex
x)
Correctness properties
isDfsForestOf :: Ord a => Forest a -> AdjacencyMap a -> Bool Source #
Check if a given forest is a correct depth-first search forest of a graph. The implementation is based on the paper "Depth-First Search and Strong Connectivity in Coq" by François Pottier.
isDfsForestOf []empty
== True isDfsForestOf [] (vertex
1) == False isDfsForestOf [Node 1 []] (vertex
1) == True isDfsForestOf [Node 1 []] (vertex
2) == False isDfsForestOf [Node 1 [], Node 1 []] (vertex
1) == False isDfsForestOf [Node 1 []] (edge
1 1) == True isDfsForestOf [Node 1 []] (edge
1 2) == False isDfsForestOf [Node 1 [], Node 2 []] (edge
1 2) == False isDfsForestOf [Node 2 [], Node 1 []] (edge
1 2) == True isDfsForestOf [Node 1 [Node 2 []]] (edge
1 2) == True isDfsForestOf [Node 1 [], Node 2 []] (vertices
[1,2]) == True isDfsForestOf [Node 2 [], Node 1 []] (vertices
[1,2]) == True isDfsForestOf [Node 1 [Node 2 []]] (vertices
[1,2]) == False isDfsForestOf [Node 1 [Node 2 [Node 3 []]]] (path
[1,2,3]) == True isDfsForestOf [Node 1 [Node 3 [Node 2 []]]] (path
[1,2,3]) == False isDfsForestOf [Node 3 [], Node 1 [Node 2 []]] (path
[1,2,3]) == True isDfsForestOf [Node 2 [Node 3 []], Node 1 []] (path
[1,2,3]) == True isDfsForestOf [Node 1 [], Node 2 [Node 3 []]] (path
[1,2,3]) == False
isTopSortOf :: Ord a => [a] -> AdjacencyMap a -> Bool Source #
Check if a given list of vertices is a correct topological sort of a graph.
isTopSortOf [3,1,2] (1 * 2 + 3 * 1) == True isTopSortOf [1,2,3] (1 * 2 + 3 * 1) == False isTopSortOf [] (1 * 2 + 3 * 1) == False isTopSortOf []empty
== True isTopSortOf [x] (vertex
x) == True isTopSortOf [x] (edge
x x) == False