Ширина-первый поиск с использованием монады состояния в Haskell

недавно я задал вопрос для построения дерева DFS из графика в Stackoverflow и узнал, что его можно просто реализовать с помощью монады состояния.

DFS в haskell

в то время как DFS требует отслеживать только посещенные узлы, чтобы мы могли использовать " Set " или "List" или какую-то линейную структуру данных для отслеживания посещенных узлов, BFS требует, чтобы структура данных "посещенный узел" и "очередь" была выполнена.

мой псевдокод для BFS is

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 процесса за итерацию.

  1. точка dequeue из очереди
  2. добавьте всех незваных соседей точки в дочерний элемент текущего дерева, очередь и список "посещенных"
  3. повторите это для next in queue

поскольку мы не используем рекурсивный обход для поиска BFS, нам нужен другой метод обхода, такой как цикл while. Я посмотрел loop-while пакет в hackage, но он кажется несколько устаревшим.

как я предполагаю, что мне нужен какой-то код вроде этого :

{-...-}
... =   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 loop для блоков do.(Я. e должен ли я использовать рекурсивный алгоритм для его преодоления или я должен думать о совершенно другом стратегия)

учитывая один из ответов, которые я нашел в предыдущем вопросе, связанном выше, кажется, что ответ должен выглядеть так:

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-}

наконец, если такая реализация для BFS с использованием монады состояния невозможна по какой-то причине (что, я считаю, не так), пожалуйста, исправьте мое ложное предположение.

Я видел некоторые примеры для BFS в Haskell без использования монады состояния, но я хочу узнать больше о том, как монада состояния может быть обработана и не удалось найти ни одного примера 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

2 ответов


преобразование графика в Tree ширина-первый немного сложнее, чем просто поиск в ширину граф. Если вы ищете график,вам нужно только вернуться из одной ветви. При преобразовании графика в дерево результат должен включать результаты из нескольких ветвей.

мы можем использовать более общий тип, чем Graph a для того, что мы можем искать или конвертировать в деревья. Мы можем искать или преобразовывать в деревья что угодно с помощью функция a -> [a]. Для Graph мы бы использовали функцию (Map.!) m, где m - это Map. Поиск с помощью таблицы транспозиции имеет подпись типа

breadthFirstSearchUnseen:: Ord r => (a -> r) -> -- how to compare `a`s 
                           (a -> Bool) ->       -- where to stop
                           (a -> [a]) ->        -- where you can go from an `a`
                           [a] ->               -- where to start
                           Maybe [a]

преобразование функции в дерево, содержащее каждый достижимый узел на самой ранней глубине, имеет подпись

shortestPathTree :: Ord r => (a -> r) -> -- how to compare `a`s
                    (a -> l)             -- what label to put in the tree
                    (a -> [a]) ->        -- where you can go from an `a`
                    a ->                 -- where to start
                    Tree l

мы можем немного более вообще начать с любого количества узлов и построить Forest который содержит каждый достижимый узел как можно раньше глубина.

shortestPathTrees :: Ord r => (a -> r) -> -- how to compare `a`s
                     (a -> l)             -- what label to put in the tree
                     (a -> [a]) ->        -- where you can go from an `a`
                     [a] ->               -- where to start
                     [Tree l]

Поиск

выполнение преобразования в дерево на самом деле не помогает нам искать, мы можем выполнить широту первого поиска на исходном графике.

import Data.Sequence (viewl, ViewL (..), (><))
import qualified Data.Sequence as Seq
import qualified Data.Set as Set

breadthFirstSearchUnseen:: Ord r => (a -> r) -> (a -> Bool) -> (a -> [a]) -> [a] -> Maybe [a]
breadthFirstSearchUnseen repr p expand = combine Set.empty Seq.empty []
    where
        combine seen queued ancestors unseen =
            go
                (seen  `Set.union` (Set.fromList . map repr            $ unseen))
                (queued ><         (Seq.fromList . map ((,) ancestors) $ unseen))
        go seen queue =
            case viewl queue of
                EmptyL -> Nothing
                (ancestors, a) :< queued ->
                    if p a
                    then Just . reverse $ ancestors'
                    else combine seen queued ancestors' unseen
                    where
                        ancestors' = a:ancestors
                        unseen = filter (flip Set.notMember seen . repr) . expand $ a

состояние, поддерживаемое в приведенном выше алгоритме поиска, является Seq очередь из каких узлов посетить далее и Set узлов, которые уже были видел. Если бы мы вместо этого отслеживали узлы, которые уже были посетил, тогда мы могли бы посетить один и тот же узел несколько раз, если мы найдем несколько путей к узлу на той же глубине. Есть более полное объяснение в ответ я написал эту ширину первого поиска.

мы можем легко написать поиск GraphС точки зрения нашего общего поиска.

import qualified Data.Map as Map

newtype Graph a = Graph (Map.Map a [a]) deriving (Ord, Eq, Show)

bfsGraph :: (Ord a) => Graph a -> (a -> Bool) -> [a] -> Maybe [a]
bfsGraph (Graph adj) test = breadthFirstSearchUnseen id test ((Map.!) adj)

