Method: RGL::Graph#transitive_reduction
- Defined in:
- lib/rgl/transitivity.rb
#transitive_reduction ⇒ Object
Returns an DirectedAdjacencyGraph which is the transitive reduction of this graph. Meaning, that each edge u -> v is omitted if path u -> … -> v exists. This method supports working with cyclic graphs; however, cycles are arbitrarily simplified which may lead to variant, although equally valid, results on equivalent graphs.
This method should run in O(|V||E|) time, where |V| and |E| are the number of vertices and edges respectively.
Raises NotDirectedError if run on an undirected graph.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/rgl/transitivity.rb', line 100 def transitive_reduction raise NotDirectedError, "transitive_reduction only supported for directed graphs" unless directed? # Compute a condensation graph in order to hide cycles. cg = condensation_graph # Use a depth first search to compute the transitive reduction over the # condensed graph. This is similar to the computation of the transitive # closure over the graph in that for any node of the graph all nodes # reachable from the node are tracked. Using a depth first search ensures # that all nodes reachable from a target node are known when considering # whether or not to add an edge pointing to that target. tr_cg = DirectedAdjacencyGraph.new paths_from = {} cg.depth_first_search do |v| paths_from[v] = Set.new cg.each_adjacent(v) do |w| # Only add the edge v -> w if there is no other edge v -> x such that # w is reachable from x. Make sure to completely skip the case where # x == w. unless cg.to_enum(:each_adjacent, v).any? { |x| x != w && paths_from[x].include?(w) } tr_cg.add_edge(v, w) # For each vertex v, track all nodes reachable from v by adding node # w to the list as well as all the nodes readable from w. paths_from[v] << w paths_from[v].merge(paths_from[w]) end end # Ensure that a vertex with no in or out edges is added to the graph. tr_cg.add_vertex(v) end # Expand the condensed transitive reduction. # # For each trivial strongly connected component in the condensed graph, # add the single node it contains to the new graph and add edges for each # edge the node begins in the original graph. # For each NON-trivial strongly connected component in the condensed # graph, add each node it contains to the new graph and add arbitrary # edges between the nodes to form a simple cycle. Then for each strongly # connected component adjacent to the current one, find and add the first # edge which exists in the original graph, starts in the first strongly # connected component, and ends in the second strongly connected # component. g = DirectedAdjacencyGraph.new tr_cg.each_vertex do |scc| # Make a cycle of the contents of non-trivial strongly connected # components. scc_arr = scc.to_a if scc.size > 1 || has_edge?(scc_arr.first, scc_arr.first) 0.upto(scc_arr.size - 2) do |idx| g.add_edge(scc_arr[idx], scc_arr[idx + 1]) end g.add_edge(scc_arr.last, scc_arr.first) end # Choose a single edge between the members of two different strongly # connected component to add to the graph. edges = self.to_enum(:each_edge) tr_cg.each_adjacent(scc) do |scc2| g.add_edge( *edges.find do |v, w| scc.member?(v) && scc2.member?(w) end ) end # Ensure that a vertex with no in or out edges is added to the graph. scc.each do |v| g.add_vertex(v) end end # Finally, the transitive reduction... g end |