Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Algorithm 使用高阶遍历函数查找顺序遍历的第k个元素后中断_Algorithm_Haskell_Binary Tree - Fatal编程技术网

Algorithm 使用高阶遍历函数查找顺序遍历的第k个元素后中断

Algorithm 使用高阶遍历函数查找顺序遍历的第k个元素后中断,algorithm,haskell,binary-tree,Algorithm,Haskell,Binary Tree,我有以下代码来按顺序遍历二叉树: data BinaryTree a = Node a (BinaryTree a) (BinaryTree a) | Leaf deriving (Show) inorder :: (a -> b -> b) -> b -> BinaryTree a -> b inorder f acc tree = go tree acc where go Leaf z = z go (Node v l r) z

我有以下代码来按顺序遍历二叉树:

data BinaryTree a =
  Node a (BinaryTree a) (BinaryTree a)
  | Leaf
  deriving (Show)

inorder :: (a -> b -> b) -> b -> BinaryTree a -> b
inorder f acc tree = go tree acc
  where go Leaf z = z
        go (Node v l r) z = (go r . f v . go l) z
使用上面的inoorder函数,我希望获得第k个元素,而不必遍历整个列表

遍历有点像一个折叠,只要你给它传递一个函数和一个起始值。我想我可以通过传递
k
作为起始值来解决这个问题,并且一个函数将递减
k
,直到它达到0,然后在该点返回当前节点内的值

我的问题是,除了修改整个函数外,我不太确定如何
打破
顺序遍历的递归,但我觉得必须修改高阶函数会破坏首先使用高阶函数的意义


有没有办法在k次迭代后中断?

我观察到在左子树和右子树上递归调用
go
的结果对
f
不可用;因此,无论
f
做什么,它都不能选择忽略递归调用的结果。因此,我相信,按照所写的顺序,
将始终遍历整个树。(edit:回顾一下,这句话可能有点强;
f
似乎有机会忽略左子树。但这一点基本上是正确的;没有理由用这种方式将左子树提升到右子树之上。)

更好的选择是对
f
进行递归调用。例如:

anyOldOrder :: (a -> b -> b -> b) -> b -> BinaryTree a -> b
anyOldOrder f z = go where
    go Leaf = z
    go (Node v l r) = f v (go l) (go r)
现在当我们写作的时候

flatten = anyOldOrder (\v ls rs -> ls ++ [v] ++ rs) []
我们会发现
flatten
足够懒惰:

> take 3 (flatten (Node 'c' (Node 'b' (Node 'a' Leaf Leaf) Leaf) undefined))
"abc"
(未定义的
用于提供证据,证明在遍历过程中从未检查过树的这一部分。)因此,我们可以编写

findK k = take 1 . reverse . take k . flatten
这将正确短路。您可以使用标准技术使
展平
稍微更有效:

flatten' t = anyOldOrder (\v l r -> l . (v:) . r) id t []
为了好玩,我还想展示如何在不使用累加器列表的情况下实现这个函数。相反,我们将生成一个有状态计算,它遍历树的“有趣”部分,在到达
k
th元素时停止。有状态计算如下所示:

import Control.Applicative
import Control.Monad.State
import Control.Monad.Trans.Maybe

kthElem k v l r = l <|> do
    i <- get
    if i == k
        then return v
        else put (i+1) >> r
我们可以验证它是否仍然像预期的那样懒惰:

> findK' 3 $ Node 'c' (Node 'b' (Node 'a' Leaf Leaf) Leaf) undefined
Just 'c'
折叠列表的概念(至少?)有两个重要的概括。第一个更强大的概念是一个反同构。Daniel Wagner回答的
anyOldOrder
遵循这种模式

但是对于你的特殊问题,类同构的概念比你需要的更强大。第二个较弱的概念是可折叠的容器
Foldable
表达了一种容器的概念,该容器的元素可以通过任意
Monoid
的操作混合在一起。这里有一个可爱的小把戏:

