斯特恩的双原子序列可以更详细地阅读over here;但是,为了我的目的,我现在就定义它。
让n
成为一个数字来生成fusc
函数。表示fusc(n)
。
如果n
为0,则返回值为0
如果n
为1,则返回值为1.
如果n
为偶数,则返回值为fusc(n / 2)
如果n
为奇数,则返回值为fusc((n - 1) / 2) + fusc((n + 1) / 2)
。
目前,我的Python代码暴力强制通过大部分代,除了除以两部分,因为它总是不会产生任何变化。
def fusc (n):
if n <= 1:
return n
while n > 2 and n % 2 == 0:
n /= 2
return fusc((n - 1) / 2) + fusc((n + 1) / 2)
但是,我的代码必须能够处理大小为 1000s 数百万位的数字,并且递归运行函数千元数百万次似乎不太高效或实用。
有没有什么方法可以通过算法改进我的代码,这样就可以传递大量的数字,而不必递归调用函数这么多次?
答案 0 :(得分:7)
对于一百万位的记忆,递归堆栈将非常大。我们可以先尝试查看一个足够大的数字,我们可以手工处理,fusc(71)
在这种情况下:
fusc(71)= fusc(35)+ fusc(36)
fusc(35)= fusc(17)+ fusc(18)
fusc(36)= fusc(18)fusc(71)= 1 * fusc(17)+ 2 * fusc(18)
fusc(17)= fusc(8)+ fusc(9)
fusc(18)= fusc(9)fusc(71)= 1 * fusc(8)+ 3 * fusc(9)
fusc(8)= fusc(4)
fusc(9)= fusc(4)+ fusc(5)fusc(71)= 4 * fusc(4)+ 3 * fusc(5)
fusc(4)= fusc(2)
fusc(3)= fusc(1)+ fusc(2)fusc(71)= 7 * fusc(2)+ 3 * fusc(3)
fusc(2)= fusc(1)
fusc(3)= fusc(1)+ fusc(2)fusc(71)= 11 * fusc(1)+ 3 * fusc(2)
fusc(2)= fusc(1)
fusc(71)= 14 * fusc(1)= 14
我们意识到在这种情况下我们可以完全避免递归,因为我们总是可以在fusc(n)
形式中表达a * fusc(m) + b * fusc(m+1)
,同时将m的值减小为0.从上面的示例中,您可以找到以下模式:
如果m是奇数:
a * fusc(m) + b * fusc(m+1)
=a * fusc((m-1)/2) + (b+a) * fusc((m+1)/2)
如果m是偶数:
a * fusc(m) + b * fusc(m+1)
=(a+b) * fusc(m/2) + b * fusc((m/2)+1)
因此,您可以使用简单的循环函数来解决O(lg(n))时间内的问题
def fusc(n):
if n == 0: return 0
a = 1
b = 0
while n > 0:
if n%2:
b = b + a
n = (n-1)/2
else:
a = a + b
n = n/2
return b
答案 1 :(得分:3)
lru_cache可以创造奇迹。确保maxsize
是2的幂。可能需要为您的应用程序调整一下这个大小。 cache_info()
会对此有所帮助。
也可以使用//
代替/
进行整数除法。
from functools import lru_cache
@lru_cache(maxsize=512, typed=False)
def fusc(n):
if n <= 1:
return n
while n > 2 and n % 2 == 0:
n //= 2
return fusc((n - 1) // 2) + fusc((n + 1) // 2)
print(fusc(1000000000078093254329870980000043298))
print(fusc.cache_info())
是的,这只是Filip Malczak提出的meomization。
你可以在while循环中使用位操作获得额外的 tiny 加速:
while not n & 1: # as long as the lowest bit is not 1
n >>= 1 # shift n right by one
<强>更新强>:
这是一种“手工”进行meomzation的简单方法:
def fusc(n, _mem={}): # _mem will be the cache of the values
# that have been calculated before
if n in _mem: # if we know that one: just return the value
return _mem[n]
if n <= 1:
return n
while not n & 1:
n >>= 1
if n == 1:
return 1
ret = fusc((n - 1) // 2) + fusc((n + 1) // 2)
_mem[n] = ret # store the value for next time
return ret
<强>更新强>
阅读short article by dijkstra himself次要更新后。
文章指出,如果f(n) = f(m)
的第一位和最后一位与m
的第一位和第一位相同,并且其间的位被反转,则为n
。我的想法是让n
尽可能小。
这是位掩码(1<<n.bit_length()-1)-2
的用途(第一位和最后一位是0
;中间位于1
; xor
n
的位是m
如上所述给出def fusc_ed(n, _mem={}):
if n <= 1:
return n
while not n & 1:
n >>= 1
if n == 1:
return 1
# https://www.cs.utexas.edu/users/EWD/transcriptions/EWD05xx/EWD578.html
# bit invert the middle bits and check if this is smaller than n
m = n ^ (1<<n.bit_length()-1)-2
n = m if m < n else n
if n in _mem:
return _mem[n]
ret = fusc(n >> 1) + fusc((n >> 1) + 1)
_mem[n] = ret
return ret
。
我只能做小基准测试;我很感兴趣,如果这对你输入的大小有任何帮助......这将减少缓存的内存,并希望带来一些加速。
import sys
sys.setrecursionlimit(10000) # default limit was 1000
我不得不增加递归限制:
_mem
基准测试给出了奇怪的结果;使用下面的代码,并确保我总是启动一个新的interperter(有一个空print(n.bit_length())
ti = timeit('fusc(n)', setup='from __main__ import fusc, n', number=1)
print(ti)
ti = timeit('fusc_ed(n)', setup='from __main__ import fusc_ed, n', number=1)
print(ti)
)我有时会得到明显更好的运行时间;在其他情况下,新代码更慢......
基准代码:
6959
24.117448464001427
0.013900151001507766
6989
23.92404893300045
0.013844672999766772
7038
24.33894686200074
24.685758719999285
这些是我得到的三个随机结果:
data= ['Mom','Dad',1,'Dog','World']
for d in data:
print("You are my {}".format("'{}'".format(d) if isinstance(d, str) else d))
这就是我停止的地方......