【发布时间】:2019-05-02 08:20:39
【问题描述】:
我正在实现拓扑排序的变体(在scala-graph 之上),它返回所有拓扑排序,而不仅仅是一个。我有一个树递归实现,我想让尾递归。我不想使用蹦床,而是想模仿this answer 中描述的调用堆栈。
这是我的算法的树递归版本:
import scalax.collection.Graph
import scalax.collection.GraphPredef._
import scalax.collection.GraphEdge._
import scala.collection.Set
def allTopologicalSorts[T](graph: Graph[T, DiEdge]): Unit = {
val indegree: Map[graph.NodeT, Int] = graph.nodes.map(node => (node, node.inDegree)).toMap
def isSource(node: graph.NodeT): Boolean = indegree.get(node).get == 0
def getSources(): Set[graph.NodeT] = graph.nodes.filter(node => isSource(node))
processSources(getSources(), indegree, List[graph.NodeT](), 0)
def processSources(sources: Set[graph.NodeT], indegrees: Map[graph.NodeT, Int], topOrder: List[graph.NodeT], cnt: Int): Unit = {
if (sources.nonEmpty) {
// `sources` contain all the nodes we can pick
// --> generate all possibilities
for (src <- sources) {
val newTopOrder = src :: topOrder
var newSources = sources - src
// Decrease the in-degree of all adjacent nodes
var newIndegrees = indegrees
for (adjacent <- src.diSuccessors) {
val newIndeg = newIndegrees.get(adjacent).get - 1
newIndegrees = newIndegrees.updated(adjacent, newIndeg)
// If in-degree becomes zero, add to sources
if (newIndeg == 0) {
newSources = newSources + adjacent
}
}
processSources(newSources, newIndegrees, newTopOrder, cnt + 1)
}
}
else if (cnt != graph.nodes.size) {
println("There is a cycle in the graph.")
}
else {
println(topOrder.reverse)
}
}
}
我们可以如下运行算法
val graph: Graph[Int, DiEdge] = Graph(2 ~> 4, 2 ~> 7, 4 ~> 5)
allTopologicalSorts(graph)
正确返回
- 列表(2、7、4、5)
- 列表(2、4、7、5)
- 列表(2、4、5、7)
现在,我尝试通过手动保留堆栈来实现尾递归版本
import scalax.collection.Graph
import scalax.collection.GraphPredef._
import scalax.collection.GraphEdge._
import scala.collection.Set
def allTopologicalSorts[T](graph: Graph[T, DiEdge]): Unit = {
val indegree: Map[graph.NodeT, Int] = graph.nodes.map(node => (node, node.inDegree)).toMap
def isSource(node: graph.NodeT): Boolean = indegree.get(node).get == 0
def getSources(): Set[graph.NodeT] = graph.nodes.filter(node => isSource(node))
def processSources(sources: Set[graph.NodeT], indegrees: Map[graph.NodeT, Int]): Unit = {
type Order = List[graph.NodeT]
case class Frame(sources: List[graph.NodeT], indegrees: Map[graph.NodeT, Int], topOrder: Order, cnt: Int)
def step(stack: List[Frame]): Unit = {
stack match {
case Frame(src :: rest, indegrees, topOrder, cnt) :: tail => {
val onBacktrackingFrame = Frame(rest, indegrees, topOrder, cnt)
// Process src now and remember to do the rest later
val newTopOrder = src :: topOrder
var newSources = rest
// Decrease the in-degree of all adjacent nodes
var newIndegrees = indegrees
for (adjacent <- src.diSuccessors) {
val newIndeg = newIndegrees.get(adjacent).get - 1
newIndegrees = newIndegrees.updated(adjacent, newIndeg)
// If in-degree becomes zero, add to sources
if (newIndeg == 0) {
newSources = adjacent :: newSources
}
}
val recursionFrame = Frame(newSources, newIndegrees, newTopOrder, cnt + 1)
step(recursionFrame :: onBacktrackingFrame :: tail)
}
case Frame(Nil, indegrees, topOrder, cnt) :: tail => {
println(topOrder.reverse)
step(tail)
}
case Nil =>
}
}
step(List(Frame(sources.toList, indegrees, List[graph.NodeT](), 0)))
}
processSources(getSources(), indegree)
}
但是,这不起作用,因为它会导致
- 列表(2、4、5、7)
- 列表(2、4、5)
- 列表(2、4、7)
- 列表(2, 4)
- 列表(2, 7)
- 列表(2)
- 列表()
堆栈有问题,但我找不到问题。
相关问题:Tail recursive algorithm for generating all topological orderings in a graph
【问题讨论】:
-
您在stackoverflow.com/a/55786425/5249621 看到我的堆栈解决方案了吗? (第二个标题为“直接解决方案”)
-
是的,问题不在于如何去做。我知道我需要模拟堆栈,这就是我所做的。但是,我的实现出了点问题,我无法弄清楚。我在这上面花了几天时间,但我真的不明白问题出在哪里。
-
您以前的版本将结构作为输入并返回一个结构作为输出。现在这些版本返回
Unit(通常很难解释副作用)。通常,一个帧包含递归调用的输入参数和结果参数。即使结果是Unit,也应该清楚来自递归子调用的Units 是否已经计算或仍在处理中。 -
目前,返回类型是 Unit,因为我会在遇到结果时打印结果。我可以很容易地添加一个额外的参数来累积结果。但是,为了调试,这更容易,一旦成功,我可以添加参数。通常,当
sources参数为 nil 时,会执行递归子调用。 -
我的意思是计算子调用的帧和未计算子调用的帧应该是不同的帧。在 AndreyTyukin 的回答中是
todos,在我的回答中是NotExpanded/Expanded/Calculated。看来你没看。
标签: scala recursion tail-recursion topological-sort