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

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