def fvs_disjoint(self, g: MultiGraph, w: set, k: int) -> set: """ Given an undirected graph G and a fbvs W in G of size at least (k + 1), is it possible to construct a fbvs X of size at most k using only the nodes of G - W? :return: The set X, or `None` if it's not possible to construct X """ # If G[W] isn't a forest, then a solution X not using W can't remove W's cycles. if not is_acyclic(g.subgraph(w)): return None # Apply reductions exhaustively. k, soln_redux = self.apply_reductions(g, w, k) # If k becomes negative, it indicates that the reductions included # more than k nodes. In other word, reduction 2 shows that there are more than k nodes # in G - W that will create cycle in W. Hence, no solution of size <= k exists. if k < 0: return None # From now onwards we assume that k >= 0 # If G has been reduced to nothing and k is >= 0 then the solution generated by the reductions # is already optimal. if len(g) == 0: return soln_redux # Recall that H is a forest as W is a feedback vertex set. Thus H has a node x of degree at most 1. # Find an x in H of degree at most 1. h = graph_minus(g, w) x = None for v in h.nodes(): if h.degree(v) <= 1: x = v break assert x is not None, "There must be at least one node x of degree at most 1" # Branch on (G - {x}, W, k−1) and (G, W ∪ {x}, k) # G is copied in the left branch (as it is modified), but passed directly in the right. soln_left = self.fvs_disjoint(graph_minus(g, {x}), w, k - 1) if soln_left is not None: return soln_redux.union(soln_left).union({x}) soln_right = self.fvs_disjoint(g, w.union({x}), k) if soln_right is not None: return soln_redux.union(soln_right) return None
def apply_reductions(self, g: MultiGraph, w: set, k: int) -> (int, set): """ Exhaustively apply reductions. The three reductions are: Reduction 1: Delete all the nodes of degree at most 1 in G. Reduction 2: If there exists a node v in H such that G[W ∪ {v}] contains a cycle, then include v in the solution, delete v and decrease the parameter by 1. That is, the new instance is (G - {v}, W, k - 1). Reduction 3: If there is a node v ∈ V(H) of degree 2 in G such that at least one neighbor of v in G is from V(H), then delete this node and make its neighbors adjacent (even if they were adjacent before; the graph could become a multigraph now). """ # Current H. h = graph_minus(g, w) # Set of nodes included in the solution as a result of reductions. x = set() while True: reduction_applied = False for f in [self.reduction1, self.reduction2, self.reduction3]: (k, solx, changed) = f(g, w, h, k) if changed: reduction_applied = True if solx is not None: x.add(solx) if not reduction_applied: return k, x
def _get_fbvs_max_size(self, g: MultiGraph, k: int) -> set: # Exhaustively apply reductions k, x0 = self.apply_reductions(g, k) # Originally reduction 5: if k < 0, terminate the algorithm and conclude that # (G, k) is a no-instance. if k < 0: return None # If G is an empty graph, then we return soln_redux if len(g) == 0: return x0 # Pick a random edge, then a random end node of that edge rand_edge = choice(g.edges()) v = choice(rand_edge) # We recurse on (G - v, k − 1). xn = self._get_fbvs_max_size(graph_minus(g, {v}), k - 1) if xn is None: # If the recursive step returns a failure, then we return a failure as well. return None else: # If the recursive step returns a feedback vertex set Xn, then we return X = Xn ∪ {v} ∪ X0. return xn.union({v}).union(x0)
def ic_compression(self, g: MultiGraph, z: set, k: int) -> MultiGraph: """ Given a graph G and an FVS Z of size (k + 1), construct an FVS of size at most k. Return `None` if no such solution exists. """ assert (len(z) == k + 1) # i in {0 .. k} for i in range(0, k + 1): for xz in itertools.combinations(z, i): x = self.fvs_disjoint(graph_minus(g, xz), z.difference(xz), k - i) if x is not None: return x.union(xz) return None
def get_fbvs(self, graph: Graph): if is_acyclic(graph): return set() # remove all nodes of degree 0 or 1 as they can't be part of any cycles remove_node_deg_01(graph) nodes = graph.nodes() for L in range(0, len(nodes) + 1): for subset in itertools.combinations(nodes, L): # make an induced subgraph with the current node subset removed new_graph = graph_minus(graph, subset) if is_acyclic(new_graph): return subset return set()
def get_fbvs(self, graph: Graph): if is_acyclic(graph): return set() # remove all nodes of degree 0 or 1 as they can't be part of any cycles remove_node_deg_01(graph) # get the set of nodes that is in at least one cycle cycles = cycle_basis(graph) nodes_in_cycles = set([item for sublist in cycles for item in sublist]) for L in range(0, len(nodes_in_cycles) + 1): for subset in itertools.combinations(nodes_in_cycles, L): # make an induced subgraph with the current node subset removed new_graph = graph_minus(graph, subset) if is_acyclic(new_graph): return subset return set()