【发布时间】:2015-02-17 23:34:24
【问题描述】:
最近,我在 Stackoverflow 中问了一个关于从 Graph 构建 DFS 树的问题,并了解到它可以通过使用 State Monad 来简单地实现。
虽然 DFS 要求只跟踪访问过的节点,因此我们可以使用“Set”或“List”或某种线性数据结构来跟踪访问过的节点,但 BFS 需要“访问过的节点”和“队列”数据结构完成。
我的 BFS 伪代码是
Q = empty queue
T = empty Tree
mark all nodes except u as unvisited
while Q is nonempty do
u = deq(Q)
for each vertex v ∈ Adj(u)
if v is not visited
then add edge (u,v) to T
Mark v as visited and enq(v)
从伪代码可以推断,我们每次迭代只需执行 3 个过程。
- 队列中的出队点
- 将该点的所有未访问邻居添加到当前树的子节点、队列和“已访问”列表中
- 对队列中的下一个重复此操作
由于我们没有使用递归遍历进行 BFS 搜索,我们需要一些其他的遍历方法,例如 while 循环。我在 hackage 中查找了 loop-while 包,但它似乎有些过时了。
我假设我需要这样的代码:
{-...-}
... = evalState (bfs) ((Set.singleton start),[start])
where
neighbors x = Map.findWithDefault [] x adj
bfs =do (vis,x:queue)<-get
map (\neighbor ->
if (Set.member neighbor vis)
then put(vis,queue)
else put ((Set.insert neighbor vis), queue++[neighbor]) >> (addToTree neighbor)
) neighbors x
(vis,queue)<-get
while (length queue > 0)
我知道这个实现是非常错误的,但这应该为我认为应该如何实现 BFS 提供简约的观点。另外,我真的不知道如何规避使用 while 循环 for do 块。(即我应该使用递归算法来克服它还是应该考虑完全不同的策略)
考虑到我在上面链接的上一个问题中找到的答案之一,答案似乎应该是这样的:
newtype Graph a = Graph (Map.Map a [a]) deriving (Ord, Eq, Show)
data Tree a = Tree a [Tree a] deriving (Ord, Eq, Show)
bfs :: (Ord a) => Graph a -> a -> Tree a
bfs (Graph adj) start = evalState (bfs') ((Set.singleton start),[start])
where
bfs' = {-part where I don't know-}
最后,如果由于某种原因无法使用 state monad 实现 BFS,(我认为不是)请纠正我的错误假设。
我在 Haskell 中看到了一些不使用 state monad 的 BFS 示例,但我想了解更多关于如何处理 state monad 的信息,但找不到任何使用 state monad 实现的 BFS 示例。
提前致谢。
编辑: 我想出了某种使用状态单子的算法,但我陷入了无限循环。
bfs :: (Ord a) => Graph a -> a -> Tree a
bfs (Graph adj) start = evalState (bfs' (Graph adj) start) (Set.singleton start)
bfs' :: (Ord a) => Graph a -> a -> State (Set.Set a) (Tree a)
bfs' (Graph adj) point= do
vis <- get
let neighbors x = Map.findWithDefault [] x adj
let addableNeighbors (x:xs) = if Set.member x vis
then addableNeighbors(xs)
else x:addableNeighbors(xs)
let addVisited (vis) (ns) = Set.union (vis) $ Set.fromList ns
let newVisited = addVisited vis $ addableNeighbors $ neighbors point
put newVisited
return (Tree point $ map (flip evalState newVisited) (map (bfs' (Graph adj)) $ addableNeighbors $ neighbors point))
EDIT2:以空间复杂性为代价,我提出了一种使用图返回和排队处理来获取 BFS 图的解决方案。尽管它不是生成 BFS 树/图的最佳解决方案,但它会起作用。
bfs :: (Ord a) => Graph a -> a -> Graph a
bfs (Graph adj) start = evalState (bfs' (Graph adj) (Graph(Map.empty)) [start]) (Set.singleton start)
bfs':: (Ord a) => Graph a -> Graph a -> [a] -> State (Set.Set a) (Graph a)
bfs' _ (Graph ret) [] = return (Graph ret)
bfs' (Graph adj) (Graph ret) (p:points)= do
vis <- get
let neighbors x = Map.findWithDefault [] x adj
let addableNeighbors ns
| null ns = []
| otherwise = if Set.member (head ns) vis
then addableNeighbors(tail ns)
else (head ns):addableNeighbors(tail ns)
let addVisited (v) (ns) = Set.union (v) $ Set.fromList ns
let unVisited = addableNeighbors $ neighbors p
let newVisited = addVisited vis unVisited
let unionGraph (Graph g1) (Graph g2) = Graph (Map.union g1 g2)
put newVisited
bfs' (Graph adj) (unionGraph (Graph ret) (Graph (Map.singleton p unVisited))) (points ++ unVisited)
EDIT3:我添加了将图形转换为树的功能。在 EDIT2 和 EDIT3 中运行函数将产生 BFS 树。它不是计算时间方面最好的算法,但我相信它对于像我这样的新手来说是直观且易于理解的:)
graphToTree :: (Ord a) => Graph a -> a -> Tree a
graphToTree (Graph adj) point = Tree point $ map (graphToTree (Graph adj)) $ neighbors point
where neighbors x = Map.findWithDefault [] x adj
【问题讨论】:
-
将命令式算法翻译成函数式语言充其量是困难的,有时甚至是不可能的。将伪代码“一对一”翻译成 Haskell 可能会非常难看。一个好的起点是函数
bfs' :: Ord a => Graph a -> a -> State (S.Set a) (Tree a),它从给定节点开始执行BFS,S.Set a是访问节点。您不需要保留节点队列 - 在命令式设置中,这很方便,但这里不是这种情况。
标签: algorithm haskell state-monad breadth-first-search