package base; import java.util.*; /** * Abstrakte generische Klasse um Wege zwischen Knoten in einem Graph zu finden. * Eine implementierende Klasse ist beispielsweise {@link game.map.PathFinding} * * @param Die Datenstruktur des Graphen */ public abstract class GraphAlgorithm { /** * Innere Klasse um {@link Node} zu erweitern, aber nicht zu verändern Sie weist * jedem Knoten einen Wert und einen Vorgängerknoten zu. * * @param */ private static class AlgorithmNode { private Node node; private double value; private AlgorithmNode previous; AlgorithmNode(Node parentNode, AlgorithmNode previousNode, double value) { this.node = parentNode; this.previous = previousNode; this.value = value; } } private Graph graph; // Diese Liste enthält alle Knoten, die noch nicht abgearbeitet wurden private List> availableNodes; // Diese Map enthält alle Zuordnungen private Map, AlgorithmNode> algorithmNodes; /** * Erzeugt ein neues GraphAlgorithm-Objekt mit dem dazugehörigen Graphen und dem * Startknoten. * * @param graph der zu betrachtende Graph * @param sourceNode der Startknoten */ public GraphAlgorithm(Graph graph, Node sourceNode) { this.graph = graph; this.availableNodes = new LinkedList<>(graph.getNodes()); this.algorithmNodes = new HashMap<>(); for (Node node : graph.getNodes()) this.algorithmNodes.put(node, new AlgorithmNode<>(node, null, -1)); this.algorithmNodes.get(sourceNode).value = 0; } /** * Diese Methode gibt einen Knoten mit dem kleinsten Wert, der noch nicht * abgearbeitet wurde, zurück und entfernt ihn aus der Liste * {@link #availableNodes}. Sollte kein Knoten gefunden werden, wird null * zurückgegeben. Verbindliche Anforderung: Verwenden Sie beim Durchlaufen der * Liste Iteratoren * * @return Der nächste abzuarbeitende Knoten oder null */ private AlgorithmNode getSmallestNode() { if (availableNodes.isEmpty()) { return null; } Iterator> iter = availableNodes.iterator(); AlgorithmNode MinElem; AlgorithmNode tempElem; if (iter.hasNext()) { // Set minimum to first elements MinElem = algorithmNodes.get(iter.next()); while (iter.hasNext()) { tempElem = algorithmNodes.get(iter.next()); if (tempElem.value > 0) { if (tempElem.value < MinElem.value) { MinElem = tempElem; } } } iter.remove(); return MinElem; } return null; } /** * Diese Methode startet den Algorithmus. Dieser funktioniert wie folgt: 1. * Suche den Knoten mit dem geringsten Wert (siehe {@link #getSmallestNode()}) * 2. Für jede angrenzende Kante: 2a. Überprüfe ob die Kante passierbar ist * ({@link #isPassable(Edge)}) 2b. Berechne den Wert des Knotens, in dem du den * aktuellen Wert des Knotens und den der Kante addierst 2c. Ist der alte Wert * nicht gesetzt (-1) oder ist der neue Wert kleiner, setze den neuen Wert und * den Vorgängerknoten 3. Wiederhole solange, bis alle Knoten abgearbeitet * wurden * * Nützliche Methoden: * * @see #getSmallestNode() * @see #isPassable(Edge) * @see Graph#getEdges(Node) * @see Edge#getOtherNode(Node) */ public void run() { AlgorithmNode v = getSmallestNode(); while (v != null) { for (Edge e : graph.getEdges(v.node)) { if (isPassable(e)) { AlgorithmNode n = algorithmNodes.get(e.getOtherNode(v.node)); double a = v.value + getValue(e); if (n.value == -1 || n.value > a) { n.value = a; n.previous = v; } } } v = getSmallestNode(); } } /** * Diese Methode gibt eine Liste von Kanten zurück, die einen Pfad zu dem * angegebenen Zielknoten representiert. Dabei werden zuerst beginnend mit dem * Zielknoten alle Kanten mithilfe des Vorgängerattributs * {@link AlgorithmNode#previous} zu der Liste hinzugefügt. Zum Schluss muss die * Liste nur noch umgedreht werden. Sollte kein Pfad existieren, geben Sie null * zurück. * * @param destination Der Zielknoten des Pfads * @return eine Liste von Kanten oder null */ public List> getPath(Node destination) { // Stage 1 ArrayList> reversePath = new ArrayList>(); AlgorithmNode prevNode = algorithmNodes.get(destination); if (prevNode == null) return null; while (prevNode != null) { AlgorithmNode prevPrevNode = prevNode.previous; if (prevPrevNode == null) return null; reversePath.add(new Edge(prevPrevNode.node, prevNode.node)); prevNode = prevPrevNode; } // Stage 2 Collections.reverse(reversePath); return reversePath; } /** * Gibt den betrachteten Graphen zurück * * @return der zu betrachtende Graph */ protected Graph getGraph() { return this.graph; } /** * Gibt den Wert einer Kante zurück. Diese Methode ist abstrakt und wird in den * implementierenden Klassen definiert um eigene Kriterien für Werte zu * ermöglichen. * * @param edge Eine Kante * @return Ein Wert, der der Kante zugewiesen wird */ protected abstract double getValue(Edge edge); /** * Gibt an, ob eine Kante passierbar ist. * * @param edge Eine Kante * @return true, wenn die Kante passierbar ist. */ protected abstract boolean isPassable(Edge edge); /** * Gibt an, ob ein Knoten passierbar ist. * * @param node Ein Knoten * @return true, wenn der Knoten passierbar ist. */ protected abstract boolean isPassable(Node node); }