Haskell 用于在O(深度)中填充树的函数
有以下练习:Haskell 用于在O(深度)中填充树的函数,haskell,tree,Haskell,Tree,有以下练习: -- 2.5 Sharing can be useful within a single object, not just between objects. -- For example, if the two subtress of a given node are identical, then they can -- be represented by the same tree. -- Part a: make a `complete a Int` function th
-- 2.5 Sharing can be useful within a single object, not just between objects.
-- For example, if the two subtress of a given node are identical, then they can
-- be represented by the same tree.
-- Part a: make a `complete a Int` function that creates a tree of
-- depth Int, putting a in every leaf of the tree.
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
--2.5共享在单个对象内非常有用,而不仅仅是在对象之间。
--例如,如果给定节点的两个子应力相同,则它们可以
--由同一棵树表示。
--a部分:制作一个“completeaint”函数,创建一个
--深度Int,在树的每一片叶子上加一个。
完成::a->整数->可能(树a)
完全x深度
|深度<0=无
|否则=仅$complete'深度
完成的地方
|d==0=空
|否则=复制树=完成”(d-1)
在节点x中复制树复制树
此实现是否在O(d)
时间内运行?你能说说为什么吗?理论答案
不,它不会在O(d)
时间内运行。Its由整数
减法d-1
控制,这需要O(logd)
时间。这是重复的O(d)
次,给出了O(d log d)
时间的渐近上界
如果使用具有渐近最优O(1)
减量的整数
表示,则该上界可以得到改善,由于渐近最优的整数
实现速度较慢,即使对于难以想象的大值也是如此
实际上,整数
算法只占程序运行时间的一小部分。对于实际的“大”深度(小于机器字),程序的运行时间将由分配和填充内存决定。对于更大的深度,您将耗尽计算机的资源
实际答案
询问运行时系统的
为了分析您的代码,我们首先需要确保它已运行。Haskell是惰性评估的,因此,除非我们采取措施使树得到完全评估,否则它可能不会。不幸的是,完全探索树需要O(2^d)
步骤。我们可以避免强制我们已经访问过的节点,如果我们跟踪它们的s。幸运的是,包中已经提供了遍历结构和通过内存位置跟踪访问节点的功能。因为我们将使用它进行评测,所以我们需要在启用评测的情况下安装它(-p
)
使用Data.Reify
需要TypeFamilies
扩展名和控件。Applicative
{-# LANGUAGE TypeFamilies #-}
import Data.Reify
import Control.Applicative
我们复制您的树
代码
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
所需的MuRef
实例要求我们提供一个mapDeRef
以使用Applicative
遍历结构,并将其转换为基函子。为mapdref
提供的第一个参数是如何转换结构的递归引用,我将其命名为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
我将此代码放在名为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
它在条目列中显示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
代码中有趣的部分是complete'
函数:
complete' d
| d == 0 = Empty
| otherwise = let copiedTree = complete' (d-1)
in Node x copiedTree copiedTree
正如所建议的,我们应该仔细分析实现的每个部分,以确保我们的假设是有效的。作为一般规则,我们可以假设以下各项每次花费1个时间单位*
:
使用数据构造函数构造值(例如,使用Empty
生成空树,或使用Node
将一个值和两棵树转换为一棵树)
对值进行模式匹配,以查看它是从哪个数据构造函数构建的,以及数据构造函数应用于哪个值
Guards和if/then/else表达式(在内部使用模式匹配实现)
将整数
与0进行比较
Cirdec提到从整数中减去1的操作是整数大小的对数运算。正如他们所说,这本质上是Integer
实现方式的产物。可以实现整数,这样只需一步就可以将它们与0进行比较,也只需一步就可以将它们递减1。为了使事情变得非常一般,可以安全地假设存在某个函数c,使得减少整数的成本是c(深度)
既然我们已经做好了准备工作,让我们开始工作吧!通常情况下,我们需要建立一个方程组并求解它。设f(d)为计算完成'd
所需的步骤数。那么第一个方程非常简单:
f(0) = 2
这是因为将d
与0进行比较需要一个步骤,检查结果是否为True
需要另一个步骤
另一个等式是有趣的部分。想想当d>0
时会发生什么:
我们计算d==0
我们检查这是否为真
(不是)
我们计算d-1
(我们将结果称为dm1
)
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
complete' d
| d == 0 = Empty
| otherwise = let copiedTree = complete' (d-1)
in Node x copiedTree copiedTree
f(0) = 2
f(0) = 2
f(d) = (3+c(depth)) + f(d-1) when d > 0
f(0) = 2
f(1) = (3+c(depth)) + f(0) = (3+c(depth)) + 2
f(2) = (3+c(depth)) + f(1)
= (3+c(depth)) + ((3+c(depth)) + 2)
= 2*(3+c(depth)) + 2
f(3) = (3+c(depth)) + f(2)
= (3+c(depth)) + (2*(3+c(depth)) + 2)
= 3*(3+c(depth)) + 2
f(d) = d*(3+c(depth)) + 2
f(D+1) = (3+c(depth)) + f(D)
= (3+c(depth)) + (D*(3+c(depth))+2)
= (D+1)*(3+c(depth))+2
f(d) = d*(3+c(depth))+2
f(d) ∈ O(d*c(depth))