不同的二叉树在Haskell中的定义:哪些获胜?

时间:2012-07-31 20:16:02

标签: haskell binary-tree

我习惯了以下Tree定义:

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

直到我遇到某个地方:

data Tree a = Empty | Leaf a | Node a (Tree a) (Tree a)

让我对Haskell的习语感到好奇。

由于Leaf a只是Node a Empty Empty,这个构造函数是否存在?我们也可以使用像

这样的独特构造函数删除Empty
Tree (Maybe (a, (Tree a), (Tree a)))

或类似的东西。

我写的第二个定义是“扩展最多”的定义,第一个定义是它与最后一个定义的中间位置。什么是实际和理论上最好的?换句话说,性能和数据类型的设计呢?

2 个答案:

答案 0 :(得分:7)

如果你想要惯用的Haskell,请使用第一个定义,因为那样你就可以减少与模式匹配的构造函数。

如果你有大量叶子的巨大二叉树,如果你想为每片叶子节省大约16个字节(额外的Tree a - 指针),请使用第二个定义(在很大程度上取决于哪个平台/编译器)你正在使用多少内存。

您提出的第三种替代方案在技术上是一种有效的表示形式(假设您的意思是Tree (Maybe (a, Tree a, Tree a)),但使用起来非常繁琐。

答案 1 :(得分:6)

dflemstr的回答很明显,但我想我会添加两个评论(原评论的评论不能容纳)。

首先,通过第二个定义可以节省内存的相同逻辑,可以对此进行类似的论证:

data Tree a = Empty 
            | Leaf a 
            | LeftOnly a (Tree a) 
            | RightOnly a (Tree a) 
            | Branch a (Tree a) (Tree a)

这是否真正重要取决于您的申请。

第二个也是更重要的一点是,如果您避免直接使用数据构造函数,则可以从这些实现选择中抽象出来。例如,可以为任何这些类型编写等效的foldTree函数。对于较短的类型,你可以这样做:

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

foldTree :: (a -> b -> b -> b) -> b -> Tree a -> b
foldTree f z Empty = z
foldTree f z (Node v l r) = f v (subfold l) (subfold r)
    where subfold = foldTree f z

对于较长的一个,你可以这样写:

data Tree a = Empty | Leaf a | Node a (Tree a) (Tree a)

foldTree :: (a -> b -> b -> b) -> b -> Tree a -> b
foldTree f z Empty = z
foldTree f z (Leaf v) = f v z z
foldTree f z (Node v l r) = f v (subfold l) (subfold r)
    where subfold = foldTree f z

对于基于Maybe的替代方案或我的五构造函数替代方案,也可以这样做。此外,此技术可以应用于您需要的树上的任何其他通用函数。 (事实上​​,很多这些函数都可以用foldTree来编写,所以大多数函数都不属于上面的定义。)