我知道的圖遍歷(DFS和BFS)實現使用可變的一組「被訪問」頂點。你將如何實現它們只有不可變的數據結構?我看到this question。現在,我不知道是否還有其他的解決方案Scala中的圖遍歷
回答
下面是做到這一點的一種方法:
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
循環。
請注意,在尾遞歸版本中,'visited'和'accumulator'包含相同的元素。原因是你想同時擁有:1)快速查找(所以'圖形(下一個) - 訪問'是線性的,就圖的大小而言)2)保存的排序。如果你有一個可以兼得的數據結構,那麼你只能使用那個。 – Philippe 2012-01-04 21:21:22
以前的解決方案有幾個問題:
這是一種低效:(圖表(下) - 訪問 - toVisit).toSeq可能是圖形的大小呈線性關係。
的DFS版本不正確,因爲它沒有根據DFS順序訪問: 例如,如果你有以下圖表:
地圖(「A」 - >設置(「B」,「d 「),」B「→Set(」C「,」D「),」C「→Set(),」D「→Set(」E「),」E「→Set())
然後,如果你運行DFS從A開始,法律DFS訪問命令是:
A,B,C,d,E A,B,d,E,C A,d,E,B ,C
上述算法可能訪問的頂點在下面非法命令: A,d,B,C,E
由於在第一次迭代中添加都 d和B到toVisit序列。
以下代碼會執行一些有向圖遍歷。 它只使用不可變的數據結構,並且是遞歸的,但有一些成本:
List toVisit可能包含許多重複項,因爲它包含我們計劃在未來訪問的所有頂點。在最糟糕的情況下,我們可能會在圖表中爲幾乎每個邊添加一個頂點。 因此,尾遞歸的空間複雜度爲,空間複雜度爲爲O(| E | + | V |)。
對於密集圖,空間複雜度爲O(| V |)的標準非尾遞歸解決方案效率更高,即使它確實需要保存長度爲O(| V |)的調用堆棧。由於存儲輸入圖本身需要O(| V | + | E |)空間,所以上述成本可能是可以接受的。 提高上述空間複雜度的一種可能的方法是用List [Iterator [Vertex]]替換爲Visis。由於迭代器被懶惰地評估,這將把空間複雜度降低到O(| V |)。
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)))
}
- 1. Spark scala RDD遍歷
- 2. 在scala中遍歷通用
- 3. Spark&Scala - RDD遍歷中的NullPointerException
- 4. 如何遍歷Scala中OrientDB的OResultSet?
- 5. Scala遍歷Case類中的雙數組
- 6. 在scala中遍歷兩個數組
- 7. 遍歷NSTableView中的視圖
- 8. 遍歷圖中的Freemarker
- 9. 圖的遍歷C
- 10. 遍歷Freebase圖
- 11. 遍歷圖像
- 12. 遍歷圖像
- 13. 圖遍歷
- 14. 遞歸遍歷一個Scala列表
- 15. 遍歷樹遍歷
- 16. Excel中:試圖遍歷
- 17. JavaScript圖遍歷庫
- 18. 遍歷視圖組
- 19. 打開圖遍歷
- 20. C#圖形遍歷
- 21. 圖遍歷問題
- 22. Clojure:遍歷套圖
- 23. jquery遍歷圖像
- 24. 承諾圖遍歷
- 25. 在scala中的一次迭代中實現遍歷函數
- 26. 在C中自動遍歷樹遍歷#
- 27. 圖遍歷的複雜性
- 28. LinkedHashMap遍歷鍵遍歷
- 29. 遍歷的R中
- 30. 遍歷OCaml中
什麼具體是缺乏你的問題的答案鏈接到?我們應該在這裏回答*我們不會回答*那裏*?它只是* BFS?如果是這樣,這功課? – huitseeker 2012-01-03 18:10:01
@huitseeker不,它不是作業。這個問題的答案看起來有些複雜。我會嘗試菲利普的答案,這對我來說看起來不錯。 – Michael 2012-01-04 08:20:40