You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Cats-Effect实现图DFS的递归调用执行异常问题求助

Cats-Effect 实现深度优先搜索(DFS)的递归执行问题解决

尝试用Cats-Effect实现图的DFS时,递归调用DFS(neighbor)未执行;后续用sequence处理List[IO[Unit]]后,仅能遍历起始节点的邻居,无法深入到邻居的邻居。以下是问题分析和解决方案:


初始代码的核心问题

  1. IO未触发执行:在neighborIteration中,neighbors.map(...)生成了Set[IO[Unit]],但仅创建了IO实例却未触发执行——IO是惰性计算,必须通过sequence、traverse或flatMap才能运行。
  2. 冗余的IO.delay:graph是纯内存数据结构,无需用IO.delay包裹,直接访问即可。
  3. 逻辑顺序错误:DFS0中先更新时间戳和前驱列表,再检查节点是否已访问,导致已访问节点被重复处理。

更新后代码的核心问题

  1. 提前标记已访问:在neighborIteration中提前将邻居节点加入visited集合,导致进入DFS0时节点已被标记为已访问,跳过后续递归处理。
  2. 逻辑顺序仍有误:DFS0依然先更新时间戳,再检查是否已访问,不符合DFS先判断再处理的逻辑。

修正后的代码

import cats.effect._
import cats.syntax.all._

object DFS extends IOApp.Simple:
    def DFS[T](graph: Map[T, Set[T]], startNode: T): IO[List[(T, Int, Int)]] =
        def neighborIteration(
            neighbors: Set[T],
            visitedRef: Ref[IO, Set[T]],
            predRef: Ref[IO, List[T]],
            dRef: Ref[IO, Map[T, Int]],
            fRef: Ref[IO, Map[T, Int]],
            timeRef: Ref[IO, Int]
        ): IO[Unit] = 
            neighbors.toList.traverse_ { neighbor =>
                DFS0(neighbor, visitedRef, predRef, dRef, fRef, timeRef)
            }

        def DFS0(
            node: T,
            visitedRef: Ref[IO, Set[T]],
            predRef: Ref[IO, List[T]],
            dRef: Ref[IO, Map[T, Int]],
            fRef: Ref[IO, Map[T, Int]],
            timeRef: Ref[IO, Int]
        ): IO[Unit] =
            visitedRef.get.flatMap { visited =>
                if (visited contains node) IO.unit
                else
                    for
                        // 标记节点为已访问,防止重复处理
                        _ <- visitedRef.update(_ + node)
                        // 记录前序时间戳
                        nextTime <- timeRef.getAndUpdate(_ + 1)
                        _ <- dRef.update(_ + (node -> nextTime))
                        // 更新前驱节点列表
                        _ <- predRef.update(node :: _)
                        // 递归处理所有邻居
                        _ <- neighborIteration(
                            graph.getOrElse(node, Set()),
                            visitedRef,
                            predRef,
                            dRef,
                            fRef,
                            timeRef
                        )
                        // 记录后序时间戳
                        nextTime2 <- timeRef.getAndUpdate(_ + 1)
                        _ <- fRef.update(_ + (node -> nextTime2))
                    yield ()
            }

        for
            predRef <- Ref.of[IO, List[T]](Nil)
            dRef <- Ref.of[IO, Map[T, Int]](Map.empty)
            fRef <- Ref.of[IO, Map[T, Int]](Map.empty)
            visitedRef <- Ref.of[IO, Set[T]](Set.empty)
            timeRef <- Ref.of[IO, Int](0)
            _ <- DFS0(startNode, visitedRef, predRef, dRef, fRef, timeRef)
            predVal <- predRef.get
            dVal <- dRef.get
            fVal <- fRef.get
            // 反转前驱列表,恢复DFS的实际访问顺序
            result = predVal.reverse.map(e => (e, dVal(e), fVal(e)))
        yield result

    override def run: IO[Unit] =
        val graph2 = Map(
          1 -> Set(2, 3, 4),
          2 -> Set(1),
          3 -> Set(1, 4),
          4 -> Set(1, 3, 7),
          5 -> Set(6),
          6 -> Set(5),
          7 -> Set(4, 8),
          8 -> Set(3, 7)
        )
        for
            dfsResult <- DFS(graph2, 1)
            _ <- IO.println(dfsResult)
        yield ()

关键修改说明

  • 正确执行惰性IO:用traverse_替代map.sequence.void,简洁遍历邻居并执行每个邻居的DFS0调用。
  • 修正访问判断逻辑:在DFS0开头先检查节点是否已访问,已访问则直接返回,避免重复处理。
  • 调整标记时机:确认节点未访问后,先标记为已访问,再处理时间戳和前驱列表,防止同一节点被多路径重复处理。
  • 恢复访问顺序:前驱列表通过node :: _反向构建,最后用reverse恢复DFS的实际访问顺序。

内容的提问来源于stack exchange,提问作者Lukas Tycho

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 20:17:00