Haskell新手求助:从比特表示重建霍夫曼编码二叉树
嘿,作为Haskell新手折腾霍夫曼编码,确实是个练手的好项目!我看你卡在了反序列化前序遍历的霍夫曼树这一步,咱们一步步拆解来解决它。
首先,先明确我们需要的基础类型(如果你的定义和这个一致,那刚好能直接用):
import qualified Data.Map as M data Bit = O | I deriving (Eq, Show) data HTree a = Leaf a | Node (HTree a) (HTree a) deriving (Eq, Show) -- 序列化用的是「符号→Bit串」的字典,反序列化得用反向的「Bit串→符号」字典 type ReverseCodeDict a = M.Map [Bit] a
反序列化的核心思路
你的序列化是前序遍历:先写根节点(Node写0,Leaf写1),再递归左子树,最后递归右子树。反序列化就得反过来,按同样的顺序递归解析,而且必须返回「解析出的树 + 剩余未处理的Bit流」——这很关键,因为解析完左子树后,剩下的Bit才是右子树的内容,得把这个状态传递下去,不然会把右子树的Bit当成左子树的一部分,直接解析崩掉。
具体实现代码
先写一个基础版本,后续再优化错误处理:
deserializeHtree :: ReverseCodeDict a -> [Bit] -> (HTree a, [Bit]) -- 遇到0,说明是Node:先解析左子树,再解析右子树 deserializeHtree revDict (O : rest) = let (leftSubtree, restAfterLeft) = deserializeHtree revDict rest (rightSubtree, restAfterRight) = deserializeHtree revDict restAfterLeft in (Node leftSubtree rightSubtree, restAfterRight) -- 遇到1,说明是Leaf:读取接下来的符号Bit串(假设myLpad补到8位,所以取前8个),转成符号 deserializeHtree revDict (I : rest) = let (symbolBits, restAfterSymbol) = splitAt 8 rest -- 这里用Just是假设字典一定能查到,实际要加错误处理 Just symbol = M.lookup symbolBits revDict in (Leaf symbol, restAfterSymbol) -- 处理空输入的异常情况 deserializeHtree _ [] = error "Invalid serialized tree: unexpected end of bit stream"
优化错误处理(更健壮的版本)
上面的基础版用了error和Just,实际项目里最好用Maybe来处理异常(比如Bit流长度不够、符号找不到),避免程序直接崩溃:
import Data.Tuple (swap) deserializeHtreeSafe :: ReverseCodeDict a -> [Bit] -> Maybe (HTree a, [Bit]) deserializeHtreeSafe revDict (O : rest) = do (leftSubtree, restAfterLeft) <- deserializeHtreeSafe revDict rest (rightSubtree, restAfterRight) <- deserializeHtreeSafe revDict restAfterLeft return (Node leftSubtree rightSubtree, restAfterRight) deserializeHtreeSafe revDict (I : rest) = -- 先检查剩余Bit够不够取一个符号的长度(这里是8位) if length rest >= 8 then let (symbolBits, restAfterSymbol) = splitAt 8 rest in case M.lookup symbolBits revDict of Just symbol -> Just (Leaf symbol, restAfterSymbol) Nothing -> Nothing -- 符号不在字典里 else Nothing -- Bit流长度不够 deserializeHtreeSafe _ [] = Nothing -- 空输入
新手避坑提示
- Bit流顺序不能乱:前序是根→左→右,解析时必须严格遵循这个顺序,不然树的结构会完全错误。
- 符号Bit串长度要匹配:如果你的
myLpad不是补到8位,一定要把splitAt的数字改成对应的长度,比如补到16位就写splitAt 16 rest。 - 反向字典的生成:你可以用
M.fromList . map swap $ M.toList yourOriginalDict来生成反向字典,记得导入Data.Tuple (swap)。
测试示例
比如我们有个简单的霍夫曼树,序列化后再反序列化验证:
-- 示例树:Node (Leaf 'a') (Node (Leaf 'b') (Leaf 'c')) sampleTree :: HTree Char sampleTree = Node (Leaf 'a') (Node (Leaf 'b') (Leaf 'c')) -- 示例字典:每个符号对应8位Bit串(随便写的) sampleDict :: CodeDict Char sampleDict = M.fromList [('a', replicate 8 I), ('b', replicate 7 I ++ [O]), ('c', replicate 6 I ++ replicate 2 O)] -- 生成反向字典 sampleRevDict :: ReverseCodeDict Char sampleRevDict = M.fromList . map swap $ M.toList sampleDict -- 测试:序列化后反序列化是否和原树一致 test :: Bool test = let serialized = serializeHtree sampleDict sampleTree (deserialized, _) = deserializeHtree sampleRevDict serialized in deserialized == sampleTree
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

