[英]How can natural numbers be represented to offer constant time addition?
Cirdec对一个基本无关的问题的回答使我想知道如何最好地表示自然数并进行恒定的加法,减一并测试零。
假设我们使用
data Nat = Z | S Nat
然后我们可以写
Z + n = n
S m + n = S(m+n)
我们可以通过放置mr
借记项(对于某个常数r
),在O(1)时间内计算m+n
在添加到n
每个S
构造函数上加一个。 为了获得O(1) isZero
,对于某个常数p
,我们需要确保每个S
构造函数最多具有p
借方。 如果我们计算a + (b + (c+...))
,那么效果很好,但是如果我们计算((...+b)+c)+d
,它就会崩溃。 问题在于借方堆积在前端。
简单的方法是直接使用可链接的列表,例如Okasaki所描述的列表。 有两个问题:
O(n)空间不是很理想。
尚不清楚(至少对我来说),当我们不关心列表的顺序时,是否需要引导队列的复杂性。
据我所知,Idris(一种依赖类型的纯函数式语言,与Haskell非常接近)以一种非常直接的方式处理该问题。 编译器了解Nat
和Fin
(上限Nat
),并在可能的情况下将它们替换为机器整数类型和运算,因此生成的代码非常有效。 但是,对于自定义类型(甚至是同构类型)以及编译阶段,情况并非如此(有一些代码示例使用Nat
进行类型检查,这导致编译时呈指数增长,我可以根据需要提供它们)。
对于Haskell,我认为可以实现类似的编译器扩展。 另一种可能性是制作TH宏来转换代码。 当然,这两种选择都不容易。
我的理解是,在基本的计算机编程术语中,潜在的问题是您希望在固定时间内连接列表。 列表没有前向引用之类的作弊手段,因此,例如,您不能在O(1)时间内跳到最后。
您可以改用环,无论使用a+(b+(c+...))
还是((...+c)+b)+a
逻辑,都可以在O(1)时间合并。 环中的节点不需要双重链接,只需链接到下一个节点即可。
减法是除去任何节点O(1),测试零(或一个)是微不足道的。 但是,对于n > 1
测试为O(n)。
如果要减少空间,则可以在每次操作时在插入或删除点处合并节点,并对其余节点进行加权。 您执行的操作越多,表示就越紧凑! 我认为最坏的情况仍然是O(n)。
我们知道,有两种“极值”解决方案可以有效地相加自然数:
仅使用O(1)时间的CPU效率。 (请参见本书中的“结构抽象”一章。)但是,该解决方案使用O(n)内存,因为我们将自然数n表示为()
的n个副本的列表。
我尚未进行实际计算,但是我相信对于O(1)数值加法,我们将不需要O(1) FIFO队列的全部功能,足以引导标准列表[]
(LIFO)以同样的方式。 如果您有兴趣,我可以尝试详细说明。
CPU高效解决方案的问题在于,我们需要为内存表示添加一些冗余,以便我们可以节省足够的CPU时间。 在某些情况下,可以在不牺牲内存大小的情况下实现添加此类冗余的操作(例如O(1)递增/递减操作)。 而且,如果我们允许任意树形,例如在具有自举列表的CPU高效解决方案中, 树形太多就无法在O(log n)内存中加以区分。
所以问题是:我们能否找到合适数量的冗余,以便亚线性内存足够,并且可以实现O(1)加法? 我相信答案是否定的 :
让我们有一个具有O(1)时间加法的表示形式+算法。 然后,我们得到m位的大小,我们将其计算为2 ^ k个数字的总和,每个数字都为(mk)位的大小。 为了表示每个求和数,我们需要(不考虑表示形式)最少(mk)位存储器,因此,一开始,我们从(至少) (mk)2 ^ k位存储器开始。 现在,在这2 ^ k个加法中的每个加法中,我们都可以执行恒定数量的操作,因此我们能够处理(理想情况下,删除)总共C 2 ^ k个比特。 因此,最后,我们需要表示结果的位数的下限是(mkC)2 ^ k位。 由于k可以任意选择,我们的对手可以设置k = mC-1 ,这意味着总和将至少用2 ^(mC-1)= 2 ^ m / 2 ^(C + 1)∈O( 2 ^ m)位。 因此,自然数n总是需要O(n)位内存!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.