所以我试图使用递归(没有标准的前奏函数)向BST中的元素添加连续数字。以下是我到目前为止的情况:
data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show)
leaf x = Node x Empty Empty
number' :: Int -> Tree a -> Tree (Int, a)
number' a Empty = Empty
number' a (Node x xl xr) = Node (a,x) (number' (a+1) xl) (number' (a+1) xr)
number :: Tree a -> Tree (Int, a)
number = number' 1
number'是一个辅助函数,它带有“a”作为计数器。它应该为每个递归调用添加1,所以我不确定为什么它正在做它正在做的事情。 截至目前,元素的级别被分配给每个元素。我希望第一个元素分配1,该元素左侧的元素,该元素左侧的元素,等等。每个元素应该得到一个+ 1,并且不应该重复任何数字。提前谢谢。
答案 0 :(得分:2)
data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show)
number :: Tree a -> Tree (Int, a)
number = fst . number' 1
number' :: Int -> Tree a -> (Tree (Int, a), Int)
number' a Empty = (Empty, a)
number' a (Node x l r) = let (l', a') = number' (a + 1) l
(r', a'') = number' a' r
in (Node (a, x) l' r', a'')
*Tr> let t = (Node 10 (Node 20 (Node 30 Empty Empty) (Node 40 Empty Empty)) (Node 50 (Node 60 Empty Empty) Empty))
*Tr> t
Node 10 (Node 20 (Node 30 Empty Empty) (Node 40 Empty Empty)) (Node 50 (Node 60 Empty Empty) Empty)
*Tr> number t
Node (1,10) (Node (2,20) (Node (3,30) Empty Empty) (Node (4,40) Empty Empty)) (Node (5,50) (Node (6,60) Empty Empty) Empty)
答案 1 :(得分:2)
我想首先解释为什么问题中的代码会分配级别数字。这将直接引导我们两个不同的解决方案,一个传递缓存,一个基于一次执行两次遍历。最后,我展示了第二个解决方案如何与其他答案提供的解决方案相关。
问题中的代码为每个节点分配级别编号。通过查看number'
函数的递归情况,我们可以理解为什么代码的行为如此:
number' a (Node x xl xr) = Node (a,x) (number' (a+1) xl) (number' (a+1) xr)
请注意,对于两个递归调用,我们使用相同的数字a + 1
。因此,两个子树中的根节点将被分配相同的数字。如果我们希望每个节点具有不同的数字,我们最好将不同的数字传递给递归调用。
如果我们想根据从左到右的预先遍序遍历来分配数字,那么a + 1
对于左子树上的递归调用是正确的,但对于右子树上的递归调用则不正确。相反,我们想要留下足够的数字来注释整个左子树,然后开始用下一个数字注释右子树。
我们需要为左子树保留多少个数字?这取决于子函数的大小,由此函数计算:
size :: Tree a -> Int
size Empty = 0
size (Node _ xl xr) = 1 + size xl + size xr
回到number'
函数的递归情况。左子树中某处注释的最小数字是a + 1
。左子树中某处注释的最大数字是a + size xl
。因此,适用于正确子树的最小数字是a + size xl + 1
。这种推理导致以下实现number'
的递归情况正常工作:
number' :: Int -> Tree a -> Tree (Int, a)
number' a Empty = Empty
number' a (Node x xl xr) = Node (a,x) (number' (a+1) xl) (number' (a + size xl + 1) xr)
不幸的是,这个解决方案存在问题:它不必要地慢。
size
的解决方案会变慢?函数size
遍历整个树。函数number'
也遍历整个树,并在所有左子树上调用size
。每个调用都将遍历整个子树。总的来说,函数size
在同一个节点上执行多次,即使它总是返回相同的值。
size
时,我们如何避免遍历树?我知道两个解决方案:我们要么通过缓存所有树的大小来避免在size
的实现中遍历树,要么我们通过编号节点和计算来避免首先调用size
一次遍历的大小。
我们在每个树节点中缓存大小:
data Tree a = Empty | Node Int a (Tree a) (Tree a) deriving (Show)
size :: Tree a -> Int
size Empty = 0
size (Node n _ _ _) = n
请注意,在Node
size
的情况下,我们只返回缓存的大小。所以这种情况不是递归的,size
不会遍历树,上面number'
实现的问题就消失了。
但有关size
的信息必须来自某个地方!每次我们创建Node
时,我们都必须提供正确的大小来填充缓存。我们可以将此任务提升为智能构造函数:
empty :: Tree a
empty = Empty
node :: a -> Tree a -> Tree a -> Tree a
node x xl xr = Node (size xl + size xr + 1) x xl xr
leaf :: a -> Tree a
leaf x = Node 1 x Empty Empty
只有node
才是必要的,但为了完整性我添加了另外两个。如果我们总是使用这三个函数之一来创建树,则缓存的大小信息将始终是正确的。
以下是适用于这些定义的number'
版本:
number' :: Int -> Tree a -> Tree (Int, a)
number' a Empty = Empty
number' a (Node _ x xl xr) = node (a,x) (number' (a+1) xl) (number' (a + size xl + 1) xr)
我们必须调整两件事:当Node
上的模式匹配时,我们会忽略大小信息。在创建Node
时,我们使用智能构造函数node
。
工作正常,但它的缺点是必须更改树的定义。一方面,无论如何缓存大小可能是一个好主意,但另一方面,它使用一些内存并强制树是有限的。如果我们想在不改变树的定义的情况下实现快速number'
,该怎么办?这使我们得到了我承诺的第二个解决方案。
我们做不到。但我们可以对树进行编号并在一次遍历中计算大小,从而避免多次size
次调用。
number' :: Int -> Tree a -> (Int, Tree (Int, a))
在类型签名中,我们看到此版本的number'
计算两条信息:结果元组的第一个组件是树的大小,第二个组件是带注释的树。 / p>
number' a Empty = (0, Empty)
number' a (Node x xl xr) = (sl + sr + 1, Node (a, x) yl yr) where
(sl, yl) = number' (a + 1) xl
(sr, yr) = number' (a + sl + 1) xr
该实现从递归调用中分解元组并组成结果的组件。请注意,sl
与上一个解决方案中的size xl
类似,sr
与size xr
类似。我们还必须命名带注释的子树:yl
是带节点编号的左子树,因此它与前一个解决方案中的number' ... xl
类似,yr
是带节点编号的右子树,所以它就像上一个解决方案中的number' ... xr
一样。
我们还必须将number
更改为仅返回number'
结果的第二个组成部分:
number :: Tree a -> Tree (Int, a)
number = snd . number' 1
我认为在某种程度上,这是最明智的解决方案。
之前的解决方案通过返回子树的大小来工作。然后,该信息用于计算下一个可用节点号。相反,我们也可以直接返回下一个可用的节点号。
number' a Empty = (a, Empty)
number' a (Node x xl xr) = (ar, Node (a, x) yl yr) where
(al, yl) = number' (a + 1) xl
(ar, yr) = number' al xr
请注意,al
与上一个解决方案中的a + sl + 1
类似,ar
与a + sl + sr + 1
类似。显然,这种改变避免了一些补充。
这基本上是谢尔文回答的解决方案,我希望这是大多数Haskellers写的版本。你也可以在状态monad中隐藏a
,al
和ar
的操作,但我不认为这对这么小的例子有帮助。 Ankur的答案显示了它的样子。
答案 2 :(得分:1)
正如您的问题中的评论所示,每次调用number
都应该返回一个整数,还需要进一步用于下一组节点。这使得函数的签名为:
Tree a -> Int -> (Tree (Int,a), Int)
看看它的最后一部分,它看起来像是国家单子的候选人,即state -> (Val,state)
。
下面的代码显示了如何使用State monad执行此操作。
import Control.Monad.State
data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show)
myTree :: Tree String
myTree = Node "A" (Node "B" (Node "D" Empty Empty) (Node "E" Empty Empty)) (Node "C" (Node "F" Empty Empty) (Node "G" Empty Empty))
inc :: State Int ()
inc = do
i <- get
put $ i + 1
return ()
number :: Tree a -> State Int (Tree (Int,a))
number Empty = return Empty
number (Node x l r) = do
i <- get
inc
l' <- number l
r' <- number r
return $ Node (i,x) l' r'
main = do
putStrLn $ show (fst (runState (number myTree) 1))