在Haskell中,可以在常量空间中对无限列表进行过滤,求和等,因为Haskell只在需要时生成列表节点,而垃圾收集它完成的列表。
我希望能与无限的树木一起工作。
下面是一个相当愚蠢的程序,它生成一个无限二叉树,其中的节点代表自然数。
然后,我编写了一个函数,对该树进行深度优先遍历,在特定级别吐出节点。
然后,我已经对可以除以5的节点做了快速求和。
理论上,该算法可以在O(n)
空间中为n
节点的O(2^n)
深度树实现。只需动态生成树,删除已经完成处理的节点。
Haskell确实生成了树,但没有垃圾收集它看起来的节点。
以下是代码,我希望看到具有类似效果但不需要O(2^n)
空间的代码。
import Data.List (foldl')
data Tree = Tree Int Tree Tree
tree n = Tree n (tree (2 * n)) (tree (2 * n + 1))
treeOne = tree 1
depthNTree n x = go n x id [] where
go :: Int -> Tree -> ([Int] -> [Int]) -> [Int] -> [Int]
go 0 (Tree x _ _) acc rest = acc (x:rest)
go n (Tree _ left right) acc rest = t2 rest where
t1 = go (n - 1) left acc
t2 = go (n - 1) right t1
main = do
x <- getLine
print . foldl' (+) 0 . filter (\x -> x `rem` 5 == 0) $ depthNTree (read x) treeOne
答案 0 :(得分:2)
您的depthNTree
使用2^n
空格,因为当您遍历正确的子树时,通过t1
保留左子树。对右子树的递归调用应该不包含对左边的引用,作为逐步垃圾收集遍历的必要条件。
这个天真的版本在这个例子中可以接受:
depthNTree n t = go n t where
go 0 (Tree x _ _) = [x]
go n (Tree _ l r) = go (n - 1) l ++ go (n - 1) r
现在main
输入24使用2 MB空间,而原始版本使用1820 MB。此处的最佳解决方案与上述类似,不同之处在于它使用差异列表:
depthNTree n t = go n t [] where
go 0 (Tree x _ _) = (x:)
go n (Tree _ l r) = go (n - 1) l . go (n - 1) r
在许多情况下,这并不比普通列表版本快得多,因为树形深度大约在20-30左右,++
的左嵌套并不是非常昂贵。如果我们使用大树深度,差异会变得更加明显:
print $ sum $ take 10 $ depthNTree 1000000 treeOne
在我的计算机上,这在差异列表中运行0.25秒,在列表中运行1.6秒。