我知道的图遍历(DFS和BFS)实现使用可变的“已访问”顶点集。如何使用只有不可变数据结构来实现它们?
我看到了这个问题。现在我想知道是否还有其他解决方案。
我知道的图遍历(DFS和BFS)实现使用可变的“已访问”顶点集。如何使用只有不可变数据结构来实现它们?
我看到了这个问题。现在我想知道是否还有其他解决方案。
这是一种可行的方式:
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)
@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)
使用此方法,您可以获得与使用while
循环编写的版本一样高效的版本。
object DirectedGraphTraversals {
type Graph[Vertex] = Map[Vertex, Set[Vertex]]
def dfs[Vertex](graph: Graph[Vertex], initialVertex: Vertex) =
dfsRec(DfsNeighbours)(graph, List(initialVertex), Set(), Set(), List())
def topologicalSort[Vertex](graph: Graph[Vertex]) =
graphDfsRec(TopologicalSortNeighbours)(graph, graph.keySet, Set(), List())
def stronglyConnectedComponents[Vertex](graph: Graph[Vertex]) = {
val exitOrder = graphDfsRec(DfsNeighbours)(graph, graph.keySet, Set(), List())
val reversedGraph = reverse(graph)
exitOrder.foldLeft((Set[Vertex](), List(Set[Vertex]()))){
case ((visitedAcc, connectedComponentsAcc), vertex) =>
val connectedComponent = dfsRec(DfsNeighbours)(reversedGraph, List(vertex), visitedAcc, visitedAcc, List()).toSet
(visitedAcc ++ connectedComponent, connectedComponent :: connectedComponentsAcc)
}._2.filterNot(_.isEmpty)
}
def reverse[Vertex](graph: Graph[Vertex]) = {
val reverseList = for {
(vertex, neighbours) <- graph.toList
neighbour <- neighbours
} yield (neighbour, vertex)
reverseList.groupBy(_._1).mapValues(_.map(_._2).toSet)
}
private sealed trait NeighboursFunc {
def apply[Vertex](graph: Graph[Vertex], vertex: Vertex, entered: Set[Vertex], exited: Set[Vertex]):Set[Vertex]
}
private object DfsNeighbours extends NeighboursFunc {
def apply[Vertex](graph: Graph[Vertex], vertex: Vertex, entered: Set[Vertex], exited: Set[Vertex]) =
graph.getOrElse(vertex, Set()).filterNot(entered)
}
private object TopologicalSortNeighbours extends NeighboursFunc {
def apply[Vertex](graph: Graph[Vertex], vertex: Vertex, entered: Set[Vertex], exited: Set[Vertex]) = {
val neighbours = graph.getOrElse(vertex, Set())
if(neighbours.exists(neighbour => entered(neighbour) && !exited(neighbour)))
throw new IllegalArgumentException("The graph is not a DAG, it contains cycles: " + graph)
else
neighbours.filterNot(entered)
}
}
@tailrec
private def dfsRec[Vertex](neighboursFunc: NeighboursFunc)(graph: Graph[Vertex], toVisit: List[Vertex],
entered: Set[Vertex], exited: Set[Vertex],
exitStack: List[Vertex]): List[Vertex] = {
toVisit match {
case List() => exitStack
case hd :: tl =>
if(exited(hd))
dfsRec(neighboursFunc)(graph, tl, entered, exited, exitStack)
else if(entered(hd) && !exited(hd))
dfsRec(neighboursFunc)(graph, tl, entered, exited + hd, hd :: exitStack)
else { // not entered yet
dfsRec(neighboursFunc)(graph, neighboursFunc(graph, hd, entered, exited) ++: toVisit, entered + hd, exited, exitStack)
}
}
}
@tailrec
private def graphDfsRec[Vertex](neighboursFunc: NeighboursFunc)(graph: Graph[Vertex], notVisited: Set[Vertex],
visited: Set[Vertex], order: List[Vertex]): List[Vertex] = {
if(notVisited.isEmpty)
order
else {
val orderSuffix = dfsRec(neighboursFunc)(graph, List(notVisited.head), visited, visited, List())
graphDfsRec(neighboursFunc)(graph, notVisited -- orderSuffix, visited ++ orderSuffix, orderSuffix ::: order)
}
}
}
object DirectedGraphTraversalsExamples extends App {
import DirectedGraphTraversals._
val graph = Map(
"B" -> Set("D", "C"),
"A" -> Set("B", "D"),
"D" -> Set("E"),
"E" -> Set("C"))
//println(dfs(graph, "A"))
println("dfs A " + dfs(graph, "A"))
println("dfs B " + dfs(graph, "B"))
println("topologicalSort " + topologicalSort(graph))
println("dfs B " + dfs(graph, "B"))
println("reverse " + reverse(graph))
println("stronglyConnectedComponents graph " + stronglyConnectedComponents(graph))
val graph2 = graph + ("C" -> Set("D"))
println("stronglyConnectedComponents graph2 " + stronglyConnectedComponents(graph2))
println("topologicalSort graph2 " + Try(topologicalSort(graph2)))
}
前面的解决方案存在一些问题:
效率低下:(graph(next) -- visited -- toVisit).toSeq 可能与图的大小成线性关系。
DFS 版本不正确,因为它没有按照 DFS 的顺序进行访问: 例如,如果您有以下图形:
Map("A" -> Set("B", "D"), "B" -> Set("C", "D"), "C" -> Set(), "D" -> Set("E"), "E" -> Set())
然后,如果您从 A 开始运行 DFS,则合法的 DFS 访问顺序为:
A, B, C, D, E A, B, D, E, C A, D, E, B, C
上述算法可能以以下非法顺序访问顶点: A, D, B, C, E
因为在第一次迭代中,您将 D 和 B 都添加到 toVisit 序列中。
您可以像下面这样进行遍历 -
object FindPathsFromANode extends App {
val startNode = "a"
val inputNodes = List(Node("a", "x"), Node("a", "b"), Node("b", "c"), Node("c", "d"), Node("b", "y"), Node("b", "z"), Node("d", "a"))
def findPaths(allNodes: List[Node], newNode: Node, path: List[Node] = Nil, isVisited: List[String] = Nil, allPaths: List[List[Node]] = Nil): List[List[Node]] = {
if (isVisited.contains(newNode.dest)) {
allPaths ++ List(path)
} else {
val nextNodes = allNodes.filter(_.source == newNode.dest)
if (nextNodes.isEmpty) {
allPaths ++ List(path :+ newNode)
} else if (nextNodes.size > 1) {
nextNodes.flatMap { node =>
findPaths(allNodes, node, path :+ newNode, isVisited :+ newNode.source)
}
} else {
findPaths(allNodes, nextNodes.head, path :+ newNode, isVisited :+ newNode.source)
}
}
}
val startNodes = inputNodes.filter(_.source == startNode)
startNodes.flatMap(node => findPaths(inputNodes, node)).foreach(println)
}
请查看以下解决方案: https://github.com/svsachin13/GraphTraversal-scala