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
Example #3
0
    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)
Example #4
0
    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()
Example #7
0
    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()
    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()