Cirdec's answer一个很大程度上不相关的问题让我想知道如何最好地用恒定时间加法表示自然数,减去1,并测试零。
假设我们使用
data Nat = Z | S Nat
然后我们可以写
Z + n = n
S m + n = S(m+n)
我们可以在O(1)时间内计算m+n
m-r
借记(对于某些常量r
),每S
个构造函数添加一个n
}}。要获得O(1)isZero
,我们需要确保每个p
构造函数最多只有S
个借方,对于某个常量p
。如果我们计算a + (b + (c+...))
,这会很有效,但如果我们计算((...+b)+c)+d
,它就会崩溃。麻烦的是,借方在前端叠加。
简单的方法就是直接使用可伸缩列表,例如Okasaki描述的列表。有两个问题:
O(n)空间并不理想。
我们并不完全清楚(至少对我来说)当我们不关心订单时,引导队列的复杂性是必要的。
< / LI> 醇>答案 0 :(得分:5)
据我所知,Idris(一种非常接近Haskell的依赖类型的纯函数式语言)以一种非常直接的方式处理这个问题。编译器知道Nat
和Fin
s(上限Nat
s)并尽可能用机器整数类型和操作替换它们,因此生成的代码非常有效。但是,对于自定义类型(甚至是同构类型)以及编译阶段来说都不是这样(有一些代码示例使用Nat
进行类型检查导致编译时指数增长,我可以提供它们需要)。
对于Haskell,我认为可以实现类似的编译器扩展。另一种可能性是制作可以转换代码的TH宏。当然,这两种选择并不容易。
答案 1 :(得分:4)
我的理解是,在基本的计算机编程术语中,潜在的问题是你想在恒定的时间内连接列表。这些列表没有像前向参考那样的作弊,所以你不能在O(1)时间内跳到最后。例如。
您可以使用环代替,无论是否使用a+(b+(c+...))
或((...+c)+b)+a
逻辑,您都可以在O(1)时间内合并。环中的节点不需要双向链接,只需链接到下一个节点。
减法是删除任何节点O(1),并且测试零(或一)是微不足道的。但是,n > 1
的测试是O(n)。
如果您想减少空间,那么在每个操作中,您可以合并插入点或删除点处的节点,并将剩余的节点加权。您执行的操作越多,表示形式就越紧凑!我认为最糟糕的情况仍然是O(n)。
答案 2 :(得分:3)
我们知道有两种“极值”解决方案可以有效地添加自然数:
CPU效率仅使用 O(1)时间。 (请参阅本书中的“结构抽象”一章。)但是,解决方案使用 O(n)内存,因为我们将自然数 n 表示为 n ()
。
我还没有完成实际计算,但我相信 O(1)数字加法我们不需要 O(1)的全部功能FIFO队列,以相同的方式引导标准列表[]
(LIFO)就足够了。如果您有兴趣,我可以尝试详细说明。
CPU高效解决方案的问题是我们需要为内存表示添加一些冗余,以便我们可以节省足够的CPU时间。在某些情况下,可以在不影响存储器大小的情况下完成添加这种冗余(例如 O(1)递增/递减操作)。如果我们允许任意树形状,例如在具有自举列表的CPU高效解决方案中,只需too many tree shapes就可以在 O(log n)内存中区分它们。
所以问题是:我们能找到恰当数量的冗余,以便线性的内存量足以让我们实现 O(1)另外?我相信答案是没有:
让我们有一个表示+算法,其中添加了 O(1)时间。然后让我们得到 m -bits的数量,我们计算它们是 2 ^ k 数的总和,每个数字的大小为( MK)位。为了表示那些我们需要(不管代表性)最小的(mk)位的内存,所以在开始时,我们从(至少)(mk)2 ^开始k 位内存。现在,在每个 2 ^ k 添加中,我们可以预先形成一定数量的操作,因此我们能够处理(并理想地删除)总计 C 2 ^ k 位。因此,最后,我们需要表示结果的位数的下限是(m-k-C)2 ^ k 位。由于 k 可以任意选择,我们的对手可以设置 k = mC-1 ,这意味着总和将用至少 2 ^表示(mC- 1)= 2 ^ m / 2 ^(C + 1)∈O(2 ^ m)位。所以自然数 n 总是需要 O(n)位的内存!