Haskell:实现二叉树及其前序、中序、后序遍历和层序遍历

用函数式编程语言实现数据结构,是非常返璞归真的一件事情。

树的定义

用参数化类型定义二叉树。

data Tree a = Empty | Node (Tree a) a (Tree a) deriving (Show)

多叉树可以用左孩子右兄弟来表示。在此基础上,森林可以用“所有的树有共同的根节点”表示成一棵多叉树,从而用左孩子右兄弟表示成二叉树。(或者二叉树森林用“左第一右其余”表示成二叉树。)

树的性质(类型类)

顺手给出树遵从的类型类。下文除了toList之外,与本节无关,阅读时可以跳过。

instance Foldable Tree where
    -- foldMap :: Monoid m => (a -> m) -> Tree a -> m
    foldMap f Empty = mempty
    foldMap f (Node l k r) = foldMap f l `mappend` f k `mappend` foldMap f r

instance Functor Tree where
    -- fmap :: (a -> b) -> Tree a -> Tree b
    fmap f Empty = Empty
    fmap f (Node l k r) = Node (fmap f l) (f k) (fmap f r)

instance Traversable Tree where
    -- traverse :: (Traversable t, Applicative f) => (a -> f b) -> Tree a -> f (Tree b)
    traverse f Empty = pure Empty
    traverse f (Node l k r) = Node <$> traverse f l <*> f k <*> traverse f r

前中后序遍历

简单递归即可。

import Data.Foldable -- toList

preOrderTraversal :: Tree a -> [a]
preOrderTraversal Empty = []
preOrderTraversal (Node l k r) = 
    k:(preOrderTraversal l) ++ preOrderTraversal r

inOrderTraversal :: Tree a -> [a]
inOrderTraversal = toList

postOrderTraversal :: Tree a -> [a]
postOrderTraversal Empty = []
postOrderTraversal (Node l k r) = 
    postOrderTraversal l ++ postOrderTraversal r ++ [k]

注意中序遍历对库函数Data.Foldable.toList的使用。因为data Tree的结构限制,三种遍历顺序中总是只有一种能使用库函数,剩下两种结构需要重写一遍。

层序遍历

思路是bfs:使用一个队列(这里用列表模拟),一开始队列中只有原树,每次取出队首的树,将其根节点加入输出列表,将其左右子树加入队列末尾。队列为空时停止。
我们使用一个尾递归来描述每一步的操作。尾递归可以保存状态,最适合这种一步一步改变状态的场景。(进一步地,如果是一个会失败的操作,可以用state monad)

levelOrderTraversal :: Tree a -> [a]
levelOrderTraversal tree = reverse $ step ([], [tree])
    where 
        step (result, trees) = case trees of
            [] -> result
            Empty:ts -> step (result, ts)
            (Node l k r):ts -> step (k:result, ts++[l, r])

注意到,输出列表在尾递归过程中是倒序存储的,以便使用默认构造函数(:)提升效率。在最终返回时reverse成正序。

测试

testTree = Node (Node (Node Empty 4 Empty) 2 Empty) 1 (Node Empty 10 (Node Empty 15 Empty))

testList = map ($ testTree) [preOrderTraversal, inOrderTraversal, postOrderTraversal, levelOrderTraversal]

-- testList == [[1,2,4,10,15],[4,2,1,10,15],[4,2,15,10,1],[1,2,10,4,15]]

目前CSDN不支持Haskell的高亮。在目前支持的高亮样式中,似乎使用swift的高亮效果比较好🤔还是知乎和Gitee比较香orz

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章