P67 - 二叉树的括号字符串表示

2026-09-07 00:00    #Haskell   #99题   #二叉树  

P67 - 二叉树的括号字符串表示

A string representation of binary trees

官方模块:Problems.P67 核心函数:treeToStringstringToTree


← P66 紧凑布局 | P68 遍历序列重建 →


编码规则

叶子只写字符;非叶节点写成 根(左,右);空子树写为空串。例如 a(b(d,e),c(,f(g,)))

实现

方法一:手写递归下降

 1treeToString :: Tree Char -> String
 2treeToString Empty = ""
 3treeToString (Branch c Empty Empty) = [c]
 4treeToString (Branch c left right) =
 5  c : '(' : treeToString left ++ "," ++ treeToString right ++ ")"
 6
 7stringToTree :: String -> Maybe (Tree Char)
 8stringToTree "" = Just Empty
 9stringToTree input = do
10  (tree, rest) <- parseTree input
11  if null rest then Just tree else Nothing
12
13parseTree :: String -> Maybe (Tree Char, String)
14parseTree [] = Nothing
15parseTree (c:'(':rest) = do
16  (left, afterLeft) <- parseOptional rest
17  (right, afterRight) <- case afterLeft of
18    ',':xs -> parseOptional xs
19    _      -> Nothing
20  case afterRight of
21    ')':xs -> Just (Branch c left right, xs)
22    _      -> Nothing
23parseTree (c:rest) = Just (leaf c, rest)
24
25parseOptional :: String -> Maybe (Tree Char, String)
26parseOptional input@(',':_) = Just (Empty, input)
27parseOptional input@(')':_) = Just (Empty, input)
28parseOptional input         = parseTree input

编码直接跟随树结构,解析器则显式返回尚未消费的字符串。

方法二:差异列表 + ReadP

 1import Text.ParserCombinators.ReadP
 2
 3treeToStringDL :: Tree Char -> String
 4treeToStringDL tree = build tree ""
 5  where
 6    build Empty = id
 7    build (Branch c Empty Empty) = (c :)
 8    build (Branch c left right) =
 9      (c :) . ('(' :) . build left . (',' :) . build right . (')' :)
10
11stringToTreeReadP :: String -> Maybe (Tree Char)
12stringToTreeReadP input = case
13  [tree | (tree, "") <- readP_to_S (treeParser <* eof) input] of
14    []    -> Nothing
15    trees -> Just (last trees)
16
17treeParser :: ReadP (Tree Char)
18treeParser = do
19  c <- satisfy (`notElem` "(),")
20  (do
21      char '('
22      left <- optionalTree ','
23      char ','
24      right <- optionalTree ')'
25      char ')'
26      pure (Branch c left right))
27    <++ pure (leaf c)
28
29optionalTree :: Char -> ReadP (Tree Char)
30optionalTree delimiter = do
31  remaining <- look
32  if not (null remaining) && head remaining == delimiter
33    then pure Empty
34    else treeParser

差异列表版本通过函数组合构造字符串,不反复复制左侧前缀;ReadP 版本则把分隔符、选择和输入结束检查交给标准解析组合子。

方法对比

方法编码解码
手写递归直接使用 ++显式传递剩余输入
差异列表 + ReadP函数组合标准解析组合子

测试

 1>>> treeToString tree1
 2"a(b(d,e),c(,f(g,)))"
 3>>> stringToTree (treeToString tree1) == Just tree1
 4True
 5>>> stringToTree "a(b,c"
 6Nothing
 7>>> treeToStringDL tree1 == treeToString tree1
 8True
 9>>> stringToTreeReadP (treeToString tree1) == Just tree1
10True

解析函数同时返回未消费后缀,顶层只接受后缀为空的结果,从而拒绝尾随垃圾字符。

参考


← P66 紧凑布局 | P68 遍历序列重建 →