Scheme SICP Ex.1.17-“文件”;“快速乘法”;慢于;乘“;?
以下函数作为本练习的介绍,说明了用加法定义的乘法。这是最简单的“易于写下来”递归定义Scheme SICP Ex.1.17-“文件”;“快速乘法”;慢于;乘“;?,scheme,lisp,multiplication,sicp,mit-scheme,Scheme,Lisp,Multiplication,Sicp,Mit Scheme,以下函数作为本练习的介绍,说明了用加法定义的乘法。这是最简单的“易于写下来”递归定义 (定义(星号a和b) (如果(=b 0) 0 (+a(星a(-B1(())))) 在前面的练习之后,我看到的第一件事是编写一个不会破坏堆栈的迭代形式: (定义(星号a和b) (国际热核实验堆star a b 0)) (定义(star iter a计数器和) (如果(=计数器0) 总和 (国际热核实验堆a(计数器1)(+a总和))) 练习1.17鼓励我们找到一个不变量,以减少问题的规模,其思想是从O(n)到O
(定义(星号a和b)
(如果(=b 0)
0
(+a(星a(-B1(()))))
在前面的练习之后,我看到的第一件事是编写一个不会破坏堆栈的迭代形式:
(定义(星号a和b)
(国际热核实验堆star a b 0))
(定义(star iter a计数器和)
(如果(=计数器0)
总和
(国际热核实验堆a(计数器1)(+a总和)))
练习1.17鼓励我们找到一个不变量,以减少问题的规模,其思想是从O(n)到O(logn)步数(在执行该特定步骤时,不做任何事情来更新结果-我们在该步骤中所做的只是减少/转换问题定义-这就是“查找不变量”的含义)(参见下面第一个代码块的第3行-在该步骤中没有向结果添加任何内容)
对于快速版本,问题是我们应该使用函数halve
和double
,这似乎意味着这些函数可以作为机器操作(恒定时间?)使用。我已经实现了“快速”版本,只需对这些函数进行如下欺骗:
(定义(快星a b)
(条件((或(=B0)(=A0))0)
((偶数?a)(快星(/a2)(*2b)))
(其他(+a(快星a(-B1()()))))
迭代形式(即O(1)空间)中的相同内容:
(注意上面第4行的+a
如何移动到下面第6行末尾的累加器,以使其处于尾部位置)
(定义(快星b)
(快星iter a b 0)
(定义(快速星iter a b和)
(cond((或(=a0)(=b0))和)
((偶数a)(快星iter(/A2)(*2B)和))
(其他(快星国际热核实验堆a(-B1)(+a总和(()))))
所以这是一个“有什么意义”的问题-这些函数比上面给出的前两个慢。这四个函数中的第一个会破坏堆栈,所以它没有用处。但第二个没有。这一个比我测试的这两个“快速”版本中的任何一个都快
我在这里遗漏了什么吗?奇怪的是,是否有一种方法可以实现减半
和加倍
,所以他们实际上给出了这里建议的log(n)结果。否则,这个问题肯定没有意义
请注意,如果a&b的大小不同,那么它们的顺序非常重要-比如乘以2100倍或100,2倍,第一个是100步,后两个是2步。这将是以后添加到这个函数中的一些内容。但是好奇的是
halve
和double
。我认为主要的问题是b
既递减又加倍,即,b
应减半,而不是a
。目前2*100将变为1*200,需要200次递减而不是100次。而if应变为4*50,然后8*25
此外,如果我们减少奇数,结果将是偶数,因此下一步将把b
的值减半。也就是说,至少有一半的迭代将b
的值减半
例如,如果
b
<1048576(2^20),则步数应小于40。通常迭代次数小于(*2(log b2))。您的代码中有一个细微的错误,这就是它速度慢的原因。对于版本3和版本4,这应该可以修复它:
(define (fast-star a b)
(cond ((or (= b 0) (= a 0)) 0)
((even? b) (fast-star (* 2 a) (/ b 2.0)))
(else (+ a (fast-star a (- b 1))))))
(define (fast-star-iter a b sum)
(cond ((or (= a 0) (= b 0)) sum)
((even? b) (fast-star-iter (* 2 a) (/ b 2.0) sum))
(else (fast-star-iter a (- b 1) (+ a sum)))))
我们的想法是在每次迭代中不断增加a
和减少b
,但根据具体情况,有时会减少b
,有时会增加一倍!还要注意,我将b
除以2.0
,以摆脱精确的算法,这会更慢
当然,你可以用另一种方法来做事情:增加
b
和减少a
——重要的是保持一致,将一个参数的问题减半,将另一个参数翻倍,我们需要在最终结果中增加一个g公式
a*n = a+a*(n-1)
a*n = a*(n/2)+a*(n/2)
你应该使用这个公式
a*n = a+a*(n-1)
a*n = a*(n/2)+a*(n/2)
注意
n
为偶数
且n为奇数
时的情况。应用此选项将使您获得O(logn)
复杂性,而不是O(n)
除了答案中指出的问题外,请注意,halve
和double
对于fixnum->fixnum的情况都是单位移位。我希望几乎所有CPU都有这样的操作。(快星(/a2)(*2b))
应该是(*2(快星(/a2)b))
。这就是答案。除以2和除以2.0之间有什么区别?这一区别背后的算法会发生什么情况?@salvarico当我们除以2
时,我们在进行整数除法,Scheme会将值表示为分数,一个精确的值,计算起来会比较慢。当我们除以2.0
时,我们正在进行浮点除法,Scheme将把值表示为十进制数,这是一个不精确的值,计算起来会更快。哦,我理解这一点。但是,由于我们只在b为偶数时除以2,因此它总是得到一个整数。然而,我仍然想知道进行整数除法和do除法会有什么不同使用十进制数进行除法(但由于它是整数,所以也是精确的)。在内存和时间方面有差异吗?是的,会有差异。当你用2.0
除法时,结果不再是整数!即使它是精确运算。例如:(/10 2.0)
将返回5.0
,而不是5
。浮点值是一种不同的数据类型,没有rational的开销,这是进行除法下注时得到的结果