001/* 002MIT License 003 004Copyright (c) 2020 earlygrey 005 006Permission is hereby granted, free of charge, to any person obtaining a copy 007of this software and associated documentation files (the "Software"), to deal 008in the Software without restriction, including without limitation the rights 009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010copies of the Software, and to permit persons to whom the Software is 011furnished to do so, subject to the following conditions: 012 013The above copyright notice and this permission notice shall be included in all 014copies or substantial portions of the Software. 015 016THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 022SOFTWARE. 023 */ 024package squidpony.squidai.graph; 025 026import java.util.ArrayList; 027 028/** 029 * Most of the algorithms that operate on a {@link Graph} are defined here, with some specific cases in subclasses. 030 * The most-frequently-used algorithm here is probably {@link #findShortestPath(Object, Object, ArrayList, Heuristic)}, 031 * or some overload of it; this is a standard A-Star algorithm backed by a priority queue that is very fast. Somewhat 032 * surprisingly, it doesn't need indices like gdx-ai's IndexedAStarPathFinder needs, and this makes the code easier to 033 * use. There's a good amount of other useful algorithms here, like {@link #detectCycle()} and various non-A-Star search 034 * methods, like {@link #breadthFirstSearch(Object, int, int)} and {@link #depthFirstSearch(Object, int, int)}. 035 * <br> 036 * This class is not meant to be extended from outside of this package, so it uses a package-private implementation. 037 * @param <V> the vertex type; often {@link squidpony.squidmath.Coord} 038 * @author earlygrey 039 */ 040public class Algorithms<V> { 041 042 final Graph<V> graph; 043 final AlgorithmImplementations<V> implementations; 044 045 Algorithms(Graph<V> graph) { 046 this.graph = graph; 047 implementations = new AlgorithmImplementations<>(graph); 048 } 049 050 //-------------------- 051 // Shortest Path 052 //-------------------- 053 054 /** 055 * Find the shortest path between the start and target vertices, using Dijkstra's algorithm implemented with a priority queue. 056 * @param start the starting vertex 057 * @param target the target vertex 058 * @return a list of vertices from start to target containing the ordered vertices of a shortest path, including both the start and target vertices 059 */ 060 public ArrayList<V> findShortestPath(V start, V target) { 061 return findShortestPath(start, target, null); 062 } 063 064 /** 065 * Find the shortest path between the start and target vertices, using the A* search algorithm with the provided heuristic, and implemented with a priority queue. 066 * @param start the starting vertex 067 * @param target the target vertex 068 * @return a list of vertices from start to target containing the ordered vertices of a shortest path, including both the start and target vertices 069 */ 070 public ArrayList<V> findShortestPath(V start, V target, Heuristic<V> heuristic) { 071 ArrayList<V> list = new ArrayList<>(); 072 findShortestPath(start, target, list, heuristic); 073 return list; 074 } 075 076 /** 077 * Find the shortest path between the start and target vertices, using the A* search algorithm with the provided 078 * heuristic, and implemented with a priority queue. Fills path with a list of vertices from start to target 079 * containing the ordered vertices of a shortest path, including both the start and target vertices. 080 * @param start the starting vertex 081 * @param target the target vertex 082 * @param path the list of vertices to which the path vertices should be added 083 * @return true if a path was found, or false if no path could be found 084 */ 085 public boolean findShortestPath(V start, V target, ArrayList<V> path, Heuristic<V> heuristic) { 086 path.clear(); 087 Node<V> startNode = graph.getNode(start); 088 Node<V> targetNode = graph.getNode(target); 089 if (startNode==null || targetNode==null) throw new IllegalArgumentException("At least one vertex is not in the graph"); 090 implementations.findShortestPath(startNode, targetNode, path, heuristic); 091 return !path.isEmpty(); 092 } 093 094 /** 095 * Find the length of the shortest path between the start and target vertices, using Dijkstra's algorithm implemented with a priority queue. 096 * @param start the starting vertex 097 * @param target the target vertex 098 * @return the sum of the weights in a shortest path from the starting vertex to the target vertex 099 */ 100 public double findMinimumDistance(V start, V target) { 101 return implementations.findMinimumDistance(graph.getNode(start), graph.getNode(target)); 102 } 103 104 //-------------------- 105 // Graph Searching 106 //-------------------- 107 108 /** 109 * Perform a breadth first search starting from the specified vertex. 110 * @param v the vertex at which to start the search 111 * @param maxVertices the maximum number of vertices to process before terminating the search 112 * @param maxDepth the maximum edge distance (the number of edges in a shortest path between vertices) a vertex should have to be 113 * considered for processing. If a vertex has a distance larger than the maxDepth, it will not be added to the 114 * returned graph 115 * @return a Graph object containing all the processed vertices, and the edges from which each vertex was encountered. 116 * The vertices and edges in the returned graph will be in the order they were encountered in the search, and this will be 117 * reflected in the iteration order of the collections returned by {@link Graph#getVertices()} and {@link Graph#getEdges()}. 118 */ 119 public Graph<V> breadthFirstSearch(V v, int maxVertices, int maxDepth) { 120 Node<V> node = graph.getNode(v); 121 if (node==null) throw new IllegalArgumentException("At least one vertex is not in the graph"); 122 Graph<V> tree = graph.createNew(); 123 implementations.breadthFirstSearch(node, tree, maxVertices, maxDepth); 124 return tree; 125 } 126 127 /** 128 * Perform a breadth first search starting from the specified vertex. 129 * @param v the vertex at which to start the search 130 * @return a Graph object containing all the processed vertices (all the vertices in this graph), and the edges from which each vertex was encountered. 131 * The vertices and edges in the returned graph will be in the order they were encountered in the search, and this will be 132 * reflected in the iteration order of the collections returned by {@link Graph#getVertices()} and {@link Graph#getEdges()}. 133 */ 134 public Graph<V> breadthFirstSearch(V v) { 135 return breadthFirstSearch(v, graph.size(), graph.size()); 136 } 137 138 /** 139 * Perform a depth first search starting from the specified vertex. 140 * @param v the vertex at which to start the search 141 * @param maxVertices the maximum number of vertices to process before terminating the search 142 * @param maxDepth the maximum edge distance (the number of edges in a shortest path between vertices) a vertex should have to be 143 * considered for processing. If a vertex has a distance larger than the maxDepth, it will not be added to the 144 * returned graph 145 * @return a Graph object containing all the processed vertices, and the edges from which each vertex was encountered. 146 * The vertices and edges in the returned graph will be in the order they were encountered in the search, and this will be 147 * reflected in the iteration order of the collections returned by {@link Graph#getVertices()} and {@link Graph#getEdges()}. 148 */ 149 public Graph<V> depthFirstSearch(V v, int maxVertices, int maxDepth) { 150 Node<V> node = graph.getNode(v); 151 if (node==null) throw new IllegalArgumentException("At least one vertex is not in the graph"); 152 Graph<V> tree = graph.createNew(); 153 implementations.depthFirstSearch(node, tree, maxVertices, maxDepth); 154 return tree; 155 } 156 157 /** 158 * Perform a depth first search starting from the specified vertex. 159 * @param v the vertex at which to start the search 160 * @return a Graph object containing all the processed vertices (all the vertices in this graph), and the edges from which each vertex was encountered. 161 * The vertices and edges in the returned graph will be in the order they were encountered in the search, and this will be 162 * reflected in the iteration order of the collections returned by {@link Graph#getVertices()} and {@link Graph#getEdges()}. 163 */ 164 public Graph<V> depthFirstSearch(V v) { 165 return depthFirstSearch(v, graph.size(), graph.size()); 166 } 167 168 169 //-------------------- 170 // Structures 171 //-------------------- 172 173 /** 174 * Checks whether there are any cycles in the graph using depth first searches. 175 * @return true if the graph contains a cycle, false otherwise 176 */ 177 public boolean detectCycle() { 178 return implementations.containsCycle(graph); 179 } 180 181 //-------------------- 182 // Structures 183 //-------------------- 184 185 /** 186 * Gets the identifier for the last run of an algorithm; this should mostly be usable internally, but may be useful 187 * when different runs have affected a graph and a {@link Node#lastRunID} may need to be checked against this. 188 * @return an identifier for which algorithm ran last, as a probably-unique int 189 */ 190 public int lastRunID() { 191 return implementations.getRunID(); 192 } 193 194}