More breadth-first search

Reachability with BFS

Last time we implemented a function to get the number of flights between two airports. We implemented it using breadth-first search, which starts at a given node and then visits all of the nodes it can get to by following edges, in order of how far away they are from the starting node. Here is our distance method:

def distance(graph: FlightGraph, start: String, end: String): Int = {
  var distances = Map(start -> 0)
  var visit = List(start)
  while (!visit.isEmpty) {
    val node = visit.head
    visit = visit.tail
    for (neighbor <- graph.neighbors(node)) {
      if (!distances.contains(neighbor)) {
        distances = distances + (neighbor -> (distances(node) + 1))
        if (neighbor == end) {
          return distances(neighbor)
        }
        visit = visit :+ neighbor
      }
    }
  }
  -1
}

isReachable

We can answer other questions using breadth-first search (BFS). For instance: is LAX reachable from BTV (i.e., can we get there at all)? In order to implement an isReachable function to answer this question, we can modify our distance function so that it doesn’t track distances. We could start with something like this:

def isReachable(graph: FlightGraph, start: String, end: String): Boolean = {
  var visit = List(start)
  while (visit.nonEmpty) {
    val node = visit.head
    visit = visit.tail
    for (neighbor <- graph.neighbors(node)) {
      if (neighbor == end) {
        return true
      }
      visit = visit :+ neighbor
    }
  }
  return false
}

This function has an issue, though: when end is not reachable from start, it will loop forever! This happens because we’re not keeping track of the nodes we visit, so we will keep visiting the same nodes over and over. For the distance method, we didn’t have this problem because we checked to see if we had already calculated the distance to a node before visiting it. In this case, we can track the set of visited nodes.

def isReachable(graph: FlightGraph, start: String, end: String): Boolean = {
  var visited = Set(start)
  var visit = List(start)
  while (visit.nonEmpty) {
    val node = visit.head
    visit = visit.tail
    for (neighbor <- graph.neighbors(node)) {
      if (!visited.contains(neighbor)) {
        visited = visited + neighbor
        if (neighbor == end) {
          return true
        }
        visit = visit :+ neighbor
      }
    }
  }
  return false
}

allReachable

How about another reachability quesiton: how can we find all of the nodes that are reachable from a particular starting node? To do this, we can just return the visited set we were tracking in isReachable:

def allReachable(graph: FlightGraph, start: String): Set[String] = {
  var visited = Set(start)
  var visit = List(start)
  while (visit.nonEmpty) {
    val node = visit.head
    visit = visit.tail
    for (neighbor <- graph.neighbors(node)) {
      if (!visited.contains(neighbor)) {
        visited = visited + neighbor
        visit = visit :+ neighbor
      }
    }
  }
  return visited
}

Paths

We ended by brainstorming a method to get the best path between two nodes. See the lecture capture for details.