Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Graph traversal in Scala

Graph traversal (DFS and BFS) implementations I know use a mutable set of "visited" vertices. How would you implement them with only immutable data structures?

I saw this question. Now I wonder if there are also other solutions

like image 462
Michael Avatar asked Jan 03 '12 17:01

Michael


People also ask

What is graph traversal with example?

In computer science, graph traversal (also known as graph search) refers to the process of visiting (checking and/or updating) each vertex in a graph. Such traversals are classified by the order in which the vertices are visited. Tree traversal is a special case of graph traversal.

What are the types of traversal in graph?

The graph has two types of traversal algorithms. These are called the Breadth First Search and Depth First Search.

What is graph traversal algorithm?

Graph traversal is a technique used for searching a vertex in a graph. The graph traversal is also used to decide the order of vertices is visited in the search process. A graph traversal finds the edges to be used in the search process without creating loops.

What is BFS graph traversal?

BFS is a graph traversal approach in which you start at a source node and layer by layer through the graph, analyzing the nodes directly related to the source node. Then, in BFS traversal, you must move on to the next-level neighbor nodes.


1 Answers

Here is one way to do it:

def traverse[A](graph : Map[A,Set[A]], toVisit : Seq[A], visited : Set[A]) : Seq[A] = {
  if(toVisit.isEmpty) {
    Seq.empty
  } else {
    val next = toVisit.head
    val succ = (graph(next) -- visited -- toVisit).toSeq
    // DFS :
    // next +: traverse(graph, succ ++ toVisit.tail, visited + next)
    // BFS :
    next +: traverse(graph, toVisit.tail ++ succ, visited + next)
  }
}

def traverseFrom[A](graph : Map[A,Set[A]], initial : A) =
  traverse(graph, Seq(initial), Set.empty)

The trick is simply to pass around the list of nodes to visit (which would correspond to your stack or queue in an imperative algorithm), and the set of visited states.

If you're worried about the call stack growing with each recursive call, you make it tail-recursive by using an extra argument:

@annotation.tailrec
def traverseTR[A](graph : Map[A,Set[A]], toVisit : Seq[A], visited : Set[A], accumulator : Seq[A]) : Seq[A] = {
  if(toVisit.isEmpty) {
    accumulator
  } else {
    val next = toVisit.head
    val succ = (graph(next) -- visited -- toVisit).toSeq
    // DFS :
    //traverseTR(graph, succ ++ toVisit.tail, visited + next, accumulator :+ next)
    // BFS :
    traverseTR(graph, toVisit.tail ++ succ, visited + next, accumulator :+ next)
  }
}

def traverseFrom[A](graph : Map[A,Set[A]], initial : A) =
  traverseTR(graph, Seq(initial), Set.empty, Seq.empty)

This will give you a version as efficient as if you wrote it using a while loop.

like image 170
Philippe Avatar answered Oct 15 '22 06:10

Philippe