Compare commits
10 Commits
7e44d0fb3f
...
0d8459c6f7
Author | SHA1 | Date | |
---|---|---|---|
0d8459c6f7 | |||
40c576280e | |||
32c23e115c | |||
6d4a8b26e6 | |||
f1303fe37b | |||
d8b5a76fb0 | |||
5c4ce8e3af | |||
37c78cf5a5 | |||
da5b0fd5b3 | |||
bb7b48e6e6 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,3 @@
|
|||||||
src/main/java/h07
|
|
||||||
|
|
||||||
### VS-Code ###
|
### VS-Code ###
|
||||||
.vscode/
|
.vscode/
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
@ -45,6 +45,15 @@ Startknoten (grün) auswählen löscht Pfäde.
|
|||||||
- Escape, `Q`:
|
- Escape, `Q`:
|
||||||
Beenden
|
Beenden
|
||||||
|
|
||||||
|
- `U`, `I`:
|
||||||
|
Gewichtung für die nächste Kante verkleinern/vergrößern
|
||||||
|
|
||||||
|
- `O`:
|
||||||
|
Gewichtung für die nächste Kante auf 1 zurücksetzten
|
||||||
|
|
||||||
|
- `F`:
|
||||||
|
Berechne oder verstecke den minimalen Spannbaum (blau)
|
||||||
|
|
||||||
## Wenn Knoten ausgewählt (rot)
|
## Wenn Knoten ausgewählt (rot)
|
||||||
|
|
||||||
- WASD:
|
- WASD:
|
||||||
|
@ -75,6 +75,19 @@ public class Canvas extends JPanel {
|
|||||||
r.run();
|
r.run();
|
||||||
g.scale(1/scale, 1/scale);
|
g.scale(1/scale, 1/scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void text(double x, double y, String text) {
|
||||||
|
FontMetrics metrics = g.getFontMetrics(g.getFont());
|
||||||
|
int d = metrics.getAscent();
|
||||||
|
g.drawString(text, round(x), round(y) + d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void textSize(int size) {
|
||||||
|
var f = g.getFont();
|
||||||
|
g.setFont(new Font(f.getName(), Font.PLAIN, size));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,4 +23,8 @@ public interface Drawable {
|
|||||||
void translated(double x, double y, Runnable r);
|
void translated(double x, double y, Runnable r);
|
||||||
|
|
||||||
void scaled(double scale, Runnable r);
|
void scaled(double scale, Runnable r);
|
||||||
|
|
||||||
|
void text(double x, double y, String text);
|
||||||
|
|
||||||
|
void textSize(int size);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package de.oshgnacknak.gruphi;
|
package de.oshgnacknak.gruphi;
|
||||||
|
|
||||||
|
import h07.algorithm.MinimumSpanningForestAlgorithm;
|
||||||
import h07.algorithm.ShortestPathsAlgorithm;
|
import h07.algorithm.ShortestPathsAlgorithm;
|
||||||
import h07.graph.DirectedGraphFactory;
|
import h07.graph.DirectedGraphFactory;
|
||||||
|
|
||||||
@ -38,4 +39,9 @@ public interface Gruphi {
|
|||||||
* @return A {@link ShortestPathsAlgorithm} for the path finding
|
* @return A {@link ShortestPathsAlgorithm} for the path finding
|
||||||
*/
|
*/
|
||||||
ShortestPathsAlgorithm<Node, Double> getShortestPathsAlgorithm();
|
ShortestPathsAlgorithm<Node, Double> getShortestPathsAlgorithm();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A {@link MinimumSpanningForestAlgorithm} for the spanning tree calculation
|
||||||
|
*/
|
||||||
|
MinimumSpanningForestAlgorithm<Node, Double> getMinimumSpanningForestAlgorithm();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package de.oshgnacknak.gruphi;
|
package de.oshgnacknak.gruphi;
|
||||||
|
|
||||||
|
import h07.algorithm.MinimumSpanningForestAlgorithm;
|
||||||
import h07.algorithm.ShortestPathsAlgorithm;
|
import h07.algorithm.ShortestPathsAlgorithm;
|
||||||
import h07.graph.DirectedGraphFactory;
|
import h07.graph.DirectedGraphFactory;
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ public class GruphiBuilder {
|
|||||||
private BiPredicate<Node, Node> neighbourPredicate;
|
private BiPredicate<Node, Node> neighbourPredicate;
|
||||||
private DirectedGraphFactory<Node, Double> directedGraphFactory;
|
private DirectedGraphFactory<Node, Double> directedGraphFactory;
|
||||||
private ShortestPathsAlgorithm<Node, Double> shortestPathsAlgorithm;
|
private ShortestPathsAlgorithm<Node, Double> shortestPathsAlgorithm;
|
||||||
|
private MinimumSpanningForestAlgorithm<Node, Double> minimumSpanningForestAlgorithm;
|
||||||
|
|
||||||
public GruphiBuilder setFrameDelay(Long frameDelay) {
|
public GruphiBuilder setFrameDelay(Long frameDelay) {
|
||||||
this.frameDelay = frameDelay;
|
this.frameDelay = frameDelay;
|
||||||
@ -43,7 +45,12 @@ public class GruphiBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GruphiBuilder setMinimumSpanningForestAlgorithm(MinimumSpanningForestAlgorithm<Node, Double> minimumSpanningForestAlgorithm) {
|
||||||
|
this.minimumSpanningForestAlgorithm = minimumSpanningForestAlgorithm;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public GruphiImpl createGruphi() {
|
public GruphiImpl createGruphi() {
|
||||||
return new GruphiImpl(frameDelay, velocity, gridSpacing, neighbourPredicate, directedGraphFactory, shortestPathsAlgorithm);
|
return new GruphiImpl(frameDelay, velocity, gridSpacing, neighbourPredicate, directedGraphFactory, shortestPathsAlgorithm, minimumSpanningForestAlgorithm);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,6 +31,8 @@ class GruphiFrame extends JFrame {
|
|||||||
|
|
||||||
private final DirectedGraph<Node, Double> graph;
|
private final DirectedGraph<Node, Double> graph;
|
||||||
|
|
||||||
|
private DirectedGraph<Node, Double> forest;
|
||||||
|
|
||||||
private final MazeGenerator<Node> mazeGenerator;
|
private final MazeGenerator<Node> mazeGenerator;
|
||||||
private final Gruphi gruphi;
|
private final Gruphi gruphi;
|
||||||
|
|
||||||
@ -44,6 +46,7 @@ class GruphiFrame extends JFrame {
|
|||||||
private Map<Node, Path<Node, Double>> paths = null;
|
private Map<Node, Path<Node, Double>> paths = null;
|
||||||
private boolean running = true;
|
private boolean running = true;
|
||||||
private boolean nuggets = false;
|
private boolean nuggets = false;
|
||||||
|
private double weight = 1.0;
|
||||||
|
|
||||||
GruphiFrame(Gruphi gruphi) {
|
GruphiFrame(Gruphi gruphi) {
|
||||||
super("Gruphi - The Graph GUI - By Osh");
|
super("Gruphi - The Graph GUI - By Osh");
|
||||||
@ -111,6 +114,7 @@ class GruphiFrame extends JFrame {
|
|||||||
}
|
}
|
||||||
graph.removeNode(selected);
|
graph.removeNode(selected);
|
||||||
selected = null;
|
selected = null;
|
||||||
|
forest = null;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
@ -141,6 +145,22 @@ class GruphiFrame extends JFrame {
|
|||||||
generatePaths();
|
generatePaths();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
case KeyEvent.VK_I: {
|
||||||
|
weight = Math.min(weight*1.01, 10);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case KeyEvent.VK_U: {
|
||||||
|
weight = Math.max(weight/1.01, 0.1);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case KeyEvent.VK_O: {
|
||||||
|
weight = 1.0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case KeyEvent.VK_F: {
|
||||||
|
generateForest();
|
||||||
|
} break;
|
||||||
|
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,6 +206,19 @@ class GruphiFrame extends JFrame {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void generateForest() {
|
||||||
|
if (forest != null) {
|
||||||
|
forest = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var algo = Objects.requireNonNull(
|
||||||
|
gruphi.getMinimumSpanningForestAlgorithm(),
|
||||||
|
"Did you supply a spanning forest implementation");
|
||||||
|
|
||||||
|
forest = algo.minimumSpanningForest(graph, Comparable::compareTo, gruphi.getDirectedGraphFactory());
|
||||||
|
}
|
||||||
|
|
||||||
private void generatePaths() {
|
private void generatePaths() {
|
||||||
clearPaths();
|
clearPaths();
|
||||||
|
|
||||||
@ -212,7 +245,7 @@ class GruphiFrame extends JFrame {
|
|||||||
if (graph.getChildrenForNode(selected).contains(n)) {
|
if (graph.getChildrenForNode(selected).contains(n)) {
|
||||||
graph.disconnectNodes(selected, n);
|
graph.disconnectNodes(selected, n);
|
||||||
} else {
|
} else {
|
||||||
graph.connectNodes(selected, 1.0, n);
|
graph.connectNodes(selected, weight, n);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
selected.pos = v;
|
selected.pos = v;
|
||||||
@ -265,6 +298,7 @@ class GruphiFrame extends JFrame {
|
|||||||
|
|
||||||
private void clearGraph() {
|
private void clearGraph() {
|
||||||
selected = null;
|
selected = null;
|
||||||
|
forest = null;
|
||||||
clearPaths();
|
clearPaths();
|
||||||
for (var node : graph.getAllNodes()) {
|
for (var node : graph.getAllNodes()) {
|
||||||
graph.removeNode(node);
|
graph.removeNode(node);
|
||||||
@ -288,9 +322,12 @@ class GruphiFrame extends JFrame {
|
|||||||
|
|
||||||
public void draw(Drawable d) {
|
public void draw(Drawable d) {
|
||||||
d.fill(Color.BLACK);
|
d.fill(Color.BLACK);
|
||||||
|
|
||||||
d.rect(0, 0, getWidth(), getHeight());
|
d.rect(0, 0, getWidth(), getHeight());
|
||||||
panningAndZooming.draw(d, () ->
|
panningAndZooming.draw(d, () ->
|
||||||
drawNodes(d));
|
drawNodes(d));
|
||||||
|
|
||||||
|
d.text(10, 10, String.format("Weight: %.2f", weight));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawNodes(Drawable d) {
|
private void drawNodes(Drawable d) {
|
||||||
@ -301,6 +338,21 @@ class GruphiFrame extends JFrame {
|
|||||||
drawCells(d);
|
drawCells(d);
|
||||||
|
|
||||||
drawPath(d);
|
drawPath(d);
|
||||||
|
drawForest(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawForest(Drawable d) {
|
||||||
|
if (forest == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
d.strokeWeight(2);
|
||||||
|
d.fill(Color.BLUE);
|
||||||
|
for (var node : forest.getAllNodes()) {
|
||||||
|
for (var child : forest.getChildrenForNode(node)) {
|
||||||
|
d.line(node.pos.x, node.pos.y, child.pos.x, child.pos.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawCells(Drawable d) {
|
private void drawCells(Drawable d) {
|
||||||
@ -323,7 +375,6 @@ class GruphiFrame extends JFrame {
|
|||||||
var path = paths.get(selected);
|
var path = paths.get(selected);
|
||||||
|
|
||||||
d.fill(Color.GREEN);
|
d.fill(Color.GREEN);
|
||||||
d.strokeWeight(2);
|
|
||||||
Node prev = null;
|
Node prev = null;
|
||||||
for (var node : path) {
|
for (var node : path) {
|
||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
@ -336,6 +387,7 @@ class GruphiFrame extends JFrame {
|
|||||||
private void drawConnections(Drawable d) {
|
private void drawConnections(Drawable d) {
|
||||||
for (var node : graph.getAllNodes()) {
|
for (var node : graph.getAllNodes()) {
|
||||||
for (var child : graph.getChildrenForNode(node)) {
|
for (var child : graph.getChildrenForNode(node)) {
|
||||||
|
d.strokeWeight(2 * graph.getArcWeightBetween(node, child));
|
||||||
d.line(node.pos.x, node.pos.y, child.pos.x, child.pos.y);
|
d.line(node.pos.x, node.pos.y, child.pos.x, child.pos.y);
|
||||||
drawConnectionArrowHead(d, node, child);
|
drawConnectionArrowHead(d, node, child);
|
||||||
}
|
}
|
||||||
@ -346,7 +398,7 @@ class GruphiFrame extends JFrame {
|
|||||||
var v = child.pos
|
var v = child.pos
|
||||||
.copy()
|
.copy()
|
||||||
.sub(node.pos);
|
.sub(node.pos);
|
||||||
var r = 4;
|
var r = 4 * graph.getArcWeightBetween(node, child);
|
||||||
var p = v.copy()
|
var p = v.copy()
|
||||||
.setMag(v.mag() - child.radius)
|
.setMag(v.mag() - child.radius)
|
||||||
.add(node.pos);
|
.add(node.pos);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package de.oshgnacknak.gruphi;
|
package de.oshgnacknak.gruphi;
|
||||||
|
|
||||||
|
import h07.algorithm.MinimumSpanningForestAlgorithm;
|
||||||
import h07.algorithm.ShortestPathsAlgorithm;
|
import h07.algorithm.ShortestPathsAlgorithm;
|
||||||
import h07.graph.DirectedGraphFactory;
|
import h07.graph.DirectedGraphFactory;
|
||||||
|
|
||||||
@ -24,17 +25,20 @@ public class GruphiImpl implements Gruphi {
|
|||||||
|
|
||||||
private final ShortestPathsAlgorithm<Node, Double> shortestPathsAlgorithm;
|
private final ShortestPathsAlgorithm<Node, Double> shortestPathsAlgorithm;
|
||||||
|
|
||||||
|
private final MinimumSpanningForestAlgorithm<Node, Double> minimumSpanningForestAlgorithm;
|
||||||
|
|
||||||
public GruphiImpl(Long frameDelay,
|
public GruphiImpl(Long frameDelay,
|
||||||
Double velocity,
|
Double velocity,
|
||||||
Double gridSpacing,
|
Double gridSpacing,
|
||||||
BiPredicate<Node, Node> neighbourPredicate,
|
BiPredicate<Node, Node> neighbourPredicate,
|
||||||
DirectedGraphFactory<Node, Double> directedGraphFactory, ShortestPathsAlgorithm<Node, Double> shortestPathsAlgorithm) {
|
DirectedGraphFactory<Node, Double> directedGraphFactory, ShortestPathsAlgorithm<Node, Double> shortestPathsAlgorithm, MinimumSpanningForestAlgorithm<Node, Double> minimumSpanningForestAlgorithm) {
|
||||||
this.frameDelay = frameDelay == null ? DEFAULT_FRAME_DELAY : frameDelay;
|
this.frameDelay = frameDelay == null ? DEFAULT_FRAME_DELAY : frameDelay;
|
||||||
this.velocity = velocity == null ? DEFAULT_VELOCITY : velocity;
|
this.velocity = velocity == null ? DEFAULT_VELOCITY : velocity;
|
||||||
this.gridSpacing = gridSpacing == null ? DEFAULT_GRID_SPACING : gridSpacing;
|
this.gridSpacing = gridSpacing == null ? DEFAULT_GRID_SPACING : gridSpacing;
|
||||||
this.neighbourPredicate = neighbourPredicate == null ? this::defaultAreNeighbours : neighbourPredicate;
|
this.neighbourPredicate = neighbourPredicate == null ? this::defaultAreNeighbours : neighbourPredicate;
|
||||||
this.directedGraphFactory = Objects.requireNonNull(directedGraphFactory, "A directedGraphFactory must be set");
|
this.directedGraphFactory = Objects.requireNonNull(directedGraphFactory, "A directedGraphFactory must be set");
|
||||||
this.shortestPathsAlgorithm = shortestPathsAlgorithm;
|
this.shortestPathsAlgorithm = shortestPathsAlgorithm;
|
||||||
|
this.minimumSpanningForestAlgorithm = minimumSpanningForestAlgorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean defaultAreNeighbours(Node a, Node b) {
|
private boolean defaultAreNeighbours(Node a, Node b) {
|
||||||
@ -70,4 +74,9 @@ public class GruphiImpl implements Gruphi {
|
|||||||
public ShortestPathsAlgorithm<Node, Double> getShortestPathsAlgorithm() {
|
public ShortestPathsAlgorithm<Node, Double> getShortestPathsAlgorithm() {
|
||||||
return shortestPathsAlgorithm;
|
return shortestPathsAlgorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MinimumSpanningForestAlgorithm<Node, Double> getMinimumSpanningForestAlgorithm() {
|
||||||
|
return minimumSpanningForestAlgorithm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
package de.oshgnacknak.gruphi;
|
package de.oshgnacknak.gruphi;
|
||||||
|
|
||||||
|
import h07.algorithm.Dijkstra;
|
||||||
|
import h07.algorithm.Kruskal;
|
||||||
|
import h07.graph.DirectedGraphFactory;
|
||||||
|
|
||||||
public class GruphiMain {
|
public class GruphiMain {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
var gruphi = new GruphiBuilder()
|
var gruphi = new GruphiBuilder()
|
||||||
// .setDirectedGraphFactory(someFactory)
|
.setDirectedGraphFactory(DirectedGraphFactory.defaultFactory())
|
||||||
// .setShortestPathsAlgorithm(somePathFinder)
|
.setShortestPathsAlgorithm(new Dijkstra<>())
|
||||||
|
.setMinimumSpanningForestAlgorithm(new Kruskal<>())
|
||||||
.createGruphi();
|
.createGruphi();
|
||||||
|
|
||||||
var frame = new GruphiFrame(gruphi);
|
var frame = new GruphiFrame(gruphi);
|
||||||
|
@ -40,8 +40,8 @@ public class MazeGenerator<V> {
|
|||||||
stack.push(current);
|
stack.push(current);
|
||||||
var neighbour = unvisited.get(random.nextInt(unvisited.size()));
|
var neighbour = unvisited.get(random.nextInt(unvisited.size()));
|
||||||
|
|
||||||
graph.connectNodes(current, random.nextDouble(), neighbour);
|
graph.connectNodes(current, 1.0, neighbour);
|
||||||
graph.connectNodes(neighbour, random.nextDouble(), current);
|
graph.connectNodes(neighbour, 1.0, current);
|
||||||
|
|
||||||
visited.add(neighbour);
|
visited.add(neighbour);
|
||||||
stack.push(neighbour);
|
stack.push(neighbour);
|
||||||
|
96
src/main/java/h07/algorithm/Dijkstra.java
Normal file
96
src/main/java/h07/algorithm/Dijkstra.java
Normal file
@ -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<V, A> implements ShortestPathsAlgorithm<V, A> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<V, Path<V, A>> shortestPaths(DirectedGraph<V, A> graph, V startNode, Monoid<A> monoid, Comparator<? super A> comparator) {
|
||||||
|
var d = new DijkstraImpl(graph, startNode, monoid, comparator);
|
||||||
|
d.computeShortestPaths();
|
||||||
|
return d.paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DijkstraImpl {
|
||||||
|
final DirectedGraph<V, A> graph;
|
||||||
|
final V startNode;
|
||||||
|
final Monoid<A> monoid;
|
||||||
|
final Comparator<? super A> comparator;
|
||||||
|
|
||||||
|
final Map<V, Path<V, A>> paths;
|
||||||
|
final Queue<V> queue;
|
||||||
|
|
||||||
|
private DijkstraImpl(DirectedGraph<V, A> graph, V startNode, Monoid<A> monoid, Comparator<? super A> 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<V, Path<V, A>> getPathMap() {
|
||||||
|
var paths = new HashMap<V, Path<V, A>>();
|
||||||
|
paths.put(startNode, Path.of(startNode));
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PriorityQueue<V> getQueue() {
|
||||||
|
var queue = new PriorityQueue<V>(Comparator.comparing(n ->
|
||||||
|
sumPathDistances(paths.get(n)), comparator));
|
||||||
|
queue.add(startNode);
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private A sumPathDistances(Path<?, A> 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<V, A> path) {
|
||||||
|
if (!paths.containsKey(child)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0 > comparator.compare(
|
||||||
|
sumPathDistances(path),
|
||||||
|
sumPathDistances(paths.get(child)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path<V, A> getPath(V node, V child) {
|
||||||
|
return paths
|
||||||
|
.get(node)
|
||||||
|
.concat(child, graph.getArcWeightBetween(node, child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
src/main/java/h07/algorithm/Kruskal.java
Normal file
64
src/main/java/h07/algorithm/Kruskal.java
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package h07.algorithm;
|
||||||
|
|
||||||
|
import h07.graph.DirectedGraph;
|
||||||
|
import h07.graph.DirectedGraphFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class Kruskal<V, A> implements MinimumSpanningForestAlgorithm<V, A> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DirectedGraph<V, A> minimumSpanningForest(DirectedGraph<V, A> graph, Comparator<? super A> comparator, DirectedGraphFactory<V, A> factory) {
|
||||||
|
var output = factory.createDirectedGraph();
|
||||||
|
var sets = new ArrayList<Set<V>>();
|
||||||
|
|
||||||
|
for (V node : graph.getAllNodes()) {
|
||||||
|
output.addNode(node);
|
||||||
|
|
||||||
|
var set = new HashSet<V>();
|
||||||
|
set.add(node);
|
||||||
|
sets.add(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
var egdes = graph.getAllNodes()
|
||||||
|
.stream()
|
||||||
|
.flatMap(n ->
|
||||||
|
graph.getChildrenForNode(n)
|
||||||
|
.stream()
|
||||||
|
.map(c ->
|
||||||
|
new Pair<>(n, c)))
|
||||||
|
.sorted(Comparator.comparing(
|
||||||
|
p -> graph.getArcWeightBetween(p.fst, p.snd),
|
||||||
|
comparator));
|
||||||
|
|
||||||
|
egdes.forEach(p -> {
|
||||||
|
var s1 = sets
|
||||||
|
.stream()
|
||||||
|
.filter(s -> s.contains(p.fst))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
|
||||||
|
if (s1.contains(p.snd)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var s2 = sets
|
||||||
|
.stream()
|
||||||
|
.filter(s -> s.contains(p.snd))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
|
||||||
|
s1.addAll(s2);
|
||||||
|
sets.remove(s2);
|
||||||
|
|
||||||
|
var arc = graph.getArcWeightBetween(p.fst, p.snd);
|
||||||
|
output.connectNodes(p.fst, arc, p.snd);
|
||||||
|
output.connectNodes(p.snd, arc, p.fst);
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package h07.algorithm;
|
||||||
|
|
||||||
|
import h07.graph.DirectedGraph;
|
||||||
|
import h07.graph.DirectedGraphFactory;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
public interface MinimumSpanningForestAlgorithm<V, A> {
|
||||||
|
|
||||||
|
DirectedGraph<V, A> minimumSpanningForest(DirectedGraph<V, A> graph, Comparator<? super A> comparator, DirectedGraphFactory<V, A> factory);
|
||||||
|
}
|
11
src/main/java/h07/algorithm/Pair.java
Normal file
11
src/main/java/h07/algorithm/Pair.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package h07.algorithm;
|
||||||
|
|
||||||
|
public class Pair<T, S> {
|
||||||
|
public T fst;
|
||||||
|
public S snd;
|
||||||
|
|
||||||
|
public Pair(T fst, S snd) {
|
||||||
|
this.fst = fst;
|
||||||
|
this.snd = snd;
|
||||||
|
}
|
||||||
|
}
|
@ -13,4 +13,8 @@ public interface DirectedGraphFactory<V, A> {
|
|||||||
* @return der {@code DirectedGraph}, der von dieses Fabrik erzeugt wird.
|
* @return der {@code DirectedGraph}, der von dieses Fabrik erzeugt wird.
|
||||||
*/
|
*/
|
||||||
DirectedGraph<V, A> createDirectedGraph();
|
DirectedGraph<V, A> createDirectedGraph();
|
||||||
|
|
||||||
|
static <V, A> DirectedGraphFactory<V, A> defaultFactory() {
|
||||||
|
return DirectedGraphImpl::new;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
91
src/main/java/h07/graph/DirectedGraphImpl.java
Normal file
91
src/main/java/h07/graph/DirectedGraphImpl.java
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package h07.graph;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
class DirectedGraphImpl<V, A> implements DirectedGraph<V, A> {
|
||||||
|
|
||||||
|
private final Map<V, Map<V, A>> theGraph;
|
||||||
|
|
||||||
|
DirectedGraphImpl() {
|
||||||
|
theGraph = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> getAllNodes() {
|
||||||
|
return Set.copyOf(theGraph.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> getChildrenForNode(V node) {
|
||||||
|
return Set.copyOf(getEdges(node).keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<V, A> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -95,11 +95,8 @@ public interface Path<V, A> extends Iterable<V> {
|
|||||||
* @throws NullPointerException falls der Knoten {@code null} ist
|
* @throws NullPointerException falls der Knoten {@code null} ist
|
||||||
*/
|
*/
|
||||||
static <V, A> Path<V, A> of(V v1) {
|
static <V, A> Path<V, A> of(V v1) {
|
||||||
throw new UnsupportedOperationException("Noch nicht implementiert.");
|
|
||||||
/*
|
|
||||||
Objects.requireNonNull(v1, "Der Knoten eines Pfades darf nicht null sein");
|
Objects.requireNonNull(v1, "Der Knoten eines Pfades darf nicht null sein");
|
||||||
return new PathImpl<>(v1);
|
return new PathImpl<>(v1);
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
120
src/main/java/h07/graph/PathImpl.java
Normal file
120
src/main/java/h07/graph/PathImpl.java
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package h07.graph;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
class PathImpl<V, A> implements Path<V, A> {
|
||||||
|
|
||||||
|
private final List<Node> 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<Node> thePath) {
|
||||||
|
this.thePath = thePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Traverser<V, A> traverser() {
|
||||||
|
return new TraverserImp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path<V, A> 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<V, A>(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<V> 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<V, A> {
|
||||||
|
Node current;
|
||||||
|
Node next;
|
||||||
|
Iterator<Node> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user