避免二叉树构造中的堆栈溢出

时间:2013-08-08 11:59:13

标签: haskell optimization lazy-evaluation strict

作为学习haskell的一部分,我决定写一个二叉树。

据我了解,如果我对大量插入和删除进行排序,即使生成的树相对较小,我最终也可以轻松达到堆栈溢出。

这是我的问题:

  1. 我可以通过对我的功能引入一些严格来避免这种情况吗? (seq / deepseq的东西?)。

  2. 我想在哪些情况下保持当前状态的插入/删除?

  3. 如果您认为代码设计错误或不正确,请随时更正或改进我的代码。

    相关代码:

    import Data.List
    
    data Tree a = Empty | Branch a (Tree a) (Tree a)
                  deriving (Eq)
    
    leaf x = Branch x Empty Empty
    
    -- insert ------------------------------------
    treeInsert :: (Eq a, Ord a) => Tree a -> a -> Tree a
    treeInsert Empty x  = leaf x
    treeInsert (Branch y l r) x | x<y = Branch y (treeInsert l x) r
                                | x>y = Branch y l (treeInsert r x)
                                | otherwise = Branch x l r  --edit
    
    
    -- delete ------------------------------------
    treeDelete :: (Eq a, Ord a) => Tree a -> a -> Tree a
    treeDelete Empty _ = Empty
    treeDelete (Branch y l r ) x    | y<x   = Branch y l (treeDelete r x)
                                    | y>x   = Branch y (treeDelete l x) r
                                    | y==x  = del' $ Branch y l r
        where
        -- if this Branch is a leaf dispose of it.
        -- if branch has only one child return the child (skip over).
        -- otherwise, replace this branch with its successor (the leftmost child of the right tree)
        --      successor will be extracted from its original location.
        del' ( Branch y Empty Empty )   = Empty
        del' ( Branch y Empty r )       = r
        del' ( Branch y l Empty )       = l
        del' ( Branch y l r )           = Branch ySucc l rWithout_ySucc
    
            where
            ( rWithout_ySucc, ySucc ) = leftmost r
    
                where
                leftmost ( Branch y Empty Empty )   = ( Empty, y )
                leftmost ( Branch y Empty r )       = ( r, y )
                leftmost ( Branch y l r )           = ( Branch y ll r, z ) where ( ll, z ) = leftmost l
    

1 个答案:

答案 0 :(得分:0)

使结构更严格的典型方法不是使其上的函数更严格,而是改变类型本身。

在这种情况下,我们可以更改

data Tree a = Empty | Branch a (Tree a) (Tree a)

data Tree a = Empty | Branch a !(Tree a) !(Tree a)

这意味着,只要你拥有Branch,所包含的两个Tree,也将被强行放在首位。由于树中的a不严格,而只是结构本身,因此称为spine-strict,这是一种非常典型的模式。

您有时可能不想这样做的原因是,现在您一次为所有操作支付“全部费用” - 从树中删除五个级别的元素将强制重建所有五个级别要立即执行新树,而如果你真的不需要实际查看这些路径,那么它们就永远不会被迫。

所以有某些结构(Okasaki非常好地描述了它们),为了获得正确的摊销的渐近性能,你想要增加额外的严格性。

实际上,只需在使用站点强制执行严格性,通常可以避免堆栈溢出。所以,想象一下你在一棵空树上有一个fold,它连续插入了许多元素。如果您使用严格 foldl'(即使使用了懒惰的树),那么与使用延迟折叠时相比,最终会消耗更少的堆栈。