我正在尝试使用递归算法构建DFS树。
伪代码是:
DFF(G)
Mark all nodes u as unvisited
while there is an unvisited node u do
DFS(u)
DFS(u)
Mark u as visited
for each v in u's neighbor do
if v is not marked
DFS(v)
虽然通过为un / visited节点构建某种数据结构,以简单的方式通过命令式语言很容易地实现这一点,为Haskell分配动态分配或某种声明,但是从Haskell开始就不可能这样做#39; s纯度阻止我在传递参数时更改数据。
data Graph a = Graph [(a,[a])] deriving (Ord, Eq, Show)
data Tree a = Node a [Tree a] deriving (Ord, Eq, Show)
type Point = (Int, Int)
type Edges = [Point]
type Path = [Point]
pathGraphFy :: Graph Point -> Point -> Tree (Point,Path)
pathGraphFy inputGraph point = getPathVertex inputGraph (point,[])
getPathVertex :: Graph Point -> (Point, Path) -> Tree (Point,Path)
getPathVertex inputGraph (point,path) =
Node (point,point:path) (map (getPathVertex inputGraph) [(x,(point:path)) | x<- neighbors, x `notElem` path])
where neighbors = pointNeighbor inputGraph point
pointNeighbor :: Graph Point -> Point -> Edges
pointNeighbor (Graph (x:xs)) point =
if fst x == point then snd x else pointNeighbor (Graph(xs)) point
这是我使用DFS-ish(或更确切地说是BFS-ish)算法进行图遍历时所得到的,但问题是它将再次访问所有不在点中的点。路径。 (即如果存在循环,它将顺时针和逆时针两种方式移动)
我还尝试使用访问点来描述另一个图表但由于参数传递的图表仅在遍历中保存Graph的数据(即不是全局的)而失败
如果只有动态分配或静态数据可以保存全局级别的数据,这可以很容易地解决,但我对Haskell有点新意,我在这个问题上无法在网上找到答案。请帮帮我:(提前致谢。
(P.S) 我已经尝试使用访问节点的传递列表,但它没有工作,因为当递归返回时,访问节点列表也将返回,从而无法跟踪数据。如果有办法制作地图&#39;或者&#39;列出&#39;全局,有可能以这种方式实现它。尽管只是一个链接答案,下面的答案,对于为什么不能(或不应该)实施的原因有很好的解释。
答案 0 :(得分:6)
涉及通过和返回状态或使用状态monad的答案比这种方法更透明,但如下文所述,它不那么有效且不能很好地概括。也就是说,无论你在这个答案中需要什么,都值得学习状态monad并在Haskell中使用不可变数据。
The paper linked in another答案纸提供了对所谓的归纳图的使用的相当学术的讨论。幸运的是,该论文的作者非常友好地将此方法实现为Haskell库fgl
。我将介绍一些有关将数据附加到节点和诸如此类的详细信息,并说明如何使用此库实现DFS。修改此算法以生成树而不是列表很容易,列表版本更加简洁。
dfs :: Graph gr => [Node] -> gr a b -> [Node]
dfs [] _ = []
-- this equation isn't strictly necessary, but it can improve performance for very dense graphs.
dfs _ g | isEmpty g = []
dfs (v:vs) g = case match v g of
(Just ctx, g') -> v:dfs (suc' ctx ++ vs) g'
_ -> dfs vs g
这里的关键是match
,它将图形分解为顶点的所谓Context
,剩下的图形(匹配返回Maybe Context
,以覆盖顶点的情况不在图中)。
顶点Context
的概念对于归纳图的概念至关重要:它被定义为元组
(adjIn, nodeId, nodeLabel, adjOut)
其中adjIn
和adjOut
是(edgeLabel, nodeId)
对的列表。
请注意,术语标签在此处使用松散,并且是指附加到顶点或边缘的一般数据。
suc'
函数接受一个上下文,并返回一个节点列表,这些节点是上下文中节点的后继节点(adjOut
,并删除了边缘标签)。
我们可以像这样构建一个图表
使用这样的代码
testGraph :: DynGraph g => gr a b
testGraph =
let nodes = [(i, "N" ++ show i) | i <- [1..5]]
edges = [(2,1,"E21")
,(4,1, "E41")
,(1,3, "E13")
,(3,4, "E34")
,(3,5,"E35")
,(5,2, "E52")]
withNodes = insNodes nodes empty
in insEdges edges withNodes
致电dfs testGraph
会产生[1,3,4,5,2]
。
注意:我很无聊并偶然发现了这个问题,所以答案只是几个小时的调查和实验。
答案 1 :(得分:3)
没有什么能阻止你在函数参数/返回值中编码状态。经典的DFS可能如下所示:
import qualified Data.Map as Map
import qualified Data.Set as Set
newtype Graph a = Graph (Map.Map a [a]) deriving (Ord, Eq, Show)
data Tree a = Tree a [Tree a] deriving (Ord, Eq, Show)
dfs :: (Ord a) => Graph a -> a -> Tree a
dfs (Graph adj) start = fst $ dfs' (Set.singleton start) start
where
neighbors x = Map.findWithDefault [] x adj
dfs' vis x =
let (subtrees, vis') =
foldr
(\y (subtrees, vis) ->
if Set.member y vis
then (subtrees, vis)
else let vis' = Set.insert y vis
(t, vis'') = dfs' vis' y
in (t : subtrees, vis'')
)
([], vis)
(neighbors x)
in (Tree x subtrees, vis')
您可以使用persistent hash tables或integer maps/sets代替Map/Set
,具体取决于您的节点类型。
要避免显式状态,您应该使用state monad:
import Control.Applicative
import Control.Monad.State
import Control.Monad
import Data.Maybe
{- ... -}
dfs :: (Ord a) => Graph a -> a -> Tree a
dfs (Graph adj) start = evalState (dfs' start) (Set.singleton start)
where
neighbors x = Map.findWithDefault [] x adj
dfs' x = Tree x . catMaybes <$>
forM (neighbors x) (\y -> get >>= \vis ->
if Set.member y vis
then return Nothing
else put (Set.insert y vis) >> Just <$> dfs' y)