为BST中的元素分配连续编号

时间:2013-07-11 21:26:40

标签: haskell recursion binary-tree

所以我试图使用递归(没有标准的前奏函数)向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,并且不应该重复任何数字。提前谢谢。

3 个答案:

答案 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类似,srsize 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类似,ara + sl + sr + 1类似。显然,这种改变避免了一些补充。

这基本上是谢尔文回答的解决方案,我希望这是大多数Haskellers写的版本。你也可以在状态monad中隐藏aalar的操作,但我不认为这对这么小的例子有帮助。 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))