covering.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. """Functions related to graph covers."""
  2. from functools import partial
  3. from itertools import chain
  4. import networkx as nx
  5. from networkx.utils import arbitrary_element, not_implemented_for
  6. __all__ = ["min_edge_cover", "is_edge_cover"]
  7. @not_implemented_for("directed")
  8. @not_implemented_for("multigraph")
  9. @nx._dispatchable
  10. def min_edge_cover(G, matching_algorithm=None):
  11. """Returns the min cardinality edge cover of the graph as a set of edges.
  12. A smallest edge cover can be found in polynomial time by finding
  13. a maximum matching and extending it greedily so that all nodes
  14. are covered. This function follows that process. A maximum matching
  15. algorithm can be specified for the first step of the algorithm.
  16. The resulting set may return a set with one 2-tuple for each edge,
  17. (the usual case) or with both 2-tuples `(u, v)` and `(v, u)` for
  18. each edge. The latter is only done when a bipartite matching algorithm
  19. is specified as `matching_algorithm`.
  20. Parameters
  21. ----------
  22. G : NetworkX graph
  23. An undirected graph.
  24. matching_algorithm : function
  25. A function that returns a maximum cardinality matching for `G`.
  26. The function must take one input, the graph `G`, and return
  27. either a set of edges (with only one direction for the pair of nodes)
  28. or a dictionary mapping each node to its mate. If not specified,
  29. :func:`~networkx.algorithms.matching.max_weight_matching` is used.
  30. Common bipartite matching functions include
  31. :func:`~networkx.algorithms.bipartite.matching.hopcroft_karp_matching`
  32. or
  33. :func:`~networkx.algorithms.bipartite.matching.eppstein_matching`.
  34. Returns
  35. -------
  36. min_cover : set
  37. A set of the edges in a minimum edge cover in the form of tuples.
  38. It contains only one of the equivalent 2-tuples `(u, v)` and `(v, u)`
  39. for each edge. If a bipartite method is used to compute the matching,
  40. the returned set contains both the 2-tuples `(u, v)` and `(v, u)`
  41. for each edge of a minimum edge cover.
  42. Examples
  43. --------
  44. >>> G = nx.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)])
  45. >>> sorted(nx.min_edge_cover(G))
  46. [(2, 1), (3, 0)]
  47. Notes
  48. -----
  49. An edge cover of a graph is a set of edges such that every node of
  50. the graph is incident to at least one edge of the set.
  51. The minimum edge cover is an edge covering of smallest cardinality.
  52. Due to its implementation, the worst-case running time of this algorithm
  53. is bounded by the worst-case running time of the function
  54. ``matching_algorithm``.
  55. Minimum edge cover for `G` can also be found using
  56. :func:`~networkx.algorithms.bipartite.covering.min_edge_covering` which is
  57. simply this function with a default matching algorithm of
  58. :func:`~networkx.algorithms.bipartite.matching.hopcroft_karp_matching`
  59. """
  60. if len(G) == 0:
  61. return set()
  62. if nx.number_of_isolates(G) > 0:
  63. # ``min_cover`` does not exist as there is an isolated node
  64. raise nx.NetworkXException(
  65. "Graph has a node with no edge incident on it, so no edge cover exists."
  66. )
  67. if matching_algorithm is None:
  68. matching_algorithm = partial(nx.max_weight_matching, maxcardinality=True)
  69. maximum_matching = matching_algorithm(G)
  70. # ``min_cover`` is superset of ``maximum_matching``
  71. try:
  72. # bipartite matching algs return dict so convert if needed
  73. min_cover = set(maximum_matching.items())
  74. bipartite_cover = True
  75. except AttributeError:
  76. min_cover = maximum_matching
  77. bipartite_cover = False
  78. # iterate for uncovered nodes
  79. uncovered_nodes = set(G) - {v for u, v in min_cover} - {u for u, v in min_cover}
  80. for v in uncovered_nodes:
  81. # Since `v` is uncovered, each edge incident to `v` will join it
  82. # with a covered node (otherwise, if there were an edge joining
  83. # uncovered nodes `u` and `v`, the maximum matching algorithm
  84. # would have found it), so we can choose an arbitrary edge
  85. # incident to `v`. (This applies only in a simple graph, not a
  86. # multigraph.)
  87. u = arbitrary_element(G[v])
  88. min_cover.add((u, v))
  89. if bipartite_cover:
  90. min_cover.add((v, u))
  91. return min_cover
  92. @not_implemented_for("directed")
  93. @nx._dispatchable
  94. def is_edge_cover(G, cover):
  95. """Decides whether a set of edges is a valid edge cover of the graph.
  96. Given a set of edges, whether it is an edge covering can
  97. be decided if we just check whether all nodes of the graph
  98. has an edge from the set, incident on it.
  99. Parameters
  100. ----------
  101. G : NetworkX graph
  102. An undirected bipartite graph.
  103. cover : set
  104. Set of edges to be checked.
  105. Returns
  106. -------
  107. bool
  108. Whether the set of edges is a valid edge cover of the graph.
  109. Examples
  110. --------
  111. >>> G = nx.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)])
  112. >>> cover = {(2, 1), (3, 0)}
  113. >>> nx.is_edge_cover(G, cover)
  114. True
  115. Notes
  116. -----
  117. An edge cover of a graph is a set of edges such that every node of
  118. the graph is incident to at least one edge of the set.
  119. """
  120. return set(G) <= set(chain.from_iterable(cover))