See the previous and initial iteration.
Terminology
Given an undirected graph \$G = (V, E)\$, the eccentricity of a node \$u \in V\$, \$e(u)\$, is the maximum length (number of edges) of a shortest path from \$u\$ to the furthermost node from \$u\$. The graph radius is the smallest eccentricity over all its nodes, or namely
$$ \min_{u \in V} e(u). $$
Explanation of the graph radius concept
What happens here is that you iterate over all nodes in the graph, and for each iterated node \$u \in V\$, you run breadth-first search starting from \$u\$; your aim here is to find the largest distance from \$u\$ to any other node in the graph. Record all those distances associated with every iterated node, and finally return the minimum of them.
What's new
The following snippet demonstrates two brute-force algorithms for computing graph radii. However, I was able to optimize the second radius finder by the following heuristic: keep track of the smallest eccentricity so far (call it, say, \$e\$) and whenever we are running yet another BFS from a node, if we reach a distance at least equal to \$e\$, we terminate search as we are not able to improve \$e\$.
Performance
I get the following figures:
Seed: 70678049304775
GraphRadiusFinder - time elapsed: 12449.61 milliseconds, radius: 4.
PruningGraphRadiusFinder - time elapsed: 1709.66 milliseconds, radius: 4.
Code
AbstractGraphRadiusFinder.java:
package net.coderodde.graph.radius; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import net.coderodde.graph.UndirectedGraphNode; /** * This abstract class defines the API for graph radius finder algorithms and * provides some shared functionality. * * @author Rodion "rodde" Efremov * @version 1.6 (Nov 21, 2015) */ public abstract class AbstractGraphRadiusFinder { protected final Deque<UndirectedGraphNode> queue = new ArrayDeque<>(); protected final Map<UndirectedGraphNode, Integer> distanceMap = new HashMap<>(); protected final List<UndirectedGraphNode> connectedComponent; public abstract int findRadius(); protected AbstractGraphRadiusFinder( UndirectedGraphNode connectedComponentRepresentative) { Objects.requireNonNull(connectedComponentRepresentative, "The connected component representative node " + "is null."); this.connectedComponent = expand(connectedComponentRepresentative); } protected List<UndirectedGraphNode> expand(UndirectedGraphNode node) { queue.add(node); distanceMap.put(node, 0); while (!queue.isEmpty()) { UndirectedGraphNode current = queue.removeFirst(); for (UndirectedGraphNode child : current.children()) { if (!distanceMap.containsKey(child)) { distanceMap.put(child, 0); queue.addLast(child); } } } return new ArrayList<>(distanceMap.keySet()); } }
GraphRadiusFinder.java:
package net.coderodde.graph.radius; import net.coderodde.graph.UndirectedGraphNode; /** * This class implements a brute-force algorithm for computing the radius of * an unweighted graph. The graph radius in question is defined as follows: * for each graph node, run breadth-first search and return the maximum length * from the source node to any other node. Gather the same number over all of * the nodes and then pick the smallest of them. * * @author Rodion "rodde" Efremov * @version 1.6 (Nov 20, 2015) */ public class GraphRadiusFinder extends AbstractGraphRadiusFinder { public GraphRadiusFinder( UndirectedGraphNode connectedComponentRepresentative) { super(connectedComponentRepresentative); } @Override public int findRadius() { int radius = Integer.MAX_VALUE; for (UndirectedGraphNode node : connectedComponent) { int tentativeRadius = getMaximumDistanceFrom(node); if (radius > tentativeRadius) { radius = tentativeRadius; } } return radius; } private int getMaximumDistanceFrom(UndirectedGraphNode node) { queue.clear(); distanceMap.clear(); queue.addLast(node); distanceMap.put(node, 0); int maximumDistance = 0; while (!queue.isEmpty()) { UndirectedGraphNode current = queue.removeFirst(); for (UndirectedGraphNode child : current.children()) { if (!distanceMap.containsKey(child)) { int distance = distanceMap.get(current) + 1; distanceMap.put(child, distance); queue.addLast(child); if (maximumDistance < distance) { maximumDistance = distance; } } } } return maximumDistance; } }
PruningGraphRadiusFinder.java:
package net.coderodde.graph.radius; import net.coderodde.graph.UndirectedGraphNode; /** * This class implements a brute-force algorithm for computing the radius of * an unweighted graph. The graph radius in question is defined as follows: * for each graph node, run breadth-first search and return the maximum length * from the source node to any other node. Gather the same number over all of * the nodes and then pick the smallest of them. * <p> * This implementation, however, keeps track of the minimum node eccentricity, * and prunes all the nodes whose distance from the initial node is equal or * larger than the cached eccentricity. * * @author Rodion "rodde" Efremov * @version 1.6 (Nov 20, 2015) */ public class PruningGraphRadiusFinder extends AbstractGraphRadiusFinder { public PruningGraphRadiusFinder( UndirectedGraphNode connectedComponentRepresentative) { super(connectedComponentRepresentative); } @Override public int findRadius() { int smallestRadius = Integer.MAX_VALUE; for (UndirectedGraphNode node : connectedComponent) { int tentativeRadius = getMaximumDistanceFrom(node, smallestRadius); if (smallestRadius > tentativeRadius) { smallestRadius = tentativeRadius; } } return smallestRadius; } private int getMaximumDistanceFrom(UndirectedGraphNode node, int smallestRadius) { queue.clear(); distanceMap.clear(); queue.addLast(node); distanceMap.put(node, 0); int maximumDistance = 0; while (!queue.isEmpty()) { UndirectedGraphNode current = queue.removeFirst(); for (UndirectedGraphNode child : current.children()) { if (!distanceMap.containsKey(child)) { int distance = distanceMap.get(current) + 1; if (distance == smallestRadius) { return distance; } distanceMap.put(child, distance); queue.addLast(child); if (maximumDistance < distance) { maximumDistance = distance; } } } } return maximumDistance; } }
UndirectedGraphNode.java:
package net.coderodde.graph; import java.util.Collections; import java.util.HashSet; import java.util.Objects; import java.util.Set; /** * This class implements an unweighted graph node. * * @author Rodion "rodde" Efremov * @version 1.6 (Nov 20, 2015) */ public class UndirectedGraphNode { private final String name; private final Set<UndirectedGraphNode> neighbors = new HashSet<>(); public UndirectedGraphNode(String name) { this.name = Objects.requireNonNull(name, "The node name is null."); } public void addNeighbor(UndirectedGraphNode neighbor) { this.neighbors.add(neighbor); neighbor.neighbors.add(this); } public Set<UndirectedGraphNode> children() { return Collections.unmodifiableSet(neighbors); } @Override public int hashCode() { return name.hashCode(); } @Override public boolean equals(Object o) { if (o == null) { return false; } if (o.getClass() != getClass()) { return false; } return name.equals(((UndirectedGraphNode) o).name); } }
PerformanceDemo.java:
import java.util.ArrayList; import java.util.List; import java.util.Random; import net.coderodde.graph.UndirectedGraphNode; import net.coderodde.graph.radius.AbstractGraphRadiusFinder; import net.coderodde.graph.radius.GraphRadiusFinder; import net.coderodde.graph.radius.PruningGraphRadiusFinder; public class PerformanceDemo { public static void main(String[] args) { int NODES = 3000; int EDGES = 25000; long seed = System.nanoTime(); Random random = new Random(seed); List<UndirectedGraphNode> graph = buildRandomGraph(NODES, EDGES, random); System.out.println("Seed: " + seed); profile(new GraphRadiusFinder(graph.get(0))); profile(new PruningGraphRadiusFinder(graph.get(0))); } private static void profile(AbstractGraphRadiusFinder finder) { long startTime = System.nanoTime(); int radius = finder.findRadius(); long endTime = System.nanoTime(); System.out.printf("%s - time elapsed: " + "%.2f milliseconds, radius: %d.\n", finder.getClass().getSimpleName(), 1.0 * (endTime - startTime) / 1e6, radius); } private static List<UndirectedGraphNode> buildRandomGraph(int nodes, int edges, Random random) { List<UndirectedGraphNode> nodeList = new ArrayList<>(nodes); for (int i = 0; i < nodes; ++i) { nodeList.add(new UndirectedGraphNode("" + i)); } for (int i = 0; i < edges; ++i) { choose(nodeList, random).addNeighbor(choose(nodeList, random)); } return nodeList; } private static <T> T choose(List<T> list, Random random) { return list.get(random.nextInt(list.size())); } }
Anything to improve here?