Haskell玫瑰树节点替换:实现前序索引定位Zipper焦点
用Haskell Zipper定位玫瑰树前序遍历节点并替换
需求说明
现有两棵玫瑰树(分别含m、n个节点),需要将第一棵树前序遍历从0开始的第i个节点,替换为第二棵树前序遍历从0开始的第j个节点。
示例
树1结构:
R _________|____________ | | | A B C / \ / \ / \ D E F G H I
树2结构:
r _____|____ | | P Q / \ / | \ S T U V W
将树1的第7个节点(C)替换为树2的第4个节点(Q)后,结果树:
R _________|____________ | | | A B Q / \ / \ / | \ D E F G U V W
问题卡点
尝试用Zipper实现,但无法写出someFunc :: Tree a -> Int -> Zipper a函数——该函数需接收树的根节点,返回聚焦于前序遍历第i个节点的Zipper(含上下文)。
已实现将树展平为前序遍历树列表的函数:
flattenToTreeList :: Tree a -> [Tree a] flattenToTreeList t = squish t [] where squish (Node x ts) xs = Node x ts : foldr squish xs ts
希望类似生成Zipper列表,取第i个元素即可定位,但思路陷入循环。
解决方案
1. 先明确玫瑰树与Zipper的基础定义
补充通用的玫瑰树和Zipper上下文定义(若你已有定义可跳过):
-- 玫瑰树类型 data Tree a = Node a [Tree a] deriving (Show, Eq) -- Zipper的上下文:记录父节点值、当前节点的左兄弟、右兄弟 data Context a = Context a [Tree a] [Tree a] deriving (Show, Eq) -- Zipper = (当前聚焦的树, 上下文栈) type Zipper a = (Tree a, [Context a])
2. 实现前序遍历生成Zipper列表的函数
模仿flattenToTreeList的递归逻辑,在遍历过程中维护并生成每个节点对应的Zipper:
preorderZippers :: Tree a -> [Zipper a] preorderZippers t = go t [] where -- 核心递归:当前树 + 上下文栈 -> 生成Zipper列表 go currentTree ctx = -- 先记录当前节点的Zipper (currentTree, ctx) : -- 递归处理所有子树,维护子树的上下文 case currentTree of Node val children -> processChildren val children ctx [] -- 叶子节点无后续Zipper _ -> [] -- 处理子树列表:父节点值、剩余子树、父上下文、已处理的左兄弟 processChildren parentVal remainingChildren parentCtx leftSiblings = case remainingChildren of [] -> [] (child : rest) -> let childCtx = Context parentVal (reverse leftSiblings) rest childZippers = go child (childCtx : parentCtx) restZippers = processChildren parentVal rest parentCtx (child : leftSiblings) in childZippers ++ restZippers
3. 实现定位第i个节点的函数
基于上面的函数,直接取列表第i个元素即可,为避免索引越界,返回Maybe类型:
someFunc :: Tree a -> Int -> Maybe (Zipper a) someFunc t i = let zippers = preorderZippers t in if i >= 0 && i < length zippers then Just (zippers !! i) else Nothing -- 若不考虑越界风险,可简化为: -- someFunc t i = preorderZippers t !! i
4. 实现节点替换的完整流程
结合定位函数完成替换操作:
-- 从Zipper还原为完整树 zipperToTree :: Zipper a -> Tree a zipperToTree (t, []) = t zipperToTree (t, Context parentVal leftSiblings rightSiblings : ctxRest) = let parentChildren = reverse leftSiblings ++ [t] ++ rightSiblings parentTree = Node parentVal parentChildren in zipperToTree (parentTree, ctxRest) -- 替换函数:tree1的第i个节点替换为tree2的第j个节点 replaceNode :: Tree a -> Int -> Tree a -> Int -> Maybe (Tree a) replaceNode tree1 i tree2 j = do zipper1 <- someFunc tree1 i let tree2Nodes = flattenToTreeList tree2 tree2Node <- if j >=0 && j < length tree2Nodes then Just (tree2Nodes !! j) else Nothing let (_, ctx) = zipper1 newZipper = (tree2Node, ctx) return (zipperToTree newZipper)
示例验证
用给出的示例测试:
-- 构造树1 tree1 :: Tree Char tree1 = Node 'R' [ Node 'A' [Node 'D' [], Node 'E' []] , Node 'B' [Node 'F' [], Node 'G' []] , Node 'C' [Node 'H' [], Node 'I' []] ] -- 构造树2 tree2 :: Tree Char tree2 = Node 'r' [ Node 'P' [Node 'S' [], Node 'T' []] , Node 'Q' [Node 'U' [], Node 'V' [], Node 'W' []] ] -- 执行替换:tree1的第7个节点替换为tree2的第4个节点 main = print $ replaceNode tree1 7 tree2 4
运行后会输出预期的结果树。
内容的提问来源于stack exchange,提问作者ayaye
相关产品推荐
相关产品推荐