мы также можем написать, как искать Trees себя.

import Data.Tree

bfsTrees :: (Ord a) => (a -> Bool) -> [Tree a] -> Maybe [a]
bfsTrees test = fmap (map rootLabel) . breadthFirstSearchUnseen rootLabel (test . rootLabel) subForest

построение деревьев

построение деревьев ширина-первая намного сложнее. К счастью!--88-->Data.Tree уже предоставляет способы построения Trees в ширину первого порядка от монадического разворачиваться. Ширина первого порядка позаботится о очереди, нам нужно будет только отслеживать состояние узлов, которые мы уже видели.

unfoldTreeM_BF тип Monad m => (b -> m (a, [b])) -> b -> m (Tree a). m - это Monad наши расчеты будут в, b это тип данных, которые мы собираемся построить дерево на основе, и a - тип для меток дерева. Чтобы использовать его для построения дерева, нам нужно сделать функцию b -> m (a, [b]). Мы собираемся переименовать a до l на ярлык, и b to a, который мы использовали для наших узлов. Нам нужно сделать a -> m (l, [a]). Для m мы будем использовать State монады от трансформаторы для отслеживания некоторого состояния; состояние будет Set узлов, представление которых r мы уже видели; мы будем использовать State (Set.Set r) монады. В целом, нам нужно предоставить функцию a -> State (Set.Set r) (l, [a]).

expandUnseen :: Ord r => (a -> r) -> (a -> l) -> (a -> [a]) -> a -> State (Set.Set r) (l, [a])
expandUnseen repr label expand a = do
    seen <- get
    let unseen = filter (flip Set.notMember seen . repr) . uniqueBy repr . expand $ a
    put . Set.union seen . Set.fromList . map repr $ unseen
    return (label a, unseen)

чтобы построить деревья, мы запускаем вычисление состояния, построенное unfoldForestM_BF

shortestPathTrees :: Ord r => (a -> r) -> (a -> l) -> (a -> [a]) -> [a] -> [Tree l]
shortestPathTrees repr label expand = run . unfoldForestM_BF k . uniqueBy repr
    where
        run = flip evalState Set.empty
        k = expandUnseen repr label expand

uniqueBy это nubBy это используетOrd например вместо Eq.

uniqueBy :: Ord r => (a -> r) -> [a] -> [a]
uniqueBy repr = go Set.empty
    where
        go seen []     = []
        go seen (x:xs) =
            if Set.member (repr x) seen
            then go seen xs
            else x:go (Set.insert (repr x) seen) xs

мы можем написать построение кратчайшего пути деревьев из Graphs с точки зрения нашего общего дерева кратчайшего пути здание

shortestPathsGraph :: Ord a => Graph a -> [a] -> [Tree a]
shortestPathsGraph (Graph adj) = shortestPathTrees id id ((Map.!) adj)

мы можем сделать то же самое для фильтрации a Forest только самые короткие пути через Forest.

shortestPathsTree :: Ord a => [Tree a] -> [Tree a]
shortestPathsTree = shortestPathTrees rootLabel rootLabel subForest

мое решение основано на рабочем уровне по уровню (wrt. в BFS), см. также этот вопрос и ответ.

общая идея: предположим, что мы уже знаем наборы посещенных элементов до каждого уровня нашего BFS как список наборов. Затем мы можем пересечь график, уровень за уровнем, обновляя наш список множеств, строя выход Tree на пути.

хитрость в том, что после такого обхода уровня за уровнем у нас будут наборы посещенных элементов после каждый уровень. И это то же самое, что и список до каждый уровень, просто сдвинут на один. Так что брака, мы можем использовать смещенный выход как входной сигнал для процедуры.

import Control.Monad.State
import qualified Data.Map as M
import Data.Maybe (fromMaybe, catMaybes)
import qualified Data.Set as S
import Data.Tree

newtype Graph a = Graph (M.Map a [a])
    deriving (Ord, Eq, Show)

tagBfs :: (Ord a) => Graph a -> a -> Maybe (Tree a)
tagBfs (Graph g) s = let (t, sets) = runState (thread s) (S.empty : sets)
                      in t
  where
    thread x = do
        sets@(s : subsets) <- get
        case M.lookup x g of
            Just vs | not (S.member x s) -> do
                -- recursively create sub-nodes and update the subsets list
                let (nodes, subsets') = runState
                                          (catMaybes `liftM` mapM thread vs) subsets
                -- put the new combined list of sets
                put (S.insert x s : subsets')
                -- .. and return the node
                return . Just $ Node x nodes
            _ -> return Nothing -- node not in the graph, or already visited

под управлением tagBfs example2 'b' это на следующем примере

example2 :: Graph Char
example2 = Graph $ M.fromList
    [ ('a', ['b', 'c', 'd'])
    , ('b', ['a'])
    , ('c', [])
    , ('d', [])
    ]

доходность

Just (Node {rootLabel = 'b',
            subForest = [Node {rootLabel = 'a',
                               subForest = [Node {rootLabel = 'c',
                                                  subForest = []},
                                            Node {rootLabel = 'd',
                                                  subForest = []}
                                           ]}
                        ]}
      )