dominating.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. """Functions for computing dominating sets in a graph."""
  2. import math
  3. from heapq import heappop, heappush
  4. from itertools import chain, count
  5. import networkx as nx
  6. __all__ = [
  7. "dominating_set",
  8. "is_dominating_set",
  9. "connected_dominating_set",
  10. "is_connected_dominating_set",
  11. ]
  12. @nx._dispatchable
  13. def dominating_set(G, start_with=None):
  14. r"""Finds a dominating set for the graph G.
  15. A *dominating set* for a graph with node set *V* is a subset *D* of
  16. *V* such that every node not in *D* is adjacent to at least one
  17. member of *D* [1]_.
  18. Parameters
  19. ----------
  20. G : NetworkX graph
  21. start_with : node (default=None)
  22. Node to use as a starting point for the algorithm.
  23. Returns
  24. -------
  25. D : set
  26. A dominating set for G.
  27. Notes
  28. -----
  29. This function is an implementation of algorithm 7 in [2]_ which
  30. finds some dominating set, not necessarily the smallest one.
  31. See also
  32. --------
  33. is_dominating_set
  34. References
  35. ----------
  36. .. [1] https://en.wikipedia.org/wiki/Dominating_set
  37. .. [2] Abdol-Hossein Esfahanian. Connectivity Algorithms.
  38. http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
  39. """
  40. all_nodes = set(G)
  41. if start_with is None:
  42. start_with = nx.utils.arbitrary_element(all_nodes)
  43. if start_with not in G:
  44. raise nx.NetworkXError(f"node {start_with} is not in G")
  45. dominating_set = {start_with}
  46. dominated_nodes = set(G[start_with])
  47. remaining_nodes = all_nodes - dominated_nodes - dominating_set
  48. while remaining_nodes:
  49. # Choose an arbitrary node and determine its undominated neighbors.
  50. v = remaining_nodes.pop()
  51. undominated_nbrs = set(G[v]) - dominating_set
  52. # Add the node to the dominating set and the neighbors to the
  53. # dominated set. Finally, remove all of those nodes from the set
  54. # of remaining nodes.
  55. dominating_set.add(v)
  56. dominated_nodes |= undominated_nbrs
  57. remaining_nodes -= undominated_nbrs
  58. return dominating_set
  59. @nx._dispatchable
  60. def is_dominating_set(G, nbunch):
  61. """Checks if `nbunch` is a dominating set for `G`.
  62. A *dominating set* for a graph with node set *V* is a subset *D* of
  63. *V* such that every node not in *D* is adjacent to at least one
  64. member of *D* [1]_.
  65. Parameters
  66. ----------
  67. G : NetworkX graph
  68. nbunch : iterable
  69. An iterable of nodes in the graph `G`.
  70. Returns
  71. -------
  72. dominating : bool
  73. True if `nbunch` is a dominating set of `G`, false otherwise.
  74. See also
  75. --------
  76. dominating_set
  77. References
  78. ----------
  79. .. [1] https://en.wikipedia.org/wiki/Dominating_set
  80. """
  81. testset = {n for n in nbunch if n in G}
  82. nbrs = set(chain.from_iterable(G[n] for n in testset))
  83. return len(set(G) - testset - nbrs) == 0
  84. @nx.utils.not_implemented_for("directed")
  85. @nx._dispatchable
  86. def connected_dominating_set(G):
  87. """Returns a connected dominating set.
  88. A *dominating set* for a graph *G* with node set *V* is a subset *D* of *V*
  89. such that every node not in *D* is adjacent to at least one member of *D*
  90. [1]_. A *connected dominating set* is a dominating set *C* that induces a
  91. connected subgraph of *G* [2]_.
  92. Note that connected dominating sets are not unique in general and that there
  93. may be other connected dominating sets.
  94. Parameters
  95. ----------
  96. G : NewtorkX graph
  97. Undirected connected graph.
  98. Returns
  99. -------
  100. connected_dominating_set : set
  101. A dominating set of nodes which induces a connected subgraph of G.
  102. Raises
  103. ------
  104. NetworkXNotImplemented
  105. If G is directed.
  106. NetworkXError
  107. If G is disconnected.
  108. Examples
  109. ________
  110. >>> G = nx.Graph(
  111. ... [
  112. ... (1, 2),
  113. ... (1, 3),
  114. ... (1, 4),
  115. ... (1, 5),
  116. ... (1, 6),
  117. ... (2, 7),
  118. ... (3, 8),
  119. ... (4, 9),
  120. ... (5, 10),
  121. ... (6, 11),
  122. ... (7, 12),
  123. ... (8, 12),
  124. ... (9, 12),
  125. ... (10, 12),
  126. ... (11, 12),
  127. ... ]
  128. ... )
  129. >>> nx.connected_dominating_set(G)
  130. {1, 2, 3, 4, 5, 6, 7}
  131. Notes
  132. -----
  133. This function implements Algorithm I in its basic version as described
  134. in [3]_. The idea behind the algorithm is the following: grow a tree *T*,
  135. starting from a node with maximum degree. Throughout the growing process,
  136. nonleaf nodes in *T* are our connected dominating set (CDS), leaf nodes in
  137. *T* are marked as "seen" and nodes in G that are not yet in *T* are marked as
  138. "unseen". We maintain a max-heap of all "seen" nodes, and track the number
  139. of "unseen" neighbors for each node. At each step we pop the heap top -- a
  140. "seen" (leaf) node with maximal number of "unseen" neighbors, add it to the
  141. CDS and mark all its "unseen" neighbors as "seen". For each one of the newly
  142. created "seen" nodes, we also decrement the number of "unseen" neighbors for
  143. all its neighbors. The algorithm terminates when there are no more "unseen"
  144. nodes.
  145. Runtime complexity of this implementation is $O(|E|*log|V|)$ (amortized).
  146. References
  147. ----------
  148. .. [1] https://en.wikipedia.org/wiki/Dominating_set
  149. .. [2] https://en.wikipedia.org/wiki/Connected_dominating_set
  150. .. [3] Guha, S. and Khuller, S.
  151. *Approximation Algorithms for Connected Dominating Sets*,
  152. Algorithmica, 20, 374-387, 1998.
  153. """
  154. if len(G) == 0:
  155. return set()
  156. if not nx.is_connected(G):
  157. raise nx.NetworkXError("G must be a connected graph")
  158. if len(G) == 1:
  159. return set(G)
  160. G_succ = G._adj # For speed-up
  161. # Use the count c to avoid comparing nodes
  162. c = count()
  163. # Keep track of the number of unseen nodes adjacent to each node
  164. unseen_degree = dict(G.degree)
  165. # Find node with highest degree and update its neighbors
  166. (max_deg_node, max_deg) = max(unseen_degree.items(), key=lambda x: x[1])
  167. for nbr in G_succ[max_deg_node]:
  168. unseen_degree[nbr] -= 1
  169. # Initially all nodes except max_deg_node are unseen
  170. unseen = set(G) - {max_deg_node}
  171. # We want a max-heap of the unseen-degree using heapq, which is a min-heap
  172. # So we store the negative of the unseen-degree
  173. seen = [(-max_deg, next(c), max_deg_node)]
  174. connected_dominating_set = set()
  175. # Main loop
  176. while unseen:
  177. (neg_deg, cnt, u) = heappop(seen)
  178. # Check if u's unseen-degree changed while in the heap
  179. if -neg_deg > unseen_degree[u]:
  180. heappush(seen, (-unseen_degree[u], cnt, u))
  181. continue
  182. # Mark all u's unseen neighbors as seen and add them to the heap
  183. for v in G_succ[u]:
  184. if v in unseen:
  185. unseen.remove(v)
  186. for nbr in G_succ[v]:
  187. unseen_degree[nbr] -= 1
  188. heappush(seen, (-unseen_degree[v], next(c), v))
  189. # Add u to the dominating set
  190. connected_dominating_set.add(u)
  191. return connected_dominating_set
  192. @nx.utils.not_implemented_for("directed")
  193. @nx._dispatchable
  194. def is_connected_dominating_set(G, nbunch):
  195. """Checks if `nbunch` is a connected dominating set for `G`.
  196. A *dominating set* for a graph *G* with node set *V* is a subset *D* of
  197. *V* such that every node not in *D* is adjacent to at least one
  198. member of *D* [1]_. A *connected dominating set* is a dominating
  199. set *C* that induces a connected subgraph of *G* [2]_.
  200. Parameters
  201. ----------
  202. G : NetworkX graph
  203. Undirected graph.
  204. nbunch : iterable
  205. An iterable of nodes in the graph `G`.
  206. Returns
  207. -------
  208. connected_dominating : bool
  209. True if `nbunch` is connected dominating set of `G`, false otherwise.
  210. References
  211. ----------
  212. .. [1] https://en.wikipedia.org/wiki/Dominating_set
  213. .. [2] https://en.wikipedia.org/wiki/Connected_dominating_set
  214. """
  215. return nx.is_dominating_set(G, nbunch) and nx.is_connected(nx.subgraph(G, nbunch))