Haskell 不同的二叉树&x27;哈斯克尔的定义:哪一个赢?

Haskell 不同的二叉树&x27;哈斯克尔的定义:哪一个赢?,haskell,binary-tree,Haskell,Binary Tree,我习惯了以下树定义: data Tree a = Empty | Node a (Tree a) (Tree a) 直到我在某处遇到了这个: data Tree a = Empty | Leaf a | Node a (Tree a) (Tree a) 这让我对Haskell的习语感到好奇 既然叶a只是节点一个空的,那么这个构造函数应该存在吗?我们也可以使用一个独特的构造函数来删除Empty Tree (Maybe (a, (Tree a), (Tree a))) 或者类似的 我写的第二个

我习惯了以下
定义:

data Tree a = Empty | Node a (Tree a) (Tree a)
直到我在某处遇到了这个:

data Tree a = Empty | Leaf a | Node a (Tree a) (Tree a)
这让我对Haskell的习语感到好奇

既然
叶a
只是
节点一个空的
,那么这个构造函数应该存在吗?我们也可以使用一个独特的构造函数来删除
Empty

Tree (Maybe (a, (Tree a), (Tree a)))
或者类似的


我写的第二个定义是“最扩展的”,第一个定义介于它和最后一个定义之间。从实践和理论上来说,什么是最好的?换句话说,关于性能和数据类型的设计呢?

如果您想要惯用的Haskell,请使用第一个定义,因为这样您就没有那么多的构造函数可供模式匹配

如果您有大量叶子的大型二叉树,如果您希望每个叶子节省大约16个字节(额外的
树a
-指针)的内存(很大程度上取决于您使用的平台/编译器节省了多少内存),请使用第二个定义


您提出的第三个备选方案在技术上是一个有效的表示法(假设您指的是
树(可能是(a,Tree a,Tree a))
,但处理起来非常繁琐。

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
在更长的时间里,你可以这样写:

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

同样的方法也可以用于基于
的替代方法或我的“五个构造函数”替代方法。此外,这种技术也可以应用于树上您需要的任何其他通用函数。(事实上,很多函数都可以用
foldTree
编写,因此大多数函数都不属于上述定义。)

第三个,
树(可能是一棵(树a)(树a))
没有编译。你是说
树(可能是一棵(树a,树a,树a))
?我明白了,所以第二个定义可以使用更少的内存,但第一个可以更快,对吗?第三个呢?同时添加两个构造函数是不是一个好主意:
节点a(树a)
要在节点a(树a)为空的情况下节省8个字节
?您必须进行基准测试,以查看可能需要多少专用构造函数;可能拥有4个构造函数的成本太高,一些编译器将使用“标记的联合”对于ADT来说,让它们使用相同的内存量。然而,第三个版本更复杂,效率更低。但大多数情况下,你不会有真正巨大的二叉树,所以所有这些都不重要。如果你想在一棵树中存储大量整数,你应该考虑使用某种前缀树。你的五个构造函数一看起来不太可能。我认为,更合理的做法是让数据树a=Empty | NETree a和数据树a=Branch a(NETree a)(NETree a)| LeftOnly a(NETree a)| right only a(NETree a)| Leaf a。是的,这需要更多一点时间,但它强制了名称所暗示的约束。