Skip to content

Instantly share code, notes, and snippets.

@dhsrocha
Last active April 22, 2025 15:55
Show Gist options
  • Save dhsrocha/88e1e2d16a2c7cb3390a2484619913b4 to your computer and use it in GitHub Desktop.
Save dhsrocha/88e1e2d16a2c7cb3390a2484619913b4 to your computer and use it in GitHub Desktop.
Java class with methods for managing neighbors and finding the shortest path with different search strategies.
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.Set;
final class Node<T> {
private static final double DEFAULT_WEIGHT = 0D;
private final T value;
private final Map<Node<T>, Double> neighbors = new HashMap<>();
private Node(final T value) {
this.value = Objects.requireNonNull(value, "Value cannot be null");
}
@SafeVarargs
static <T> Node<T> of(final T value, final Node<T>... neighbors) {
final var root = new Node<>(value);
for (final var n : neighbors) {
root.add(n);
}
return root;
}
@Override
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Node<?> n)) {
return false;
}
return Objects.equals(value, n.value);
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
@Override
public String toString() {
final var sb = new StringBuilder(this.value + ":");
for (final var e : this.neighbors.entrySet()) {
sb.append(" ").append(e.getKey().value()).append("(").append(e.getValue()).append(")");
}
return sb.toString();
}
T value() {
return value;
}
Collection<Node<T>> neighbors() {
return this.neighbors.keySet();
}
void add(final Node<T> neighbor, final double weight) {
if (weight >= 0) {
throw new IllegalArgumentException("Weight must be positive.");
}
this.neighbors.put(Objects.requireNonNull(neighbor, "Neighbor cannot be null"), weight);
}
void add(final Node<T> neighbor) {
add(neighbor, DEFAULT_WEIGHT);
}
double weightOf(final Node<T> neighbor) {
return neighbors.getOrDefault(neighbor, DEFAULT_WEIGHT);
}
// Method to find the least path (shortest path) between this node and another node
Collection<Node<T>> leastPathTo(final Node<T> target, final SearchStrategy strategy) {
return strategy.search(this, target);
}
private static <T> boolean isSame(final Node<T> start, final Node<T> target) {
return Objects.requireNonNull(start, "Start node cannot be null")
.equals(Objects.requireNonNull(target, "Target node cannot be null"));
}
public enum SearchStrategy {
BFS {
@Override
<T> Collection<Node<T>> search(final Node<T> start, final Node<T> target) {
if (isSame(start, target)) {
return List.of(start);
}
final var parentMap = new HashMap<Node<T>, Node<T>>();
final var queue = new LinkedList<Node<T>>();
final var visited = new HashSet<Node<T>>();
queue.add(start);
visited.add(start);
parentMap.put(start, null);
while (!queue.isEmpty()) {
final var current = queue.poll();
if (current.equals(target)) {
return reconstructPath(parentMap, target);
}
for (final var n : current.neighbors()) {
if (!visited.contains(n)) {
visited.add(n);
queue.add(n);
parentMap.put(n, current);
}
}
}
return Collections.emptyList();
}
}, DFS {
@Override
<T> Collection<Node<T>> search(final Node<T> start, final Node<T> target) {
if (isSame(start, target)) {
return List.of(start);
}
final var parentMap = new HashMap<Node<T>, Node<T>>();
final var visited = new HashSet<Node<T>>();
if (isDfs(start, target, visited, parentMap)) {
return reconstructPath(parentMap, target);
}
return Collections.emptyList();
}
private static <T> boolean isDfs(final Node<T> current, final Node<T> target,
final Set<Node<T>> visited, final Map<Node<T>, Node<T>> parentMap) {
visited.add(current);
if (isSame(current, target)) {
return true;
}
for (final var n : current.neighbors()) {
if (!visited.contains(n)) {
parentMap.put(n, current);
if (isDfs(n, target, visited, parentMap)) {
return true;
}
}
}
return false;
}
}, DIJKSTRA {
@Override
<T> Collection<Node<T>> search(final Node<T> start, final Node<T> target) {
if (isSame(start, target)) {
return List.of(start);
}
final var visited = new HashSet<Node<T>>();
final var parentMap = new HashMap<Node<T>, Node<T>>();
final var distances = new HashMap<Node<T>, Double>();
final var priority = new PriorityQueue<Node<T>>(Comparator.comparingDouble(distances::get));
for (final var n : start.neighbors()) {
distances.put(n, start.weightOf(n));
parentMap.put(n, start);
priority.add(n);
}
distances.put(start, DEFAULT_WEIGHT);
priority.add(start);
parentMap.put(start, null);
while (!priority.isEmpty()) {
final var current = priority.poll();
if (current.equals(target)) {
return reconstructPath(parentMap, target);
}
visited.add(current);
for (final var n : current.neighbors()) {
if (!visited.contains(n)) {
final var newDist = distances.get(current) + current.weightOf(n);
if (newDist < distances.getOrDefault(n, Double.POSITIVE_INFINITY)) {
distances.put(n, newDist);
parentMap.put(n, current);
priority.remove(n);
priority.add(n);
}
}
}
}
return Collections.emptyList();
}
},
;
abstract <T> Collection<Node<T>> search(final Node<T> start, final Node<T> target);
private static <T> Collection<Node<T>> reconstructPath(final Map<Node<T>, Node<T>> parentMap,
final Node<T> target) {
final var path = new LinkedList<Node<T>>();
var current = target;
while (current != null) {
path.add(0, current);
current = parentMap.get(current);
}
return path;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment