理论答案
不,它不会在O(d) 时间运行。它的asymptotic performance 由Integer 减法d-1 支配,这需要O(log d) 时间。这会重复O(d) 次,给出时间的渐近上限O(d log d)。
如果您使用具有渐近最优O(1) 减量的Integer 表示,则此上限可以提高。 In practice we don't,因为渐近最优的 Integer 实现即使对于难以想象的大值也会更慢。
实际上Integer 算术将是程序运行时间的一小部分。对于实际的“大”深度(小于机器字),程序的运行时间将取决于分配和填充内存。对于更大的深度,您将耗尽计算机的资源。
实用答案
询问运行时系统的profiler。
为了分析您的代码,我们首先需要确保它已运行。 Haskell 是惰性求值的,因此,除非我们做一些事情来使树被完全求值,否则它可能不会。不幸的是,完全探索这棵树需要O(2^d) 步骤。如果我们跟踪它们的StableNames,我们可以避免强制我们已经访问过的节点。幸运的是,data-reify 包已经提供了遍历结构并通过其内存位置跟踪访问的节点。由于我们将使用它进行分析,我们需要在启用分析的情况下安装它 (-p)。
cabal install -p data-reify
使用Data.Reify 需要TypeFamilies 扩展和Control.Applicative。
{-# LANGUAGE TypeFamilies #-}
import Data.Reify
import Control.Applicative
我们复制您的Tree 代码。
data Tree a = Empty | Node a (Tree a) (Tree a)
complete :: a -> Integer -> Maybe (Tree a)
complete x depth
| depth < 0 = Nothing
| otherwise = Just $ complete' depth
where complete' d
| d == 0 = Empty
| otherwise = let copiedTree = complete' (d-1)
in Node x copiedTree copiedTree
使用 data-reify 将数据转换为图形需要我们有一个数据类型的基本函子。基本函子是删除了显式递归的类型的表示。 Tree 的基本函子是 TreeF。增加了一个额外的类型参数来表示该类型的递归出现,并且每次递归出现都被新的参数替换。
data TreeF a x = EmptyF | NodeF a x x
deriving (Show)
reifyGraph 所需的 MuRef 实例要求我们提供一个 mapDeRef 以使用 Applicative 遍历结构并将其转换为基本函子。提供给mapDeRef(我将其命名为deRef)的第一个参数是我们如何转换结构的递归出现。
instance MuRef (Tree a) where
type DeRef (Tree a) = TreeF a
mapDeRef deRef Empty = pure EmptyF
mapDeRef deRef (Node a l r) = NodeF a <$> deRef l <*> deRef r
我们可以编写一个小程序来运行来测试complete 函数。当图表很小时,我们将它打印出来看看发生了什么。当图变大时,我们只会打印出它有多少个节点。
main = do
d <- getLine
let (Just tree) = complete 0 (read d)
graph@(Graph nodes _) <- reifyGraph tree
if length nodes < 30
then print graph
else print (length nodes)
我将此代码放在一个名为profileSymmetricTree.hs 的文件中。要编译它,我们需要使用-prof 启用分析并使用-rtsopts 启用运行时系统。
ghc -fforce-recomp -O2 -prof -fprof-auto -rtsopts profileSymmetricTree.hs
当我们运行它时,我们将使用+RTS 选项-p 启用时间配置文件。我们将在第一次运行时为其提供深度输入 3。
profileSymmetricTree +RTS -p
3
let [(1,NodeF 0 2 2),(2,NodeF 0 3 3),(3,NodeF 0 4 4),(4,EmptyF)] in 1
我们已经从图中可以看出,节点在树的左右两侧共享。
分析器创建一个文件,profileSymmetricTree.prof。
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 43 0 0.0 0.7 100.0 100.0
main Main 87 0 100.0 21.6 100.0 32.5
...
main.(...) Main 88 1 0.0 4.8 0.0 5.1
complete Main 90 1 0.0 0.0 0.0 0.3
complete.complete' Main 92 4 0.0 0.2 0.0 0.3
complete.complete'.copiedTree Main 94 3 0.0 0.1 0.0 0.1
它在entries 列中显示complete.complete' 被执行4 次,complete.complete'.copiedTree 被评估3 次。
如果你用不同的深度重复这个实验,并绘制结果,你应该会很好地了解complete 的实际渐近性能是什么。
以下是更深入的分析结果,300000。
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 43 0 0.0 0.0 100.0 100.0
main Main 87 0 2.0 0.0 99.9 100.0
...
main.(...) Main 88 1 0.0 0.0 2.1 5.6
complete Main 90 1 0.0 0.0 2.1 5.6
complete.complete' Main 92 300001 1.3 4.4 2.1 5.6
complete.complete'.copiedTree Main 94 300000 0.8 1.3 0.8 1.3