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))