{-# LANGUAGE DeriveFoldable #-}

-- Note that for this trick  only I've 
-- switched the order of the Node fields.
data BinaryTree a =
  Node (BinaryTree a) a (BinaryTree a)
  | Leaf
  deriving (Show, Foldable)

index :: [a] -> Int -> Maybe a
[] `index` _ = Nothing
(x : _) `index` 0 = Just x
(_ : xs) `index` i = xs `index` (i - 1)

(!?) :: Foldable f => Int -> f a -> Maybe a
xs !? i = toList xs `index` i
那么您可以直接使用
以索引到树中


这个技巧很可爱,事实上派生
可折叠的
非常方便,但它不会帮助您理解任何东西。首先,我将展示如何在不使用
Foldable
的情况下,相当直接和高效地定义
treetolost

treeToList :: BinaryTree a -> [a]
treeToList t = treeToListThen t []
神奇之处在于
treeToListThen
函数
treeToListThen t more
t
转换为列表,并将列表
more
附加到结果的末尾。事实证明,这种轻微的泛化是使列表转换高效所需的全部

treeToListThen :: BinaryTree a -> [a] -> [a]
treeToListThen Leaf more = more
treeToListThen (Node v l r) more =
  treeToListThen l $ v : treeToListThen r more
我们不需要对左子树进行顺序遍历,然后追加其他所有内容,而是告诉左遍历完成后要在末尾粘贴什么!这避免了重复列表连接的潜在严重低效性,在不好的情况下,重复列表连接会使事情变成O(n^2)

回到可折叠的
概念,将事物转换为列表是
foldr
的一个特例:

toList = foldr (:) []
那么,我们如何为树实现
foldr
?它最终与我们使用
toList
所做的有些相似:

foldrTree :: (a -> b -> b) -> b -> BinaryTree a -> b
foldrTree _ n Leaf = n
foldrTree c n (Node v l r) = foldrTree c rest l
  where
    rest = v `c` foldrTree c n r
也就是说,当我们走到左边时,我们告诉它,当它完成时,它应该处理当前节点及其右子节点

现在
foldr
并不是
Foldable
最基本的操作;那实际上是

foldMap :: (Foldable f, Monoid m)
        => (a -> m) -> f a -> m
可以使用
foldMap
来实现
foldr
,使用一个特殊的
Monoid
,这有点棘手。除非您要求,否则我现在不想给您带来太多的细节(但您应该查看
Data.Foldable
foldr
的默认定义)。相反,我将展示如何使用Daniel Wagner的
anyOldOrder
定义
foldMap

instance Foldable BinaryTree where
  foldMap f = anyOldOrder bin mempty where
    bin lres v rres = lres <> f v <> rres
实例可折叠二进制树,其中
foldMap f=任何旧订单仓位成员,其中
垃圾箱lres v rres=垃圾箱f v rres

以k(顺序为f[]树)
为例,其中
f
是一个函数,它生成一个列表,其中按顺序遍历树。@chepner我认为编写的
inoorder
将始终遍历整个树。显然,最好编写一个“更好的”
in order
函数,当它的
f
足够懒时可以避免这样做;在我看来,这里的问题是关于如何解决的。@chepner,但这并没有提前结束。我原以为这样也行,inorder会懒散地进行计算,但当我使用
获取1(inorder(:)[]testTree)
@AR7 With
go(Node v l r)z=go r(f v(go l z))
时,情况似乎并非如此,最外层的调用是对
go
的递归调用,因此除了递归之外别无选择(因此检查子树,并通过归纳法检查右脊椎上的每个节点)。另一方面,对于
go(node v l r)=fv(go l)(go r)
,最外层的调用是对
f
的非递归调用。这意味着它是
finstance Foldable BinaryTree where
  foldMap f = anyOldOrder bin mempty where
    bin lres v rres = lres <> f v <> rres