Ширина-первый поиск с использованием монады состояния в Haskell
недавно я задал вопрос для построения дерева DFS из графика в Stackoverflow и узнал, что его можно просто реализовать с помощью монады состояния.
в то время как 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 процесса за итерацию.
- точка dequeue из очереди
- добавьте всех незваных соседей точки в дочерний элемент текущего дерева, очередь и список "посещенных"
- повторите это для 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)
мы также можем написать, как искать Tree
s себя.
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
уже предоставляет способы построения Tree
s в ширину первого порядка от монадического разворачиваться. Ширина первого порядка позаботится о очереди, нам нужно будет только отслеживать состояние узлов, которые мы уже видели.
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
мы можем написать построение кратчайшего пути деревьев из Graph
s с точки зрения нашего общего дерева кратчайшего пути здание
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 = []}
]}
]}
)