From d650feaf41ec5d84f8d2917be10af290962ea568 Mon Sep 17 00:00:00 2001 From: Wilf Wilson Date: Wed, 27 Oct 2021 13:58:13 +0100 Subject: [PATCH] Re-add PR #459: ExecuteDFS (generic depth first search) --- Makefile.am | 2 + doc/oper.xml | 169 +++++++++++++++++++++++++++++- doc/z-chap4.xml | 3 + gap/attr.gi | 237 +++++++++++++++++++++++------------------- gap/grahom.gi | 2 +- gap/oper.gd | 5 + gap/oper.gi | 221 +++++++++++++++++++++++++++------------ gap/prop.gi | 55 +++++++++- src/dfs.c | 124 ++++++++++++++++++++++ src/dfs.h | 21 ++++ src/digraphs.c | 9 +- tst/standard/oper.tst | 130 ++++++++++++++++++++++- tst/standard/prop.tst | 8 ++ 13 files changed, 803 insertions(+), 183 deletions(-) create mode 100644 src/dfs.c create mode 100644 src/dfs.h diff --git a/Makefile.am b/Makefile.am index 2927390d7..3ef5f57a2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,6 +31,7 @@ pkginclude_HEADERS += src/cliques.h pkginclude_HEADERS += src/perms.h pkginclude_HEADERS += src/planar.h pkginclude_HEADERS += src/schreier-sims.h +pkginclude_HEADERS += src/dfs.h if WITH_INCLUDED_BLISS pkginclude_HEADERS += extern/bliss-0.73/bignum.hh @@ -56,6 +57,7 @@ digraphs_la_SOURCES += src/homos-graphs.c digraphs_la_SOURCES += src/perms.c digraphs_la_SOURCES += src/planar.c digraphs_la_SOURCES += src/schreier-sims.c +digraphs_la_SOURCES += src/dfs.c if WITH_INCLUDED_BLISS digraphs_la_SOURCES += extern/bliss-0.73/defs.cc diff --git a/doc/oper.xml b/doc/oper.xml index 54d8d0ed7..7ba411a9a 100644 --- a/doc/oper.xml +++ b/doc/oper.xml @@ -1321,7 +1321,7 @@ gap> D := CompleteDigraph(5); gap> VerticesReachableFrom(D, 1); [ 2, 1, 3, 4, 5 ] gap> VerticesReachableFrom(D, 3); -[ 1, 2, 3, 4, 5 ] +[ 1, 3, 2, 4, 5 ] gap> D := EmptyDigraph(5); gap> VerticesReachableFrom(D, 1); @@ -2126,6 +2126,173 @@ gap> LexicographicProduct(NullDigraph(0), CompleteDigraph(10)); <#/GAPDoc> +<#GAPDoc Label="NewDFSRecord"> + + + A record. + + This record contains three lists (parent, preorder and postorder) with their length + equal to the number of verticies in the digraph. Each index of the lists maps to the + vertex within the digraph equating to the vertex number. These lists store + the following: + + parent + at each index, the parent of the vertex is stored + preorder + at each index, the preorder number (order in which the vertex is visited) + is stored + postorder + at each index, the postorder number (order in which the vertex is backtracked on) + is stored + + + The record also stores a further 4 attributes. + + current + the current vertex that is being visited + child + the child of the current vertex + graph + the digraph + stop + whether to stop the depth first search + + + Initially, the current and child attributes will have -1 values and the lists (parent, + preorder and postorder) will have -1 values at all of their indicies as no vertex has + been visited. The stop attribute will initially be false. + This record should be passed into the ExecuteDFS function. + See . + record := NewDFSRecord(CompleteDigraph(2)); +rec( child := -1, current := -1, + graph := , + parent := [ -1, -1 ], postorder := [ -1, -1 ], + preorder := [ -1, -1 ], stop := false ) +gap> record.preorder; +[ -1, -1 ] +gap> record.postorder; +[ -1, -1 ] +gap> record.stop; +false +gap> record.parent; +[ -1, -1 ] +gap> record.child; +-1 +gap> record.current; +-1 +gap> record.graph; + +]]> + + +<#/GAPDoc> + +<#GAPDoc Label="DFSDefault"> + + + + This is a default function to be passed into the ExecuteDFS function. + This does nothing and can be used in place of the PreOrderFunc, PostOrderFunc, + AncestorFunc and/or CrossFunc of the ExecuteDFS function. + See . + PreOrderFunc := function(record, data) +> data.num_vertices := data.num_vertices + 1; +> end;; +gap> record := NewDFSRecord(CompleteDigraph(2));; +gap> data := rec(num_vertices := 0);; +gap> ExecuteDFS(record, data, 1, PreOrderFunc, +> DFSDefault, DFSDefault, DFSDefault); +gap> data; +rec( num_vertices := 2 ) +gap> record := NewDFSRecord(CompleteDigraph(2));; +gap> ExecuteDFS(record, [], 1, DFSDefault, +> DFSDefault, DFSDefault, DFSDefault); +gap> record; +rec( child := 1, current := 1, + graph := , + parent := [ 1, 1 ], postorder := [ 2, 1 ], preorder := [ 1, 2 ], + stop := false ) +]]> + + +<#/GAPDoc> + +<#GAPDoc Label="ExecuteDFS"> + + + + This performs a full depth first search from the start vertex (where start is a vertex within the graph). + The depth first search can be terminated by changing the record.stop attribute to true in the + PreOrderFunc, PostOrderFunc, AncestorFunc or CrossFunc functions. + ExecuteDFS takes 7 arguments: + + record + the depth first search record (created using NewDFSRecord) + data + an object that you want to manipulate in the functions passed. + start + the vertex where we begin the depth first search. + PreOrderFunc + this function is called when a vertex is first visited. This vertex + is stored in record.current + PostOrderFunc + this function is called when a vertex has no more unvisited children + causing us to backtrack. This vertex is stored in record.child and its parent is stored + in record.current + AncestorFunc + this function is called when (record.current, + record.child) is an edge and record.child is an ancestor of record.current. An ancestor here means that + record.child is on the same branch as record.current but was visited prior to record.current + CrossFunc + this function is called when (record.current, + record.child) is an edge and record.child has been visited before record.current + and it is not an ancestor of record.current + + Note that this function only performs a depth first search on the vertices reachable from start. + It is also important to note that all functions passed need to accept arguments record and data. + Finally, for the start vertex, its parent is itself and the PreOrderFunc + will be called on it. + See . + record := NewDFSRecord(CycleDigraph(10));; +gap> ExecuteDFS(record, [], 1, DFSDefault, +> DFSDefault, DFSDefault, DFSDefault); +gap> record.preorder; +[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] +gap> record := NewDFSRecord(CompleteDigraph(10));; +gap> data := rec(cycle_vertex := 0);; +gap> AncestorFunc := function(record, data) +> record.stop := true; +> data.cycle_vertex := record.child; +> end;; +gap> ExecuteDFS(record, data, 1, DFSDefault, +> DFSDefault, AncestorFunc, DFSDefault); +gap> record.stop; +true +gap> data.cycle_vertex; +1 +gap> record.preorder; +[ 1, 2, 3, -1, -1, -1, -1, -1, -1, -1 ] +gap> record := NewDFSRecord(Digraph([[2, 3], [4], [5], [], [4]]));; +gap> CrossFunc := function(record, data) +> record.stop := true; +> Add(data, record.child); +> end;; +gap> data := [];; +gap> ExecuteDFS(record, data, 1, DFSDefault, +> DFSDefault, DFSDefault, CrossFunc); +gap> record.stop; +true +gap> data; +[ 4 ] +]]> + + +<#/GAPDoc> + <#GAPDoc Label="IsDigraphPath"> diff --git a/doc/z-chap4.xml b/doc/z-chap4.xml index 00224832d..268ca3d79 100644 --- a/doc/z-chap4.xml +++ b/doc/z-chap4.xml @@ -18,6 +18,9 @@ <#Include Label="IsMatching"> <#Include Label="DigraphMaximalMatching"> <#Include Label="DigraphMaximumMatching"> + <#Include Label="NewDFSRecord"> + <#Include Label="DFSDefault"> + <#Include Label="ExecuteDFS">
Neighbours and degree diff --git a/gap/attr.gi b/gap/attr.gi index b18f8a891..5b1b54e94 100644 --- a/gap/attr.gi +++ b/gap/attr.gi @@ -13,23 +13,10 @@ InstallMethod(DigraphNrVertices, "for a digraph by out-neighbours", InstallGlobalFunction(OutNeighbors, OutNeighbours); -# The next method is (yet another) DFS which simultaneously computes: -# 1. *articulation points* as described in -# https://www.eecs.wsu.edu/~holder/courses/CptS223/spr08/slides/graphapps.pdf -# 2. *bridges* as described in https://stackoverflow.com/q/28917290/ -# (this is a minor adaption of the algorithm described in point 1). -# 3. a *strong orientation* as alluded to somewhere on the internet that I can -# no longer find. It's essentially just "orient every edge in the DFS tree -# away from the root, and every other edge (back edges) from the node with -# higher `pre` value to the one with lower `pre` value (i.e. they point -# backwards from later nodes in the DFS to earlier ones). If the graph is -# bridgeless, then it is guaranteed that the orientation of the last -# sentence is strongly connected." - BindGlobal("DIGRAPHS_ArticulationPointsBridgesStrongOrientation", function(D) - local N, copy, articulation_points, bridges, orientation, nbs, counter, pre, - low, nr_children, stack, u, v, i, w, connected; + local N, copy, PostOrderFunc, PreOrderFunc, AncestorCrossFunc, data, record, + connected; N := DigraphNrVertices(D); @@ -41,115 +28,109 @@ function(D) # the graph disconnected), no bridges, strong orientation (since # the digraph with 0 nodes is strongly connected). return [true, [], [], D]; - elif not IsSymmetricDigraph(D) then - copy := DigraphSymmetricClosure(DigraphMutableCopyIfMutable(D)); + elif not IsSymmetricDigraph(D) or IsMultiDigraph(D) then + copy := DigraphSymmetricClosure(DigraphMutableCopy(D)); + copy := DigraphRemoveAllMultipleEdges(copy); MakeImmutable(copy); else copy := D; fi; - # outputs - articulation_points := []; - bridges := []; - orientation := List([1 .. N], x -> BlistList([1 .. N], [])); + PostOrderFunc := function(record, data) + local child, current; + child := record.child; + current := record.parent[child]; + if record.preorder[child] > record.preorder[current] then + # stops the duplication of articulation_points + if current <> 1 and data.low[child] >= record.preorder[current] then + Add(data.articulation_points, current); + fi; + if data.low[child] = record.preorder[child] then + Add(data.bridges, [current, child]); + fi; + if data.low[child] < data.low[current] then + data.low[current] := data.low[child]; + fi; + fi; + end; - # Get out-neighbours once, to avoid repeated copying for mutable digraphs. - nbs := OutNeighbours(copy); + PreOrderFunc := function(record, data) + local current, parent; + current := record.current; + if current <> 1 then + parent := record.parent[current]; + if parent = 1 then + data.nr_children := data.nr_children + 1; + fi; + data.orientation[parent][current] := true; + fi; + data.counter := data.counter + 1; + data.low[current] := data.counter; + end; - # number of nodes encountered in the search so far - counter := 0; + AncestorCrossFunc := function(record, data) + local current, child, parent; + current := record.current; + child := record.child; + parent := record.parent[current]; + # current -> child is a back edge + if child <> parent and record.preorder[child] < data.low[current] then + data.low[current] := record.preorder[child]; + fi; + data.orientation[current][child] := not data.orientation[child][current]; + end; + + data := rec(); - # the order in which the nodes are visited, -1 indicates "not yet visited". - pre := ListWithIdenticalEntries(N, -1); + # outputs + data.articulation_points := []; + data.bridges := []; + data.orientation := List([1 .. N], x -> BlistList([1 .. N], [])); # low[i] is the lowest value in pre currently reachable from node i. - low := []; + data.low := []; + + # number of nodes encountered in the search so far + data.counter := 0; # nr_children of node 1, for articulation points the root node (1) is an # articulation point if and only if it has at least 2 children. - nr_children := 0; - - stack := Stack(); - u := 1; - v := 1; - i := 0; - - repeat - if pre[v] <> -1 then - # backtracking - i := Pop(stack); - v := Pop(stack); - u := Pop(stack); - w := nbs[v][i]; - - if v <> 1 and low[w] >= pre[v] then - Add(articulation_points, v); - fi; - if low[w] = pre[w] then - Add(bridges, [v, w]); - fi; - if low[w] < low[v] then - low[v] := low[w]; - fi; - else - # diving - part 1 - counter := counter + 1; - pre[v] := counter; - low[v] := counter; - fi; - i := PositionProperty(nbs[v], w -> w <> v, i); - while i <> fail do - w := nbs[v][i]; - if pre[w] <> -1 then - # v -> w is a back edge - if w <> u and pre[w] < low[v] then - low[v] := pre[w]; - fi; - orientation[v][w] := not orientation[w][v]; - i := PositionProperty(nbs[v], w -> w <> v, i); - else - # diving - part 2 - if v = 1 then - nr_children := nr_children + 1; - fi; - orientation[v][w] := true; - Push(stack, u); - Push(stack, v); - Push(stack, i); - u := v; - v := w; - i := 0; - break; - fi; - od; - until Size(stack) = 0; - - if counter = DigraphNrVertices(D) then + data.nr_children := 0; + + record := NewDFSRecord(copy); + ExecuteDFS(record, + data, + 1, + PreOrderFunc, + PostOrderFunc, + AncestorCrossFunc, + AncestorCrossFunc); + if data.counter = DigraphNrVertices(D) then connected := true; - if nr_children > 1 then - Add(articulation_points, 1); + if data.nr_children > 1 then + Add(data.articulation_points, 1); fi; - if not IsEmpty(bridges) then - orientation := fail; + if not IsEmpty(data.bridges) then + data.orientation := fail; else - orientation := DigraphByAdjacencyMatrix(DigraphMutabilityFilter(D), - orientation); + data.orientation := DigraphByAdjacencyMatrix(DigraphMutabilityFilter(D), + data.orientation); fi; else - connected := false; - articulation_points := []; - bridges := []; - orientation := fail; + connected := false; + data.articulation_points := []; + data.bridges := []; + data.orientation := fail; fi; if IsImmutableDigraph(D) then SetIsConnectedDigraph(D, connected); - SetArticulationPoints(D, articulation_points); - SetBridges(D, bridges); + SetArticulationPoints(D, data.articulation_points); + SetBridges(D, data.bridges); if IsSymmetricDigraph(D) then - SetStrongOrientationAttr(D, orientation); + SetStrongOrientationAttr(D, data.orientation); fi; fi; - return [connected, articulation_points, bridges, orientation]; + return [connected, data.articulation_points, data.bridges, data.orientation]; end); InstallMethod(ArticulationPoints, "for a digraph by out-neighbours", @@ -702,7 +683,37 @@ end); InstallMethod(DigraphTopologicalSort, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], -D -> DIGRAPH_TOPO_SORT(OutNeighbours(D))); +function(D) + local i, record, num_vertices, data, AncestorFunc, PostOrderFunc; + if DigraphNrVertices(D) = 0 then + return []; + fi; + record := NewDFSRecord(D); + num_vertices := DigraphNrVertices(D); + data := rec(count := 0, + out := ListWithIdenticalEntries(num_vertices, 0)); + AncestorFunc := function(record, data) + if record.current <> record.child then + record.stop := true; + fi; + end; + PostOrderFunc := function(record, data) + data.count := data.count + 1; + data.out[data.count] := record.child; + end; + for i in DigraphVertices(D) do + if record.preorder[i] <> -1 then + continue; + fi; + ExecuteDFS(record, data, i, DFSDefault, + PostOrderFunc, AncestorFunc, + DFSDefault); + if record.stop then + return fail; + fi; + od; + return data.out; +end); InstallMethod(DigraphStronglyConnectedComponents, "for a digraph by out-neighbours", @@ -988,7 +999,7 @@ function(D, v) # # localParameters is a list of 3-tuples [a_{i - 1}, b_{i - 1}, c_{i - 1}] for # each i between 1 and localDiameter where c_i (respectively a_i and b_i) is - # the number of vertices at distance i − 1 (respectively i and i + 1) from v + # the number of vertices at distance i - 1 (respectively i and i + 1) from v # that are adjacent to a vertex w at distance i from v. # gives a shortest path spanning tree rooted at and is used by @@ -2267,18 +2278,30 @@ InstallMethod(DigraphReflexiveTransitiveReductionAttr, InstallMethod(UndirectedSpanningForest, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local C; + local C, record, data, PreOrderFunc, i; if DigraphNrVertices(D) = 0 then return fail; fi; - C := MaximalSymmetricSubdigraph(D)!.OutNeighbours; - C := DIGRAPH_SYMMETRIC_SPANNING_FOREST(C); + C := MaximalSymmetricSubdigraph(D); + record := NewDFSRecord(C); + data := List(DigraphVertices(C), x -> []); + + PreOrderFunc := function(record, data) + if record.parent[record.current] <> record.current then + Add(data[record.parent[record.current]], record.current); + Add(data[record.current], record.parent[record.current]); + fi; + end; + for i in DigraphVertices(C) do + ExecuteDFS(record, data, i, PreOrderFunc, DFSDefault, + DFSDefault, DFSDefault); + od; if IsMutableDigraph(D) then - D!.OutNeighbours := C; + D!.OutNeighbours := data; ClearDigraphEdgeLabels(D); return D; fi; - C := ConvertToImmutableDigraphNC(C); + C := ConvertToImmutableDigraphNC(data); SetUndirectedSpanningForestAttr(D, C); SetIsUndirectedForest(C, true); SetIsMultiDigraph(C, false); diff --git a/gap/grahom.gi b/gap/grahom.gi index d555639ae..b02e70fcf 100644 --- a/gap/grahom.gi +++ b/gap/grahom.gi @@ -238,7 +238,7 @@ end); # Finds a set S of homomorphism from gr1 to gr2 such that every homomorphism g # between the two graphs can expressed as a composition g = f * x of an element -# f in S and an automorphism x of gr2 +# f in S and an automorphism x of gr2 InstallMethod(HomomorphismsDigraphsRepresentatives, "for a digraph and a digraph", diff --git a/gap/oper.gd b/gap/oper.gd index e8fa1f176..71494aad1 100644 --- a/gap/oper.gd +++ b/gap/oper.gd @@ -142,3 +142,8 @@ DeclareOperation("PartialOrderDigraphJoinOfVertices", [IsDigraph, IsPosInt, IsPosInt]); DeclareOperation("PartialOrderDigraphMeetOfVertices", [IsDigraph, IsPosInt, IsPosInt]); + +# 11. DFS +DeclareOperation("NewDFSRecord", [IsDigraph]); +DeclareOperation("DFSDefault", [IsRecord, IsObject]); +DeclareGlobalFunction("ExecuteDFS"); diff --git a/gap/oper.gi b/gap/oper.gi index 29187ced8..8d7c02812 100644 --- a/gap/oper.gi +++ b/gap/oper.gi @@ -1388,7 +1388,7 @@ end); InstallMethod(DigraphPath, "for a digraph by out-neighbours and two pos ints", [IsDigraphByOutNeighboursRep, IsPosInt, IsPosInt], function(D, u, v) - local verts; + local verts, record, out, path_info, PostOrderFunc, AncestorFunc, AddToPath; verts := DigraphVertices(D); if not (u in verts and v in verts) then @@ -1405,7 +1405,38 @@ function(D, u, v) DigraphConnectedComponents(D).id[v] then return fail; fi; - return DIGRAPH_PATH(OutNeighbours(D), u, v); + record := NewDFSRecord(D); + path_info := Stack(); + AddToPath := function(current, child) + local edge; + edge := Position(OutNeighborsOfVertex(D, current), child); + Push(path_info, edge); + Push(path_info, child); + end; + AncestorFunc := function(record, data) + if u = v and record.child = u and Size(data) = 0 then + AddToPath(record.current, record.child); + record.preorder[v] := DigraphNrVertices(D) + 1; + fi; + end; + PostOrderFunc := function(record, data) + if record.child <> u and + record.preorder[record.child] <= record.preorder[v] then + AddToPath(record.current, record.child); + fi; + end; + ExecuteDFS(record, path_info, u, + DFSDefault, PostOrderFunc, + AncestorFunc, DFSDefault); + if Size(path_info) <= 1 then + return fail; + fi; + out := [[u], []]; + while Size(path_info) <> 0 do + Add(out[1], Pop(path_info)); + Add(out[2], Pop(path_info)); + od; + return out; end); InstallMethod(IsDigraphPath, "for a digraph and list", @@ -1700,17 +1731,40 @@ end); InstallMethod(DigraphLongestDistanceFromVertex, "for a digraph and a pos int", [IsDigraphByOutNeighboursRep, IsPosInt], function(D, v) - local dist; + local record, PreOrderFunc, PostOrderFunc, data, AncestorFunc; if not v in DigraphVertices(D) then ErrorNoReturn("the 2nd argument must be a vertex of the 1st ", "argument ,"); fi; - dist := DIGRAPH_LONGEST_DIST_VERTEX(OutNeighbours(D), v); - if dist = -2 then + record := NewDFSRecord(D); + data := rec(depth := ListWithIdenticalEntries(DigraphNrVertices(D), 0), + prev := 0); + AncestorFunc := function(record, data) + record.stop := true; + end; + PostOrderFunc := function(record, data) + data.depth[record.current] := data.prev; + data.prev := data.prev + 1; + end; + PreOrderFunc := function(record, data) + local i, neighbours; + data.prev := 0; + neighbours := OutNeighborsOfVertex(record.graph, record.current); + for i in [1 .. Size(neighbours)] do + # need to bypass the CrossFunc + if record.postorder[neighbours[i]] <> -1 then + record.preorder[neighbours[i]] := -1; + fi; + od; + end; + ExecuteDFS(record, data, v, + PreOrderFunc, PostOrderFunc, + AncestorFunc, DFSDefault); + if record.stop then return infinity; fi; - return dist; + return data.depth[v]; end); InstallMethod(DigraphLayers, "for a digraph, and a positive integer", @@ -1963,50 +2017,47 @@ end); InstallMethod(VerticesReachableFrom, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) - local N, index, current, succ, visited, prev, n, i, parent, - have_visited_root; + local N, record, data, AncestorFunc, PreOrderFunc; + N := DigraphNrVertices(D); if 0 = root or root > N then ErrorNoReturn("the 2nd argument (root) is not a vertex of the 1st ", "argument (a digraph)"); fi; - index := ListWithIdenticalEntries(N, 0); - have_visited_root := false; - index[root] := 1; - current := root; - succ := OutNeighbours(D); - visited := []; - parent := []; - parent[root] := fail; - repeat - prev := current; - for i in [index[current] .. Length(succ[current])] do - n := succ[current][i]; - if n = root and not have_visited_root then - Add(visited, root); - have_visited_root := true; - elif index[n] = 0 then - Add(visited, n); - parent[n] := current; - index[current] := i + 1; - current := n; - index[current] := 1; - break; - fi; - od; - if prev = current then - current := parent[current]; + + record := NewDFSRecord(D); + data := rec(result := [], root_is_child := false); + + PreOrderFunc := function(record, data) + if record.current <> root then + Add(data.result, record.current); + fi; + end; + + AncestorFunc := function(record, data) + if record.child = root and not data.root_is_child then + data.root_is_child := true; + Add(data.result, root); fi; - until current = fail; - return visited; + end; + + ExecuteDFS(record, + data, + root, + PreOrderFunc, + DFSDefault, + AncestorFunc, + DFSDefault); + return data.result; end); InstallMethod(DominatorTree, "for a digraph and a vertex", [IsDigraph, IsPosInt], function(D, root) - local M, node_to_preorder_num, preorder_num_to_node, parent, index, next, - current, succ, prev, n, semi, lastlinked, label, bucket, idom, - compress, eval, pred, N, w, y, x, i, v; + local M, preorder_num_to_node, PreOrderFunc, record, parent, + node_to_preorder_num, semi, lastlinked, label, bucket, idom, compress, eval, + pred, N, w, y, x, i, v; + M := DigraphNrVertices(D); if 0 = root or root > M then @@ -2014,36 +2065,25 @@ function(D, root) "argument (a digraph)"); fi; - node_to_preorder_num := []; - node_to_preorder_num[root] := 1; - preorder_num_to_node := [root]; + preorder_num_to_node := []; - parent := []; - parent[root] := fail; + PreOrderFunc := function(record, data) + Add(data, record.current); + end; - index := ListWithIdenticalEntries(M, 1); + record := NewDFSRecord(D); + ExecuteDFS(record, + preorder_num_to_node, + root, + PreOrderFunc, + DFSDefault, + DFSDefault, + DFSDefault); + + parent := record.parent; + parent[root] := fail; + node_to_preorder_num := record.preorder; - next := 2; - current := root; - succ := OutNeighbours(D); - repeat - prev := current; - for i in [index[current] .. Length(succ[current])] do - n := succ[current][i]; - if not IsBound(node_to_preorder_num[n]) then - Add(preorder_num_to_node, n); - parent[n] := current; - index[current] := i + 1; - node_to_preorder_num[n] := next; - next := next + 1; - current := n; - break; - fi; - od; - if prev = current then - current := parent[current]; - fi; - until current = fail; semi := [1 .. M]; lastlinked := M + 1; label := []; @@ -2089,7 +2129,7 @@ function(D, root) od; bucket[w] := []; for v in pred[w] do - if IsBound(node_to_preorder_num[v]) then + if node_to_preorder_num[v] <> -1 then x := eval(v); if node_to_preorder_num[semi[x]] < node_to_preorder_num[semi[w]] then semi[w] := semi[x]; @@ -2200,3 +2240,50 @@ function(D, i, j) return fail; end); + +############################################################################# +# 11. DFS +############################################################################# + +InstallMethod(NewDFSRecord, +"for a digraph", [IsDigraph], +function(graph) + local record; + record := rec(); + record.graph := graph; + record.child := -1; + record.current := -1; + record.stop := false; + record.parent := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.preorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + record.postorder := ListWithIdenticalEntries(DigraphNrVertices(graph), -1); + return record; +end); + +InstallMethod(DFSDefault, +"for a record and an object", [IsRecord, IsObject], +function(record, data) +end); + +# * PreOrderFunc is called with (record, data) when a vertex is popped from the +# stack for the first time. +# * PostOrderFunc is called with (record, data) when all of record.child's +# children have been visited (i.e. when we backtrack from record.child to +# record.parent[record.child]). +# * AncestorFunc is called with (record, data) when (record.current, +# record.child) is an edge and record.child is an ancestor of record.current. +# * CrossFunc is called with (record, data) when (record.current, record.child) +# is an edge, the preorder value of record.current is greater than the +# preorder value of child, and record.current and child are unrelated +# by ancestry. +InstallGlobalFunction(ExecuteDFS, +function(record, data, start, PreOrderFunc, PostOrderFunc, AncestorFunc, + CrossFunc) + if not IsEqualSet(RecNames(record), ["stop", "graph", "child", "parent", + "preorder", "postorder", "current"]) then + ErrorNoReturn("the 1st argument must be created with ", + "NewDFSRecord,"); + fi; + ExecuteDFS_C(record, data, start, PreOrderFunc, PostOrderFunc, + AncestorFunc, CrossFunc); +end); diff --git a/gap/prop.gi b/gap/prop.gi index 955f540de..0f08f1e08 100644 --- a/gap/prop.gi +++ b/gap/prop.gi @@ -173,7 +173,7 @@ D -> DigraphNrVertices(D) <= 1 and IsEmptyDigraph(D)); InstallMethod(IsAcyclicDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], function(D) - local n; + local n, i, record, FoundCycle, components; n := DigraphNrVertices(D); if n = 0 then return true; @@ -188,7 +188,26 @@ function(D) fi; return false; fi; - return IS_ACYCLIC_DIGRAPH(OutNeighbours(D)); + record := NewDFSRecord(D); + FoundCycle := function(record, data) + record.stop := true; + end; + components := DigraphConnectedComponents(D); + if Size(components.comps) = 1 then + ExecuteDFS(record, [], 1, DFSDefault, + DFSDefault, FoundCycle, DFSDefault); + return not record.stop; + fi; + # handles disconnected digraphs + for i in [1 .. Size(components.comps)] do + record := NewDFSRecord(D); + ExecuteDFS(record, [], components.comps[i][1], + DFSDefault, DFSDefault, FoundCycle, DFSDefault); + if record.stop then + return false; + fi; + od; + return true; end); # Complexity O(number of edges) @@ -308,7 +327,37 @@ D -> DigraphPeriod(D) = 1); InstallMethod(IsAntisymmetricDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], -D -> IS_ANTISYMMETRIC_DIGRAPH(OutNeighbours(D))); +function(D) + local record, AncestorFunc, i, components; + if DigraphNrVertices(D) <= 1 then + return true; + fi; + record := NewDFSRecord(D); + + AncestorFunc := function(record, data) + local pos, neighbours; + if record.child = record.current then + return; + fi; + # checks if the child has a symmetric edge with current node + neighbours := OutNeighboursOfVertex(record.graph, record.child); + pos := Position(neighbours, record.current); + if pos <> fail then + record.stop := true; + fi; + end; + + components := DigraphConnectedComponents(D); + for i in [1 .. Size(components.comps)] do + record := NewDFSRecord(D); + ExecuteDFS(record, [], components.comps[i][1], DFSDefault, DFSDefault, + AncestorFunc, DFSDefault); + if record.stop then + return false; + fi; + od; + return true; +end); InstallMethod(IsTransitiveDigraph, "for a digraph by out-neighbours", [IsDigraphByOutNeighboursRep], diff --git a/src/dfs.c b/src/dfs.c new file mode 100644 index 000000000..e7430a533 --- /dev/null +++ b/src/dfs.c @@ -0,0 +1,124 @@ +/******************************************************************************* +** +*A dfs.c GAP package Digraphs Lea Racine +** James Mitchell +** +** Copyright (C) 2014-21 - Julius Jonusas, James Mitchell, Michael Torpey, +** Wilf A. Wilson et al. +** +** This file is free software, see the digraphs/LICENSE. +** +*******************************************************************************/ + +#include "dfs.h" + +#include // for false, true, bool +#include // for uint64_t +#include // for NULL, free + +#include "digraphs-config.h" +#include "digraphs-debug.h" +#include "digraphs.h" + +// Extreme examples are on the pull request #459 + +Obj ExecuteDFS(Obj self, Obj args) { + DIGRAPHS_ASSERT(LEN_PLIST(args) == 7); + Obj record = ELM_PLIST(args, 1); + Obj data = ELM_PLIST(args, 2); + Obj start = ELM_PLIST(args, 3); + Obj PreorderFunc = ELM_PLIST(args, 4); + Obj PostOrderFunc = ELM_PLIST(args, 5); + Obj AncestorFunc = ELM_PLIST(args, 6); + Obj CrossFunc = ELM_PLIST(args, 7); + + DIGRAPHS_ASSERT(NARG_FUNC(PreorderFunc) == 2); + DIGRAPHS_ASSERT(IS_FUNC(AncestorFunc)); + DIGRAPHS_ASSERT(NARG_FUNC(AncestorFunc) == 2); + DIGRAPHS_ASSERT(IS_FUNC(PostOrderFunc)); + DIGRAPHS_ASSERT(NARG_FUNC(PostOrderFunc) == 2); + DIGRAPHS_ASSERT(IS_FUNC(PreorderFunc)); + DIGRAPHS_ASSERT(IS_PREC(record)); + DIGRAPHS_ASSERT(IS_INTOBJ(start)); + DIGRAPHS_ASSERT(IS_FUNC(CrossFunc)); + DIGRAPHS_ASSERT(NARG_FUNC(CrossFunc) == 2); + + Obj D = ElmPRec(record, RNamName("graph")); + Int N = DigraphNrVertices(D); + + if (INT_INTOBJ(start) > N) { + ErrorQuit( + "the third argument must be a vertex in your graph,", 0L, 0L); + } + Int top = 0; + Obj stack = NEW_PLIST(T_PLIST_CYC, N); + AssPlist(stack, ++top, start); + + UInt preorder_num = 0; + UInt postorder_num = 0; + + Int current = 0; + + Obj parent = ElmPRec(record, RNamName("parent")); + Obj postorder = ElmPRec(record, RNamName("postorder")); + Obj preorder = ElmPRec(record, RNamName("preorder")); + + DIGRAPHS_ASSERT(LEN_PLIST(parent) == N); + DIGRAPHS_ASSERT(LEN_PLIST(postorder) == N); + DIGRAPHS_ASSERT(LEN_PLIST(preorder) == N); + + SET_ELM_PLIST(parent, INT_INTOBJ(start), start); + + Obj neighbors = FuncOutNeighbours(self, D); + DIGRAPHS_ASSERT(IS_PLIST(neighbors)); + + Int RNamChild = RNamName("child"); + Int RNamCurrent = RNamName("current"); + Int RNamStop = RNamName("stop"); + + while (top > 0) { + current = INT_INTOBJ(ELM_PLIST(stack, top--)); + DIGRAPHS_ASSERT(current != 0); + if (current < 0) { + Int child = -1 * current; + AssPRec(record, RNamChild, INTOBJ_INT(child)); + AssPRec(record, RNamCurrent, ELM_PLIST(parent, child)); + CALL_2ARGS(PostOrderFunc, record, data); + SET_ELM_PLIST(postorder, child, INTOBJ_INT(++postorder_num)); + CHANGED_BAG(record); + continue; + } else if (INT_INTOBJ(ELM_PLIST(preorder, current)) > 0) { + continue; + } else { + AssPRec(record, RNamCurrent, INTOBJ_INT(current)); + CALL_2ARGS(PreorderFunc, record, data); + SET_ELM_PLIST(preorder, current, INTOBJ_INT(++preorder_num)); + CHANGED_BAG(record); + AssPlist(stack, ++top, INTOBJ_INT(-1 * current)); + } + + if (ElmPRec(record, RNamStop) == True) { + break; + } + + Obj succ = ELM_PLIST(neighbors, current); + for (UInt j = 0; j < LEN_LIST(succ); ++j) { + UInt v = INT_INTOBJ(ELM_LIST(succ, LEN_LIST(succ) - j)); + AssPRec(record, RNamChild, INTOBJ_INT(v)); + if (INT_INTOBJ(ELM_PLIST(preorder, v)) == -1) { + SET_ELM_PLIST(parent, v, INTOBJ_INT(current)); + CHANGED_BAG(record); + AssPlist(stack, ++top, INTOBJ_INT(v)); + } else if (INT_INTOBJ(ELM_PLIST(postorder, v)) == -1) { + CALL_2ARGS(AncestorFunc, record, data); + } else if (INT_INTOBJ(ELM_PLIST(preorder, v)) + < INT_INTOBJ(ELM_PLIST(preorder, current))) { + CALL_2ARGS(CrossFunc, record, data); + } + if (ElmPRec(record, RNamStop) == True) { + break; + } + } + } + return record; +} diff --git a/src/dfs.h b/src/dfs.h new file mode 100644 index 000000000..40ec66e35 --- /dev/null +++ b/src/dfs.h @@ -0,0 +1,21 @@ +/******************************************************************************* +** +*A dfs.h GAP package Digraphs Lea Racine +** James Mitchell +** +** Copyright (C) 2014-21 - Julius Jonusas, James Mitchell, Michael Torpey, +** Wilf A. Wilson et al. +** +** This file is free software, see the digraphs/LICENSE. +** +*******************************************************************************/ + +#ifndef DIGRAPHS_SRC_DFS_H_ +#define DIGRAPHS_SRC_DFS_H_ + +// GAP headers +#include "compiled.h" // for Obj, Int + +Obj ExecuteDFS(Obj self, Obj args); + +#endif // DIGRAPHS_SRC_DFS_H_ diff --git a/src/digraphs.c b/src/digraphs.c index 842abe830..7ca058e19 100644 --- a/src/digraphs.c +++ b/src/digraphs.c @@ -13,13 +13,14 @@ *******************************************************************************/ #include "digraphs.h" -#include "digraphs-config.h" #include // for false, true, bool #include // for uint64_t #include // for NULL, free #include "cliques.h" +#include "dfs.h" // for ExecuteDFS +#include "digraphs-config.h" #include "digraphs-debug.h" // for DIGRAPHS_ASSERT #include "homos.h" // for FuncHomomorphismDigraphsFinder #include "planar.h" // for FUNC_IS_PLANAR, . . . @@ -2313,6 +2314,12 @@ static StructGVarFunc GVarFuncs[] = { FuncSUBGRAPH_HOMEOMORPHIC_TO_K4, "src/planar.c:FuncSUBGRAPH_HOMEOMORPHIC_TO_K4"}, + {"ExecuteDFS_C", + 7, + "record, data, start, PreorderFunc, x, y, z", + ExecuteDFS, + "src/dfs.c:ExecuteDFS"}, + {0, 0, 0, 0, 0} /* Finish with an empty entry */ }; diff --git a/tst/standard/oper.tst b/tst/standard/oper.tst index 967ef2669..e5ecc9f53 100644 --- a/tst/standard/oper.tst +++ b/tst/standard/oper.tst @@ -1279,6 +1279,16 @@ ent , gap> DigraphPath(gr, 11, 11); Error, the 2nd and 3rd arguments and must be vertices of the 1st argum\ ent , +gap> D := Digraph([[2], [3], [2, 3]]); + +gap> DigraphPath(D, 1, 3); +[ [ 1, 2, 3 ], [ 1, 1 ] ] +gap> DigraphPath(D, 2, 1); +fail +gap> DigraphPath(D, 3, 3); +[ [ 3, 3 ], [ 2 ] ] +gap> DigraphPath(D, 1, 1); +fail # IteratorOfPaths gap> gr := CompleteDigraph(5);; @@ -1845,7 +1855,7 @@ gap> D := CompleteDigraph(5); gap> VerticesReachableFrom(D, 1); [ 2, 1, 3, 4, 5 ] gap> VerticesReachableFrom(D, 3); -[ 1, 2, 3, 4, 5 ] +[ 1, 3, 2, 4, 5 ] gap> D := EmptyDigraph(5); gap> VerticesReachableFrom(D, 1); @@ -1891,7 +1901,7 @@ gap> VerticesReachableFrom(D, 1); gap> VerticesReachableFrom(D, 2); [ 4 ] gap> VerticesReachableFrom(D, 3); -[ 1, 2, 4, 3, 5 ] +[ 1, 3, 2, 4, 5 ] gap> VerticesReachableFrom(D, 4); [ ] gap> VerticesReachableFrom(D, 5); @@ -1903,7 +1913,7 @@ gap> VerticesReachableFrom(D, 1); gap> VerticesReachableFrom(D, 2); [ 4 ] gap> VerticesReachableFrom(D, 3); -[ 1, 2, 4, 3, 5 ] +[ 1, 3, 2, 4, 5 ] gap> VerticesReachableFrom(D, 4); [ ] gap> VerticesReachableFrom(D, 5); @@ -2791,6 +2801,120 @@ gap> Unbind(U); gap> Unbind(u1); gap> Unbind(u2); +# DFS + +# NewDFSRecord +gap> NewDFSRecord(ChainDigraph(10)); +rec( child := -1, current := -1, + graph := , + parent := [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ], + postorder := [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ], + preorder := [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ], stop := false ) +gap> NewDFSRecord(CompleteDigraph(2)); +rec( child := -1, current := -1, + graph := , parent := [ -1, -1 ], + postorder := [ -1, -1 ], preorder := [ -1, -1 ], stop := false ) +gap> NewDFSRecord(Digraph([[1], [2], [1], [1], [2]])); +rec( child := -1, current := -1, + graph := , + parent := [ -1, -1, -1, -1, -1 ], postorder := [ -1, -1, -1, -1, -1 ], + preorder := [ -1, -1, -1, -1, -1 ], stop := false ) + +# DFSDefault +gap> DFSDefault(rec(), []); +gap> DFSDefault(rec(), rec()); + +# ExecuteDFS +gap> record := NewDFSRecord(CompleteDigraph(10));; +gap> ExecuteDFS(record, [], 2, DFSDefault, +> DFSDefault, DFSDefault, DFSDefault); +gap> record.preorder; +[ 2, 1, 3, 4, 5, 6, 7, 8, 9, 10 ] +gap> record := NewDFSRecord(CompleteDigraph(15));; +gap> data := rec(cycle_vertex := 0);; +gap> AncestorFunc := function(record, data) +> record.stop := true; +> data.cycle_vertex := record.child; +> end;; +gap> ExecuteDFS(record, data, 1, DFSDefault, +> DFSDefault, AncestorFunc, DFSDefault); +gap> record.stop; +true +gap> data.cycle_vertex; +1 +gap> record.preorder; +[ 1, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ] +gap> record := NewDFSRecord(Digraph([[2, 3], [4], [5], [], [4]]));; +gap> CrossFunc := function(record, data) +> record.stop := true; +> Add(data, record.child); +> end;; +gap> data := [];; +gap> ExecuteDFS(record, data, 1, DFSDefault, +> DFSDefault, DFSDefault, CrossFunc); +gap> record.stop; +true +gap> data; +[ 4 ] +gap> AncestorFunc := function(record, data) +> Add(data.cycle_vertex, record.child); +> end;; +gap> CrossFunc := function(record, data) +> Add(data.cross_vertex, record.child); +> end;; +gap> record := NewDFSRecord(Digraph([[2, 3, 3], [4, 4], [5, 1, 1], [], [4]]));; +gap> data := rec(cycle_vertex := [], cross_vertex := []);; +gap> ExecuteDFS(record, data, 1, DFSDefault, +> DFSDefault, AncestorFunc, CrossFunc); +gap> data; +rec( cross_vertex := [ 4 ], cycle_vertex := [ 1, 1 ] ) +gap> ExecuteDFS(rec(), data, 1, DFSDefault, +> DFSDefault, AncestorFunc, CrossFunc); +Error, the 1st argument must be created with NewDFSRecord, +gap> D := ChainDigraph(1);; +gap> ExecuteDFS(NewDFSRecord(D), [], 3, DFSDefault, DFSDefault, DFSDefault, +> DFSDefault); +Error, the third argument must be a vertex in your graph, + +# IsDigraphPath +gap> D := Digraph(IsMutableDigraph, Combinations([1 .. 5]), IsSubset); + +gap> DigraphReflexiveTransitiveReduction(D); + +gap> MakeImmutable(D); + +gap> IsDigraphPath(D, [1, 2, 3], []); +Error, the 2nd and 3rd arguments (lists) are incompatible, expected 3rd argume\ +nt of length 2, got 0 +gap> IsDigraphPath(D, [1], []); +true +gap> IsDigraphPath(D, [1, 2], [5]); +false +gap> IsDigraphPath(D, [32, 31, 33], [1, 1]); +false +gap> IsDigraphPath(D, [32, 33, 31], [1, 1]); +false +gap> IsDigraphPath(D, [6, 9, 16, 17], [3, 3, 2]); +true +gap> IsDigraphPath(D, [33, 9, 16, 17], [3, 3, 2]); +false +gap> IsDigraphPath(D, [6, 9, 18, 1], [9, 10, 2]); +false + +# IsDigraphPath +gap> D := Digraph(IsMutableDigraph, Combinations([1 .. 5]), IsSubset); + +gap> DigraphReflexiveTransitiveReduction(D); + +gap> MakeImmutable(D); + +gap> IsDigraphPath(D, DigraphPath(D, 6, 1)); +true +gap> ForAll(List(IteratorOfPaths(D, 6, 1)), x -> IsDigraphPath(D, x)); +true +gap> IsDigraphPath(D, []); +Error, the 2nd argument (a list) must have length 2, but found length 0 + # gap> DIGRAPHS_StopTest(); gap> STOP_TEST("Digraphs package: standard/oper.tst", 0); diff --git a/tst/standard/prop.tst b/tst/standard/prop.tst index c55a58892..8cdca9acf 100644 --- a/tst/standard/prop.tst +++ b/tst/standard/prop.tst @@ -179,6 +179,14 @@ gap> HasIsAcyclicDigraph(gr); false gap> IsAcyclicDigraph(gr); false +gap> gr := DigraphDisjointUnion(ChainDigraph(10), ChainDigraph(2)); + +gap> IsAcyclicDigraph(gr); +true +gap> gr := DigraphDisjointUnion(CompleteDigraph(5), ChainDigraph(2)); + +gap> IsAcyclicDigraph(gr); +false # IsFunctionalDigraph gap> IsFunctionalDigraph(multiple);