distance_measures.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. """Distance measures approximated metrics."""
  2. import networkx as nx
  3. from networkx.utils.decorators import py_random_state
  4. __all__ = ["diameter"]
  5. @py_random_state(1)
  6. def diameter(G, seed=None):
  7. """Returns a lower bound on the diameter of the graph G.
  8. The function computes a lower bound on the diameter (i.e., the maximum eccentricity)
  9. of a directed or undirected graph G. The procedure used varies depending on the graph
  10. being directed or not.
  11. If G is an `undirected` graph, then the function uses the `2-sweep` algorithm [1]_.
  12. The main idea is to pick the farthest node from a random node and return its eccentricity.
  13. Otherwise, if G is a `directed` graph, the function uses the `2-dSweep` algorithm [2]_,
  14. The procedure starts by selecting a random source node $s$ from which it performs a
  15. forward and a backward BFS. Let $a_1$ and $a_2$ be the farthest nodes in the forward and
  16. backward cases, respectively. Then, it computes the backward eccentricity of $a_1$ using
  17. a backward BFS and the forward eccentricity of $a_2$ using a forward BFS.
  18. Finally, it returns the best lower bound between the two.
  19. In both cases, the time complexity is linear with respect to the size of G.
  20. Parameters
  21. ----------
  22. G : NetworkX graph
  23. seed : integer, random_state, or None (default)
  24. Indicator of random number generation state.
  25. See :ref:`Randomness<randomness>`.
  26. Returns
  27. -------
  28. d : integer
  29. Lower Bound on the Diameter of G
  30. Raises
  31. ------
  32. NetworkXError
  33. If the graph is empty or
  34. If the graph is undirected and not connected or
  35. If the graph is directed and not strongly connected.
  36. See Also
  37. --------
  38. networkx.algorithms.distance_measures.diameter
  39. References
  40. ----------
  41. .. [1] Magnien, Clémence, Matthieu Latapy, and Michel Habib.
  42. *Fast computation of empirically tight bounds for the diameter of massive graphs.*
  43. Journal of Experimental Algorithmics (JEA), 2009.
  44. https://arxiv.org/pdf/0904.2728.pdf
  45. .. [2] Crescenzi, Pierluigi, Roberto Grossi, Leonardo Lanzi, and Andrea Marino.
  46. *On computing the diameter of real-world directed (weighted) graphs.*
  47. International Symposium on Experimental Algorithms. Springer, Berlin, Heidelberg, 2012.
  48. https://courses.cs.ut.ee/MTAT.03.238/2014_fall/uploads/Main/diameter.pdf
  49. """
  50. # if G is empty
  51. if not G:
  52. raise nx.NetworkXError("Expected non-empty NetworkX graph!")
  53. # if there's only a node
  54. if G.number_of_nodes() == 1:
  55. return 0
  56. # if G is directed
  57. if G.is_directed():
  58. return _two_sweep_directed(G, seed)
  59. # else if G is undirected
  60. return _two_sweep_undirected(G, seed)
  61. def _two_sweep_undirected(G, seed):
  62. """Helper function for finding a lower bound on the diameter
  63. for undirected Graphs.
  64. The idea is to pick the farthest node from a random node
  65. and return its eccentricity.
  66. ``G`` is a NetworkX undirected graph.
  67. .. note::
  68. ``seed`` is a random.Random or numpy.random.RandomState instance
  69. """
  70. # select a random source node
  71. source = seed.choice(list(G))
  72. # get the distances to the other nodes
  73. distances = nx.shortest_path_length(G, source)
  74. # if some nodes have not been visited, then the graph is not connected
  75. if len(distances) != len(G):
  76. raise nx.NetworkXError("Graph not connected.")
  77. # take a node that is (one of) the farthest nodes from the source
  78. *_, node = distances
  79. # return the eccentricity of the node
  80. return nx.eccentricity(G, node)
  81. def _two_sweep_directed(G, seed):
  82. """Helper function for finding a lower bound on the diameter
  83. for directed Graphs.
  84. It implements 2-dSweep, the directed version of the 2-sweep algorithm.
  85. The algorithm follows the following steps.
  86. 1. Select a source node $s$ at random.
  87. 2. Perform a forward BFS from $s$ to select a node $a_1$ at the maximum
  88. distance from the source, and compute $LB_1$, the backward eccentricity of $a_1$.
  89. 3. Perform a backward BFS from $s$ to select a node $a_2$ at the maximum
  90. distance from the source, and compute $LB_2$, the forward eccentricity of $a_2$.
  91. 4. Return the maximum between $LB_1$ and $LB_2$.
  92. ``G`` is a NetworkX directed graph.
  93. .. note::
  94. ``seed`` is a random.Random or numpy.random.RandomState instance
  95. """
  96. # get a new digraph G' with the edges reversed in the opposite direction
  97. G_reversed = G.reverse()
  98. # select a random source node
  99. source = seed.choice(list(G))
  100. # compute forward distances from source
  101. forward_distances = nx.shortest_path_length(G, source)
  102. # compute backward distances from source
  103. backward_distances = nx.shortest_path_length(G_reversed, source)
  104. # if either the source can't reach every node or not every node
  105. # can reach the source, then the graph is not strongly connected
  106. n = len(G)
  107. if len(forward_distances) != n or len(backward_distances) != n:
  108. raise nx.NetworkXError("DiGraph not strongly connected.")
  109. # take a node a_1 at the maximum distance from the source in G
  110. *_, a_1 = forward_distances
  111. # take a node a_2 at the maximum distance from the source in G_reversed
  112. *_, a_2 = backward_distances
  113. # return the max between the backward eccentricity of a_1 and the forward eccentricity of a_2
  114. return max(nx.eccentricity(G_reversed, a_1), nx.eccentricity(G, a_2))