尾递归算法,用于生成图中的所有拓扑顺序

Kev*_*vin 4 scala tail-recursion graph topological-sort scala-cats

给定一个图,我需要生成所有拓扑顺序。例如,给定下图:

在此处输入图片说明

我想生成所有拓扑顺序,这些顺序是:

  • 2 4 7 5
  • 2 7 4 5
  • 2 4 5 7

因为可能存在许多拓扑顺序,所以我需要延迟生成它们。当前,我有一个递归工作的实现,并且可以在scala-graph库的顶部进行工作:

import scalax.collection.Graph
import scalax.collection.GraphPredef._
import scalax.collection.GraphEdge._

import scala.collection.mutable.ArrayStack
import scala.collection.Set

def allTopologicalSorts[T](graph: Graph[T, DiEdge]): Stream[List[graph.NodeT]] = {
  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], topOrder: List[graph.NodeT], cnt: Int): Stream[List[graph.NodeT]] = {
    if (sources.nonEmpty) {
      // `sources` contain all the nodes we can pick
      // --> generate all possibilities
      sources.toStream.flatMap(src => {
        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) {
      throw new Error("There is a cycle in the graph.")
    }
    else {
      topOrder.reverse #:: Stream.empty[List[graph.NodeT]]
    }
  }

  processSources(getSources(), indegree, List[graph.NodeT](), 0)
}
Run Code Online (Sandbox Code Playgroud)

现在,我可以生成所有(或仅几个)拓扑顺序,如下所示:

val graph: Graph[Int, DiEdge] = Graph(2 ~> 4, 2 ~> 7, 4 ~> 5)
allTopologicalSorts(graph) foreach println
Run Code Online (Sandbox Code Playgroud)

我如何才能使算法尾部递归但仍然很懒?

Dmy*_*tin 5

如何使树映射尾部递归?

Scala中二叉树的尾递归最大深度方法

尝试使用 scala.util.control.TailCalls

import scalax.collection.Graph
import scalax.collection.GraphPredef._
import scalax.collection.GraphEdge._

import scala.collection.Set
import scala.util.control.TailCalls.{TailRec, done, tailcall}

import cats.Monad
import cats.instances.stream._
import cats.syntax.traverse._

object App {

  implicit val tailRecMonad: Monad[TailRec] = new Monad[TailRec] {
    override def pure[A](x: A): TailRec[A] = done(x)
    override def flatMap[A, B](fa: TailRec[A])(f: A => TailRec[B]): TailRec[B] = fa.flatMap(f)
    override def tailRecM[A, B](a: A)(f: A => TailRec[Either[A, B]]): TailRec[B] = ???
  }

  def allTopologicalSorts[T](graph: Graph[T, DiEdge]): Stream[List[graph.NodeT]] = {
    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], topOrder: List[graph.NodeT], cnt: Int): TailRec[Stream[List[graph.NodeT]]] = {
      if (sources.nonEmpty) {
        // `sources` contain all the nodes we can pick
        // --> generate all possibilities
        sources.toStream.flatTraverse/*flatMap*/(src => {
          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
            }
          }

          tailcall(processSources(newSources, newIndegrees, newTopOrder, cnt + 1))
        })
      }
      else if (cnt != graph.nodes.size) {
        done(throw new Error("There is a cycle in the graph."))
      }
      else {
        done(topOrder.reverse #:: Stream.empty[List[graph.NodeT]])
      }
    }

    processSources(getSources(), indegree, List[graph.NodeT](), 0).result
  }

  def main(args: Array[String]): Unit = {
    val graph: Graph[Int, DiEdge] = Graph(2 ~> 4, 2 ~> 7, 4 ~> 5)
    allTopologicalSorts(graph) foreach println
  }
}
Run Code Online (Sandbox Code Playgroud)

或者你可以使用 cats.free.Trampoline

http://eed3si9n.com/herding-cats/stackless-scala-with-free-monads.html

import scalax.collection.Graph
import scalax.collection.GraphEdge._
import scalax.collection.GraphPredef._

import cats.free.Trampoline
import cats.free.Trampoline.{done, defer}
import cats.instances.stream._
import cats.instances.function._
import cats.syntax.traverse._

import scala.collection.Set

object App {

  def allTopologicalSorts[T](graph: Graph[T, DiEdge]): Stream[List[graph.NodeT]] = {
    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], topOrder: List[graph.NodeT], cnt: Int): Trampoline[Stream[List[graph.NodeT]]] = {
      if (sources.nonEmpty) {
        // `sources` contain all the nodes we can pick
        // --> generate all possibilities
        sources.toStream.flatTraverse(src => {
          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
            }
          }

          defer(processSources(newSources, newIndegrees, newTopOrder, cnt + 1))
        })
      }
      else if (cnt != graph.nodes.size) {
        done(throw new Error("There is a cycle in the graph."))
      }
      else {
        done(topOrder.reverse #:: Stream.empty[List[graph.NodeT]])
      }
    }

    processSources(getSources(), indegree, List[graph.NodeT](), 0).run
  }

  def main(args: Array[String]): Unit = {
    val graph: Graph[Int, DiEdge] = Graph(2 ~> 4, 2 ~> 7, 4 ~> 5)
    allTopologicalSorts(graph) foreach println
  }
}
Run Code Online (Sandbox Code Playgroud)