diff --git a/.gitignore b/.gitignore index 01d1f17..92001f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -src/main/java/h07 - ### VS-Code ### .vscode/ *.code-workspace diff --git a/src/main/java/h07/algorithm/Dijkstra.java b/src/main/java/h07/algorithm/Dijkstra.java new file mode 100644 index 0000000..0f0a304 --- /dev/null +++ b/src/main/java/h07/algorithm/Dijkstra.java @@ -0,0 +1,96 @@ +package h07.algorithm; + +import h07.algebra.Monoid; +import h07.graph.DirectedGraph; +import h07.graph.Path; + +import java.util.*; +import java.util.stream.Stream; + +public class Dijkstra implements ShortestPathsAlgorithm { + + @Override + public Map> shortestPaths(DirectedGraph graph, V startNode, Monoid monoid, Comparator comparator) { + var d = new DijkstraImpl(graph, startNode, monoid, comparator); + d.computeShortestPaths(); + return d.paths; + } + + private class DijkstraImpl { + final DirectedGraph graph; + final V startNode; + final Monoid monoid; + final Comparator comparator; + + final Map> paths; + final Queue queue; + + private DijkstraImpl(DirectedGraph graph, V startNode, Monoid monoid, Comparator comparator) { + this.graph = Objects.requireNonNull(graph, "Argument graph may not be null"); + this.startNode = Objects.requireNonNull(startNode, "Argument startNode may not be null"); + this.monoid = Objects.requireNonNull(monoid, "Argument monoid may not be null"); + this.comparator = Objects.requireNonNull(comparator, "Argument comparator may not be null"); + + if (!graph.getAllNodes().contains(startNode)) { + throw new NoSuchElementException("Node startNode not in graph"); + } + + this.paths = getPathMap(); + this.queue = getQueue(); + } + + private HashMap> getPathMap() { + var paths = new HashMap>(); + paths.put(startNode, Path.of(startNode)); + return paths; + } + + private PriorityQueue getQueue() { + var queue = new PriorityQueue(Comparator.comparing(n -> + sumPathDistances(paths.get(n)), comparator)); + queue.add(startNode); + return queue; + } + + private A sumPathDistances(Path path) { + return Stream.iterate( + path.traverser(), + Path.Traverser::hasNextNode, + t -> { t.walkToNextNode(); return t; }) + .map(Path.Traverser::getDistanceToNextNode) + .reduce(monoid.zero(), monoid::add); + } + + public void computeShortestPaths() { + while (!queue.isEmpty()) { + var node = queue.remove(); + + for (var child : graph.getChildrenForNode(node)) { + var path = getPath(node, child); + + if (isShortestPath(child, path)) { + paths.put(child, path); + queue.remove(child); + queue.add(child); + } + } + } + } + + private boolean isShortestPath(V child, Path path) { + if (!paths.containsKey(child)) { + return true; + } + + return 0 > comparator.compare( + sumPathDistances(path), + sumPathDistances(paths.get(child))); + } + + private Path getPath(V node, V child) { + return paths + .get(node) + .concat(child, graph.getArcWeightBetween(node, child)); + } + } +} \ No newline at end of file diff --git a/src/main/java/h07/graph/DirectedGraphImpl.java b/src/main/java/h07/graph/DirectedGraphImpl.java new file mode 100644 index 0000000..46d885c --- /dev/null +++ b/src/main/java/h07/graph/DirectedGraphImpl.java @@ -0,0 +1,91 @@ +package h07.graph; + +import java.util.*; + +class DirectedGraphImpl implements DirectedGraph { + + private final Map> theGraph; + + DirectedGraphImpl() { + theGraph = new HashMap<>(); + } + + @Override + public Collection getAllNodes() { + return Set.copyOf(theGraph.keySet()); + } + + @Override + public Collection getChildrenForNode(V node) { + return Set.copyOf(getEdges(node).keySet()); + } + + private Map getEdges(V node) { + var edges = theGraph.get(Objects.requireNonNull(node)); + if (edges == null) { + throw new NoSuchElementException("Node not in graph: " + node); + } + return edges; + } + + @Override + public A getArcWeightBetween(V from, V to) { + checkToNode(to); + + var arc = getEdges(from).get(to); + if (arc == null) { + throw new NoSuchElementException("No edge from " + from + " to " + to); + } + return arc; + } + + @Override + public void addNode(V node) { + Objects.requireNonNull(node); + if (theGraph.containsKey(node)) { + throw new IllegalArgumentException("Node already exists in graph: " + node); + } + theGraph.put(node, new HashMap<>()); + } + + @Override + public void removeNode(V node) { + Objects.requireNonNull(node); + if (theGraph.remove(node) == null) { + throw new NoSuchElementException("Node does not exist in graph: " + node); + } + for (var edges : theGraph.values()) { + edges.remove(node); + } + } + + @Override + public void connectNodes(V from, A weight, V to) { + Objects.requireNonNull(from); + Objects.requireNonNull(weight); + checkToNode(to); + var edges = getEdges(from); + if (edges.containsKey(to)) { + throw new IllegalArgumentException("Edge from " + from + " to " + to + " already exists"); + } + edges.put(to, weight); + } + + @Override + public void disconnectNodes(V from, V to) { + Objects.requireNonNull(from); + checkToNode(to); + + var edges = getEdges(from); + if (edges.remove(to) == null) { + throw new NoSuchElementException("No edge from " + from + " to " + to); + } + } + + private void checkToNode(V to) { + Objects.requireNonNull(to); + if (!theGraph.containsKey(to)) { + throw new NoSuchElementException("Node not in graph: " + to); + } + } +} diff --git a/src/main/java/h07/graph/Path.java b/src/main/java/h07/graph/Path.java index ad7198e..17cbe9c 100644 --- a/src/main/java/h07/graph/Path.java +++ b/src/main/java/h07/graph/Path.java @@ -95,11 +95,8 @@ public interface Path extends Iterable { * @throws NullPointerException falls der Knoten {@code null} ist */ static Path of(V v1) { - throw new UnsupportedOperationException("Noch nicht implementiert."); - /* Objects.requireNonNull(v1, "Der Knoten eines Pfades darf nicht null sein"); return new PathImpl<>(v1); - */ } /** diff --git a/src/main/java/h07/graph/PathImpl.java b/src/main/java/h07/graph/PathImpl.java new file mode 100644 index 0000000..f17809e --- /dev/null +++ b/src/main/java/h07/graph/PathImpl.java @@ -0,0 +1,120 @@ +package h07.graph; + +import java.util.*; + +class PathImpl implements Path { + + private final List thePath; + + public PathImpl(V v1) { + Objects.requireNonNull(v1, "Argument v1 may not be null"); + this.thePath = List.of(new Node(v1, null)); + } + + public PathImpl(List thePath) { + this.thePath = thePath; + } + + @Override + public Traverser traverser() { + return new TraverserImp(); + } + + @Override + public Path concat(V node, A distance) { + Objects.requireNonNull(node, "Argument node may not be null"); + Objects.requireNonNull(distance, "Argument distance may not be null"); + + var list = new ArrayList<>(thePath); + list.add(new Node(node, distance)); + return new PathImpl(list); + } + + @Override + public Iterator iterator() { + var iter = thePath.iterator(); + return new Iterator<>() { + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public V next() { + return iter.next().node; + } + }; + } + + @Override + public String toString() { + var sb = new StringBuilder(); + var trav = traverser(); + + while(trav.hasNextNode()) { + sb.append(trav.getCurrentNode()) + .append(" -[") + .append(trav.getDistanceToNextNode()) + .append("]-> "); + trav.walkToNextNode(); + } + + return sb + .append(trav.getCurrentNode()) + .toString(); + } + + private class Node { + final V node; + final A distance; + + private Node(V node, A distance) { + this.node = node; + this.distance = distance; + } + } + + private class TraverserImp implements Traverser { + Node current; + Node next; + Iterator iter; + + TraverserImp() { + this.iter = thePath.iterator(); + if (iter.hasNext()) { + current = iter.next(); + } + if (iter.hasNext()) { + next = iter.next(); + } + } + + @Override + public V getCurrentNode() { + return current.node; + } + + @Override + public A getDistanceToNextNode() { + if (next == null) { + throw new IllegalStateException("No next node present"); + } + return next.distance; + } + + @Override + public void walkToNextNode() { + if (next == null) { + throw new NoSuchElementException("No next node present"); + } + + current = next; + next = iter.hasNext() ? iter.next() : null; + } + + @Override + public boolean hasNextNode() { + return next != null; + } + } +}