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

Haskell 为BST中的元素分配连续编号,haskell,recursion,binary-tree,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) = No

因此,我试图严格使用递归(没有标准的前奏函数)向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
“数字”是一个辅助函数,携带“a”作为计数器。它应该为每个递归调用添加1,所以我不确定它为什么要这样做。 到目前为止,元素的级别已指定给每个元素。我希望第一个元素分配为1,第二个元素左边的元素,第三个元素左边的元素,等等。每个元素都应该分配一个+1,不应该重复任何数字。提前谢谢

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'')



正如您问题中的评论所建议的那样,对
number
的每次调用都应返回一个整数,该整数还需要进一步用于下一组节点。这使得函数的签名:

树a->Int->(树(Int,a),Int)

查看它的最后一部分,它看起来像是State monad的候选者,即
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))
import Control.Monad.State
数据树a=空|节点a(树a)(树a)派生(显示)
myTree::树字符串
myTree=节点“A”(节点“B”(节点“D”为空)(节点“E”为空)(节点“C”(节点“F”为空)(节点“G”为空))
inc::State Int()
inc=do
i状态Int(树(Int,a))
number Empty=返回空
编号(节点x l r)=do

正如您问题中的评论所建议的那样,每次调用
number
都应该返回一个整数,该整数还需要进一步用于下一组节点。这使得函数的签名:

树a->Int->(树(Int,a),Int)

查看它的最后一部分,它看起来像是State monad的候选者,即
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))
import Control.Monad.State
数据树a=空|节点a(树a)(树a)派生(显示)
myTree::树字符串
myTree=节点“A”(节点“B”(节点“D”为空)(节点“E”为空)(节点“C”(节点“F”为空)(节点“G”为空))
inc::State Int()
inc=do
i状态Int(树(Int,a))
number Empty=返回空
编号(节点x l r)=do

我想首先解释一下为什么问题中的代码会指定级别编号。这将直接引导我们找到两种不同的解决方案,一种是通过缓存传递,另一种是基于同时进行两次遍历。最后,我将展示第二个解决方案与其他答案提供的解决方案之间的关系

问题代码中需要更改哪些内容? 问题中的代码为每个节点分配级别编号。通过查看
number'
函数的递归情况,我们可以理解代码为何会有这样的行为:

number' a (Node x xl xr) = Node (a,x) (number' (a+1) xl) (number' (a+1)  xr) 
size :: Tree a -> Int
size Empty = 0
size (Node _ xl xr) = 1 + size xl + size xr
请注意,对于这两个递归调用,我们使用相同的数字,
a+1
。因此,两个子树中的根节点将被分配相同的编号。如果我们希望每个节点都有一个不同的数字,我们最好将不同的数字传递给递归调用

我们应该向递归调用传递什么号码? 如果我们想根据从左到右的预顺序遍历来分配数字,那么
a+1对于左子树上的递归调用是正确的,但是对于右子树上的递归调用则不正确。相反,我们希望留下足够的数字来注释整个左子树,然后开始用下一个数字注释右子树

我们需要为左子树保留多少个数字?这取决于此函数计算的子树大小:

number' a (Node x xl xr) = Node (a,x) (number' (a+1) xl) (number' (a+1)  xr) 
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
注意,在
节点
大小
情况下,我们只返回缓存的大小。因此,这种情况不是递归的,
size
不会遍历树,上面的
number'
实现的问题就消失了

但是关于
大小的信息必须来自某个地方!每次创建
节点
,我们都必须提供正确的大小来填充缓存。我们可以将此任务交给聪明的施工人员:

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
只有
节点
是真正必要的,但为了完整性,我添加了另外两个节点。如果我们总是使用这三个函数中的一个来创建树,那么缓存的大小信息总是正确的

以下是使用这些定义的
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)
我们必须调整两件事:在
节点上进行模式匹配时,忽略大小信息。在创建
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