I'm currently learning algorithms and was trying to adapt some code from Algorithms by Robert Sedgewick. Here's a link to the part of the code I'm having problems with: http://algs4.cs.princeton.edu/44sp/DirectedCycle.java.html.
For my code the dependencies are a Vertex class with a constructor that just takes a vertex number. I also have a DirectedEdge class, that has a fromVertex, a toVertex and a weight. Finally, I have an EdgeWeightedDigraph class that has a list of the vertices within the graph and a DirectedEdge adjacency list that holds a list of the edges adjacent to a particular vertex and a test class that just initializes the various instances variables with the data in addition to executing the DirectedCycle.java program. The code compiles and runs but returns a wrong cycle using the data supplied by the book (http://algs4.cs.princeton.edu/42digraph/tinyDG.txt). The book specifies a Directed cycle: 3 5 4 3 but my code returns 3 2 3 instead. I noticed that the code runs fine until it encounters an already marked vertex and the onStack else-if code executes. For some reason the loop that contains the first call to dfs() iterates only once for index = 0 so after a marked vertex is encountered it does not retrace its steps and continue with the next vertex as it should with a depth first search. Not sure what I'm missing but any help will be appreciated. Please let me know if you need me to include the other code for the dependent classes listed above.
Here's my own code:
import java.util.Stack;
public class DirectedCycle {
private boolean[] marked;
private Vertex[] edgeTo;
private Stack<Vertex> cycle;
private boolean[] onStack;
public DirectedCycle(EdgeWeightedDigraph graph) {
onStack = new boolean[graph.getNumOfVertices()];
edgeTo = new Vertex[graph.getNumOfVertices()];
marked = new boolean[graph.getNumOfVertices()];
for (int index = 0; index < graph.getVertices().size(); index++) {
if (!marked[index] && cycle == null) {
dfs(graph, graph.getVertex(index));
}
}
}
private void dfs(EdgeWeightedDigraph graph, Vertex vertex) {
onStack[vertex.getVertexNumber()] = true;
marked[vertex.getVertexNumber()] = true;
for (DirectedEdge w : graph.adjacent(vertex)) {
if (this.hasCycle()) {
return;
}
else if (!marked[w.toVertex().getVertexNumber()]) {
edgeTo[w.toVertex().getVertexNumber()] = vertex;
dfs(graph, w.toVertex());
}
else if (onStack[w.toVertex().getVertexNumber()]) {
cycle = new Stack<>();
for (Vertex v = vertex;
v.getVertexNumber() != w.toVertex().getVertexNumber();
v = edgeTo[v.getVertexNumber()]) {
cycle.push(v);
}
cycle.push(w.toVertex());
cycle.push(vertex);
}
}
onStack[vertex.getVertexNumber()] = false;
}
public boolean hasCycle() {
return cycle != null;
}
public Iterable<Vertex> cycle() {
return cycle;
}
}
Related
I have the own data structure for the graph, and I need the implementation method:
List<Edge<T>> getPath(T start, T finish)
Performance not important, I search the simplest and most readable way. But my data structure should support the directed and undirected graph types and I stuck with it.
public class Graph<T> {
private boolean isDirected = false;
private Map<Vertex<T>, List<Edge<T>>> graph = new HashMap<>();
public Graph() {
}
public Graph(boolean isDirected) {
this.isDirected = isDirected;
}
public List<Edge<T>> getPath(T start, T finish) {
if (start.equals(finish)) {
return new ArrayList<>();
}
// TODO here is the method I'm stuck with.
if (isDirected) {
// Call search for directed graph
} else {
// Call search for undirected graph
}
}
public void addEdge(T first, T second) {
final Vertex<T> master = new Vertex<>(first);
final Vertex<T> slave = new Vertex<>(second);
final Set<Vertex<T>> vertices = graph.keySet();
if (!vertices.contains(master) || !vertices.contains(slave)) {
throw new IllegalArgumentException();
}
graph.get(master).add(new Edge<>(master, slave));
if (!isDirected) {
graph.get(slave).add(new Edge<>(slave, master));
}
}
public void addVertex(T value) {
final List<Edge<T>> result = graph.putIfAbsent(new Vertex<>(value), new ArrayList<>());
if (result != null) {
throw new IllegalArgumentException();
}
}
}
This Vertex and Edge class:
#Data
#AllArgsConstructor
#EqualsAndHashCode
public class Vertex<T> {
private T value;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Edge<T> {
private Vertex<T> first;
private Vertex<T> second;
}
I will be very grateful for Your help.
It is not totally clear what kind of path you want to find. The shortest path, any path,...?
If you want to find the shortest path, A* is a really simple algorithm to implement. The pseudo code can be found here. A* is a best-first search algorithm for a weighted graph (E.g. the distance of an edge or another kind of cost to travel on the edge like time). The algorithm uses a heuristic function to select the next node/vertex to evaluate. A* basically repeats the following steps:
Select a next node/vertex which has not already been visited. The selection is made using the heuristic function
If this new node equals the goal position, return the shortest path found
Evaluate all paths currently known and select the one with the lowest cost
I could also provide a Java code snippet (based on the pseudo code) if it's necessary. Be aware that the pseudo code in the end constructs the shortest path backwards (from goal to start).
You are also using a generic for your graph. Both your Vertext and Edge class use this generic. Let's assume that this generic T is a double. In your code this means that your Vertex is only a one-dimensional double. This does not make sense when you want to represent a graph of 2D or 3D points.
Is it even really necessary to use this generic? Wouldn't it be sufficient to simply support vertices which consists of floats, doubles or integers? Using a generic type or more abstract class (like Number) might give some problems when you for example want to compute the distance between vertices.
I have a binary tree in which every node represents a electronics gate (AND, OR, ...). My mission is to calculate the total value of the tree (like this one in the picture, a binary tree):
This is my code so far (without the threads implementation):
gate_node:
public class gate_node {
gate_node right_c, left_c;
Oprtator op;
int value;
int right_v, left_v;
public gate_node(gate_node right, gate_node left, Oprtator op) {
this.left_c = left;
this.right_c = right;
this.op = op;
right_v = left_v = 0;
}
void add_input(int right_v, int left_v){
this.right_v=right_v;
this.left_v=left_v;
}
int compute(int array_index, int arr_size) {
/*
* The following use of a static sInputCounter assumes that the
* static/global input array is ordered from left to right, irrespective
* of "depth".
*/
final int left, right;
System.out.print(this.op+"(");
if (null != this.left_c) {
left = this.left_c.compute(array_index,arr_size/2);
System.out.print(",");
} else {
left = main_class.arr[array_index];
System.out.print(left + ",");
}
if (null != this.right_c) {
right = this.right_c.compute(array_index + arr_size/2,arr_size/2);
System.out.print(")");
} else {
right = main_class.arr[array_index + 1];
System.out.print(right + ")");
}
return op.calc(left, right);
}
}
Oprtator:
public abstract class Oprtator {
abstract int calc(int x, int y);
}
And
public class and extends Oprtator {
public int calc(int x, int y){
return (x&y);
}
}
Or
public class or extends Oprtator {
public int calc(int x, int y){
return (x|y);
}
}
The tree:
public class tree implements Runnable {
gate_node head;
tree(gate_node head) {
this.head = head;
}
void go_right() {
head = head.right_c;
}
void go_left() {
head = head.left_c;
}
#Override
public void run() {
// TODO Auto-generated method stub
}
}
main class
public class main_class {
public static int arr[] = { 1, 1, 0, 1, 0, 1, 0, 1 };
public static void main(String[] args) {
tree t = new tree(new gate_node(null, null, new and()));
t.head.right_c = new gate_node(null, null, new or());
t.head.right_c.right_c = new gate_node(null, null, new and());
t.head.right_c.left_c = new gate_node(null, null, new and());
t.head.left_c = new gate_node(null, null, new or());
t.head.left_c.right_c = new gate_node(null, null, new and());
t.head.left_c.left_c = new gate_node(null, null, new and());
int res = t.head.compute(0, arr.length);
System.out.println();
System.out.println("The result is: " + res);
}
}
I want to calculate it using thread pool, like this algorithm:
Preparation:
Implement each gate as a class/object. It has to have 2 attributes: input A, input B and a way to calculate result;
Implement a tree. Each node is a pair (gate, next_node). Root is a node with next_node being null. Leaves are nodes such that no other node points to it.
Use a shared (thread safe) queue of nodes. It is initially empty.
There is a fixed number (pick one, does not depend on number of gates) of threads which continuously wait for an element from the queue (unless the result is reached in which case they just quit).
Loop:
Whenever an input occurs on a node put the node in a queue (at the beginning inputs go to leaves). This can be simply implemented by defining add_input method on a gate.
A thread picks up a node from queue:
If one of the input is missing discard it (it will be there one more time when second input appears). Another idea is to put the node in a queue only when both inputs are there.
If both inputs are there, then calculate result and pass it to next_node if it is not null (and put next_node in the queue). If next_node is null, then this is your result - break the loop and finalize.
the only problem is that I don't know how to create a shared BlockingQueue that every node object in the tree can insert himself into it, and how to create an array of fixed sized of threads that constantly waits for new elements in the queue to be available (and then execute them)..... until the head is removed from the list (meaning we are done calculating).
I searched online for BlockingQueue examples but I only found producer and consumer examples and I'm having a hard time to move those example to fit my problem. I would really appreciate it if anyone could try to help me.
I can give you a few starting pointers to get you going :)
To create your threads just spawn that many threads:
for (int i=0;i<MAX_THREADS;i++) {
new Thread(myRunnable).start();
}
You may well want to store a reference to those threads but it isn't required. The threads need no special setup as they are all identical and they all just sit there grabbing items off the queue.
To share a blocking queue the simplest way is just to make it static and final:
static final BlockingQueue blockingQueue();
Now all the threads can access it.
Incidentally if I was doing this I wouldn't use the Queue at all, I'd use a ThreadPoolExecutor and just send the processing to that as new runnables.
http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
Background: Imagine I have a little Robot. I place this Robot at some Node in a Map (Graph). The Robot can call the giveMeMapCopy() method to get a copy of the whole map that he is sat in. I want to give my little Robot a function by which he can use a breadth first traversal to find the shortest path to the Exit node. Here is an example of such a map:
I have watched videos on YouTube on how to do a breadth first traversal of a graph, so I have a good idea of what needs to be done. The problem is, I am finding it hard to make my logic recursive. Here is my code:
public class Robot
{
// fields required for traversal
private Queue<ArrayList<String>> queue;
private ArrayList<ArrayList<String>> result;
private String workingNode;
private ArrayList<String> routeSoFar;
private Queue<String> knownShortestPath;
public Robot() {
queue = new LinkedList<ArrayList<String>>();
result = new ArrayList<ArrayList<String>>();
routeSoFar = new ArrayList<String>();
knownShortestPath = new LinkedList<String>();
}
// Runs when Robot has reached a node.
public void enterNodeActions() {
knownShortestPath = determineIdealPath();
}
// Runs to determine where to go next
public String chooseNextNode() {
if(!knownShortestPath.isEmpty())
{
// TODO: Need to go through the
}
}
public LinkedList<String> determineIdealPath()
{
try {
// Get the map
Map m = giveMeMapCopy();
// Get all entry nodes of map
Set<String> entryNodes = m.getEntryNodes();
/*
* Loop through all Entry nodes, and find out where we are.
* Set that as current working node.
*/
for (String n : entryNodes) {
if(n == getMyLocation())
{
workingNode = n;
}
}
// All enighbours of working node.
Set<String> neighboursNames = getNeighboursNames(workingNode);
/*
* For each neighbour, construct a path from working node to the neighbour node
* And add path to Queue and Result (if not already present).
*/
for(String node : neighboursNames)
{
if(!node.equals(getMyLocation()))
{
ArrayList<String> route = new ArrayList<String>();
route.add(getMyLocation());
route.add(node);
if(!containsRoute(result, route))
{
if(!containsRoute(queue, route))
{
queue.add(route);
}
result.add(route);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Where I want the recursion to happen is after I have gone through all neighbours of the Entry node [ A ], I want to move to the next [ B ] and do the same for that, i.e. go through each of its neighbours (ignoring A, cause it is already present in Result list) and add them to the Queue and Result lists.
I hope the problem is clear, if not please let me know, and I'll try to clarify anything is not clear.
Breadth-first search is typically done without recursion as it is based on a queue (of partial path in your case). Depth-first search on the other hand is based on a stack, wich can be implemented quite naturally using the call-stack of a recursive function.
Essentially, what you want is to implement Dijkstra's algorithm or something similar to find the shortest path between a source and destination in a graph.
You can find such implementation in Java here. Just set all the weights at 1.
I'm developing a Java application and I'm using the JUNG library.
In my application I first create a DelegateTree and draw it to the screen:
public static GraphZoomScrollPane generateTree(Tree tree,
GraphicalUserInterface gui) {
/* Create a new tree */
edu.uci.ics.jung.graph.Tree<Node, Edge> graphTree = new DelegateTree<Node, Edge>();
/* Add all nodes and vertices to the tree */
graphTree.addVertex(tree.getRoot());
addChildren(tree.getRoot(), graphTree);
/* Create the visualization */
TreeLayout<Node, Edge> treeLayout = new TreeLayout<Node, Edge>(graphTree);
VisualizationViewer<Node, Edge> vv = new VisualizationViewer<Node, Edge>(treeLayout);
vv.setBackground(Color.WHITE);
vv.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller<Edge>());
vv.getRenderContext().setEdgeShapeTransformer(new EdgeShape.Line<Node, Edge>());
vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<Node>());
vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.S);
vv.addGraphMouseListener(new ClickNode(gui, vv));
final DefaultModalGraphMouse<Node, Edge> graphMouse = new DefaultModalGraphMouse<Node, Edge>();
graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING);
vv.setGraphMouse(graphMouse);
return new GraphZoomScrollPane(vv);
}
Afterwards the user is able to add new children to the leaves of my tree. But when I just do
graphTree.addEdge(edge, parent, child);
and then redraw the VisualizationViewer, the visualization lost the 'Tree' structure. It just adds the child somewhere above the parent and all other children of that new child right on top of it.
Is there a better way to dynamically add children to the leaves of my tree? Or do I have to use something else for redrawing instead of just vv.repaint()?
Any help would really be appreciated.
An example of what happens:
http://www.dylankiss.be/JUNGExample.PNG
Starting with just the root (OUTLOOK), after adding 3 children (Leaf, Leaf, Leaf) with different edges (sunny, overcast, rainy), they just appear on top of each other.
EDIT: This is the addChildren() method.
private static void addChildren(Node node, edu.uci.ics.jung.graph.Tree<Node, Edge> tree) {
for (int i = 0; i < node.getChildren().size(); i++) {
tree.addEdge(new Edge(node.getChildren().get(i).getParentValue()), node, node.getChildren().get(i));
addChildren(node.getChildren().get(i), tree);
}
}
EDIT 2: This is the part of an AWT ActionListener where I add new children to the tree.
while (there are still edges to be added) {
value = name of new edge;
child = new Node(this.m_node, value);
this.m_node.addChild(child);
graphTree.addEdge(new Edge(value), this.m_node, child);
}
Posting the method that is in charge of adding new edges would help here :)
But at first glance, it seems that you are adding 3 different edges between the same two nodes (OUTLOOK and Leaf). I'm guessing you are doing this (or the equivalent with Node and Edge instances):
graphTree.addChild("sunny", "OUTLOOK", "Leaf");
graphTree.addChild("overcast", "OUTLOOK", "Leaf");
graphTree.addChild("rainy", "OUTLOOK", "Leaf");
In this case, as JUNG graphs maintain unicity of nodes, you end up with only two nodes, and 3 different edges between them. When JUNG tries to display this graph, you will get the two nodes and the 3 overlapping edges as you used EdgeShape.Line.
If you original goal was indeed to set 3 different edges between two nodes, try using different edge shapes to avoid overlapping and get a better rendering, e.g. EdgeShape.BentLine or such.
If you wanted 3 different nodes, you will have to use 3 different names, or 3 different Node instances which are not equals.
Good luck :)
EDIT:
Following your comment, I took a look at the TreeLayout sources, and there is a small issue which makes it impossible to dynamically update the layout.
To fix the problem, use this class instead:
import edu.uci.ics.jung.algorithms.layout.TreeLayout;
import java.awt.Point;
import java.util.Collection;
import edu.uci.ics.jung.graph.Forest;
import edu.uci.ics.jung.graph.util.TreeUtils;
public class DynamicTreeLayout<V, E>
extends TreeLayout<V, E>
{
public DynamicTreeLayout(Forest<V, E> g) {
this(g, DEFAULT_DISTX, DEFAULT_DISTY);
}
public DynamicTreeLayout(Forest<V, E> g, int distx) {
this(g, distx, DEFAULT_DISTY);
}
public DynamicTreeLayout(Forest<V, E> g, int distx, int disty) {
super(g, distx, disty);
}
protected void buildTree() {
alreadyDone.clear(); // This was missing and prevented the layout to update positions
this.m_currentPoint = new Point(20, 20);
Collection<V> roots = TreeUtils.getRoots(graph);
if (roots.size() > 0 && graph != null) {
calculateDimensionX(roots);
for (V v : roots) {
calculateDimensionX(v);
m_currentPoint.x += this.basePositions.get(v) / 2 + this.distX;
buildTree(v, this.m_currentPoint.x);
}
}
}
private int calculateDimensionX(V v) {
int localSize = 0;
int childrenNum = graph.getSuccessors(v).size();
if (childrenNum != 0) {
for (V element : graph.getSuccessors(v)) {
localSize += calculateDimensionX(element) + distX;
}
}
localSize = Math.max(0, localSize - distX);
basePositions.put(v, localSize);
return localSize;
}
private int calculateDimensionX(Collection<V> roots) {
int localSize = 0;
for (V v : roots) {
int childrenNum = graph.getSuccessors(v).size();
if (childrenNum != 0) {
for (V element : graph.getSuccessors(v)) {
localSize += calculateDimensionX(element) + distX;
}
}
localSize = Math.max(0, localSize - distX);
basePositions.put(v, localSize);
}
return localSize;
}
}
You will also need to add the following if you want the layout to be updated and the viewer to be repainted for each modification of your graph:
layout.setGraph(g);
vv.repaint();
I wrote a simple Depth-First search algorithm, which works, but is failing to build the patch right. Im having a tough time trying to understand, why - so, basically, need your help, guys :)
Here is the code:
public void Search(String from, String to) {
String depart = from;
String destin = to;
if ( (NoChildren(depart) == false)
&& (!depart.equalsIgnoreCase(destin)) ) {
while (!depart.equalsIgnoreCase(to)) {
closeStack.push(depart);
depart = getChildren(depart);
}
}
}
public boolean NoChildren(String from) {
boolean noChildren = false;
int counter = 0;
for(int j = 0; j < Flights.size(); j++) {
FlightInfo flight = (FlightInfo)Flights.elementAt(j);
if (flight.from().equalsIgnoreCase(from)) {
counter++;
}
}
if (counter == 0) {
noChildren = true;
}
return noChildren;
}
public String getChildren(String from) {
for(int j = 0; j < Flights.size(); j++) {
FlightInfo flight = (FlightInfo)Flights.elementAt(j);
if (flight.from().equalsIgnoreCase(from)) {
openStack.push(flight.to().toString());
}
}
return openStack.pop().toString();
}
I made it longer just for clearance, and planning to optimize it - just need to make it work properly first :))
Ok, the main problem is with that closeStack, which was meant to contain a path from start to finish - but now, it contains whatever the algorithm checked :-[
Thanx in advance!!
Maxim, there are a whole bunch of errors in your code. It looks as if you had an idea for what you wanted to do, and then threw some code at it until something emerged and worked a bit, but there's no clear concept here and thus it's no wonder it's not really working.
The core of this program is the Flights collection (why is Flights uppercased?), and it's very possible to build a working route finder around it. I'm not sure whether it would help you more to give you some hints or to simply build the program for you.
Update: I've meanwhile found a flight schedule for a Polish airline (don't ask!) with 203 distinct routings that I can use to fill and test a flight connection structure. I'm going to start hacking and we'll see how it goes.
Update: Here's the code.
To be at all useful for your apparent purpose, it's probably not enough to just find a routing (i.e. an itinerary of airports visited); you probably want a list of flights taken to get there. Note, of course, that there may be multiple combinations of flights that have the same itinerary - this code just finds the first.
You may want to modify the algorithm to place a weight (= cost) on travel time, if you have those, so your passengers get not just the smallest number of legs (= hops from one airport to the next) but also the shortest combined travel time. This more general form of the algorithm would be called Dijkstra's Algorithm, and is also described in Wikipedia.
Interestingly enough, it seems that BFS is not really suited to a recursive solution. Like your original code, my code is essentially imperative with a few loops. Note that the correct "main" data structure for doing BFS is not a stack but a queue!
public class Maxim {
/**
* Create a Maxim instance and run a search on it.
*/
public static void main(String[] args) {
try {
Maxim maxim = new Maxim();
Route r = maxim.findRoute("FCO", "DNV"); // tests a single origin/destination pair
if (r == null) {
System.out.println("No route found");
} else {
System.out.println(Arrays.deepToString(r.toArray()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* A simple Flight. Contains a flight number and only a single leg.
* number: Flight number
* dep: Departure airport
* arr: Arrival airport
*/
class Flight {
final String number, dep, arr;
public Flight(String number, String departure, String arrival) {
this.number = number; this.dep = departure; this.arr = arrival;
}
public String toString() {
return "Flight [number=" + this.number + ", dep=" + this.dep + ", arr=" + this.arr + "]";
}
}
/**
* Airport: A city and a list of Flights originating from it.
*/
class Airport {
public final String city;
public List<Flight> flights = new ArrayList<Flight>();
public Airport(String city) {
this.city = city;
}
public String toString() {
return "Airport [city=" + this.city + ", flights=" + this.flights + "]";
}
}
/**
* Route: A list of flights that get a traveller from a given origin to a destination.
*/
static class Route extends ArrayList<Flight> { }
/**
* Our known list of flights. It's not really needed after initialization.
*/
private List<Flight> flights = new ArrayList<Flight>();
/**
* List of airports. These constitute the graph we search.
*/
private Map<String, Airport> airports = new HashMap<String, Airport>();
/**
* Constructor. Constructs the "airports" graph from a list of "flights" read from a file.
*/
public Maxim() throws Exception {
// Read flights from file into list "flights".
// The file contains strings like " 696KGDWAW" = flight number, departure airport, arrival airport
BufferedReader flightReader = new BufferedReader(new FileReader("/home/carl/XX.flights"));
while (true) {
String flt = flightReader.readLine();
if (flt == null) break;
flights.add(new Flight(flt.substring(0,4), flt.substring(4, 7), flt.substring(7, 10)));
}
flightReader.close();
// Create a map of each airport to a list of Flights departing from it.
// This is the graph we'll be doing BFS on.
for (Flight flight : flights) {
String from = flight.dep;
if (!airports.containsKey(from)) {
Airport port = new Airport(from);
port.flights.add(flight);
airports.put(from, port);
} else {
Airport port = airports.get(from);
port.flights.add(flight);
}
}
}
/**
Algorithm (from Wikipedia):
1. Enqueue the root node.
2. Dequeue a node and examine it.
If the element sought is found in this node, quit the search and return a result.
Otherwise enqueue any successors (the direct child nodes) that have not yet been discovered.
3. If the queue is empty, every node on the graph has been examined – quit the search and return "not found".
4. Repeat from Step 2.
*/
public Route findRoute(String origin, String destination) {
Queue<Airport> queue = new LinkedList<Airport>();
Map<Airport, Flight> backtrack = new HashMap<Airport, Flight>();
Airport oriApt = this.airports.get(origin);
if (oriApt == null) return null; // origin airport not found - no solution
queue.add(oriApt);
while (!queue.isEmpty()) {
Airport apt = queue.remove();
if (apt == null) break;
if (apt.city.equals(destination)) { // Made it to destination; create the route and return it
Route toHere = new Route();
while (apt != oriApt) {
Flight flt = backtrack.get(apt);
toHere.add(flt);
apt = airports.get(flt.dep);
}
Collections.reverse(toHere);
return toHere;
}
// enqueue all new airports reachable from this airport.
// record the flight that got us there in backtrack.
for (Flight flt: apt.flights) {
Airport destApt = airports.get(flt.arr);
if (backtrack.containsKey(destApt)) continue; // we've been to this destination before - ignore
backtrack.put(destApt, flt);
queue.add(destApt);
}
}
// if we're here, we didn't find anything.
return null;
}
}