/// <summary> /// Perform a Breadth First Search on the graph, given a station to start at, and a destination. This function will always find a path /// (and currently prints it out to the console and returns true) if the graph is connected, however, the path is not (guaranteed to be) /// optimal. /// /// Because Breadth First Search is primarily a search algorithm, and not a path finding algorithm, some extra functionality has been added /// to the graph, namely the wrapping of GraphNode objects in PathNode objects, which contain a linked-list style reference from a node to /// it's previous node, to enable a backtrace once the destination is found. This backtrace returns the path taken to the node that is found /// and is the desired output of this function. The return value needs to adjusted a JSON object that contains this path. /// </summary> /// <param name="start">The station to start at.</param> /// <param name="end">The station to end at.</param> /// <returns>At this stage, true = found, false = not found (and generally means an error has occurred, probably in the creation /// of the graph structure, and probably with the format of the input data).</returns> public bool BFS(Station start, Station end) { // this dictionary contains PathNode pairs, a node and it's previous node, to determine the path // a PathNode contains a GraphNode and the name of the line that was used when passing the node. Dictionary<PathNode, PathNode> path = new Dictionary<PathNode, PathNode>(); // FIRST CHECK IF THE start NODE EXISTS GraphNode root = null; bool found = false; foreach (GraphNode n in graph) // first check if the station is in the graph { if (n.name == start.Name) { root = n; // store this node as the root node to begin BFS with found = true; // the station does exist } } if (found == false) // the station does NOT exist, return return false; // END CHECK IF start NODE EXISTS // BFS PROPER Queue<PathNode> queue = new Queue<PathNode>(); // pathnode stores the line as well as the node List<string> visited = new List<string>(); // this list contains the name of ALL nodes that have been visited. queue.Enqueue(new PathNode(root.name, "", root)); // enqueue the root node (starting point) visited.Add(root.name); // and mark it as visited (this list keeps track of visited nodes so they don't get visited again) path.Add(new PathNode(root.name, "", root), null); // no line given to PathNode just yet while (queue.Count > 0) // while the queue is not empty, we still have nodes to check { PathNode check = queue.Dequeue(); // dequeue the next node if (check.node.name == end.Name) // if this is the end node, we are done { //Console.WriteLine("FOUND IT!! Start {0} End {1}\n", start.Name, end.Name); // print a message PathNode curr = check, prev; // set curr to the node we found and init prev to null List<string> list_path = new List<string>(); // this list contains the path list_path.Add(curr.line + " - " + curr.station); // add the node we just found as the end of the path while (curr.node != root) // while we are not back at the first node { if (path.TryGetValue(curr, out prev) == true) // found a value matching the key { list_path.Add(curr.line + " - " + prev.station); // add the previous node to the path curr = prev; // set current node to previous node and repeat } else { Console.WriteLine("Couldn't find the prev pathnode, path broken."); // uh oh //return false; // we should return if this happens, there's no way to show the path, it shouldn't happen though break; // finished, we're doomed } } //print the path to console //Console.WriteLine(); list_path.Reverse(); // the nodes are back to front, reverse them foreach (string s in list_path) { Console.WriteLine("Path: {0}", s); // print each 'line - station' in the path } return true; } // This part is the main BFS loop, it just keeps going until we find the destination // FIND ALL NODES THAT THIS NODE CONNECTS TO AND ADD THEM TO THE QUEUE string[] lines = check.node.adjacency_list.Keys.ToArray<string>(); // get all the lines that this node is part of foreach (string line in lines) { Dictionary<string, Station> adj = check.node.adjacency_list[line]; // grab the next/prev dict foreach (KeyValuePair<string, Station> pair in adj) // for next, and prev { if (pair.Value != null) // either next or prev exists { foreach (GraphNode n in graph) // find the station in the graph (this sucks, I have to search the whole graph to find the node again) { if (n.name == pair.Value.Name && visited.Contains(n.name) == false) // if the node name matches the next/prev name we are checking AND we haven't visited it before { PathNode to_queue = new PathNode(n.name, line, n); // create a new PathNode with the node name, line and a copy of the GraphNode queue.Enqueue(to_queue); // enqueue it visited.Add(n.name); // mark it as visited found = true; // flag the station does exist path.Add(to_queue, check); // add the node, and it's previous node to the path so we can retrace steps at the end } } if (found == false) // the station does NOT exist, return false (this is bad and shouldn't happen, means we have bad references) return false; } } } } return false; // not found, again, shouldn't happen if we are searching for a station that exists }
public List<string> lines; // what lines is this station part of (ie a train line and a bus route) #endregion Fields #region Constructors public GraphNode(Station station, string line, Station next, Station prev) { name = station.Name; // set the name of the node to the name of the station lines = new List<string>(); // this list is kinda redundant, but you can see what lines exist in the graph if you access it lines.Add(line); // add this line/route to the list of lines/routes that this station is part of adjacency_list = new Dictionary<string, Dictionary<string, Station>>(); // <line, <next/prev, station>> Dictionary<string, Station> adj = new Dictionary<string, Station>(); // <next = Station, prev = Station> adj.Add("next", next); // "next" = Station next adj.Add("prev", prev); // "prev" = Station prev adjacency_list.Add(line, adj); }