我制作了一个程序,打印出一个数字列表,每个数字都需要比前一个步骤更多的步骤(根据Collatz Conjecture):
limit = 1000000000
maximum = 0
known = {}
for num in xrange(2, limit):
start_num = num
steps = 0
while num != 1:
if num < start_num:
steps += known[num]
break;
if num & 1:
num = (num*3)+1
steps += 1
steps += 1
num //= 2
known[start_num] = steps
if steps > maximum:
print start_num,"\t",steps
maximum = steps
我缓存我已经知道的结果以加速程序。这种方法可以达到10亿的限制,我的计算机内存不足(8GB)。
提前谢谢。
答案 0 :(得分:6)
看起来很难加速Collatz计划;我知道are distributed的最佳程序,使用全球数百(数千......)台PC上的空闲周期。
在纯CPython中,您可以采取一些简单的方法来优化您的程序,尽管速度和空间优化通常不一致:
known
列表而不是字典需要更少的内存。你为每个号码存储一些东西; dicts更适合稀疏映射。array.array
需要更少的空间 - 尽管比使用列表要慢。n
,3*n + 1
必须是偶数,因此您可以直接转到(3*n + 1)//2 == n + (n >> 1) + 1
将2个步骤合并为1。 / LI>
n
花了s
步,然后2*n
将s+1
,4*n
将s+2
,8*n
将s+3
,所以上。这里有一些包含所有这些建议的代码,虽然我使用的是Python 3(在Python 2中,您至少想要将range
更改为xrange
)。请注意,启动时会有很长的延迟 - 这是用大量的array
填充十亿个32位无符号零所花费的时间。
def coll(limit):
from array import array
maximum = 0
known = array("L", (0 for i in range(limit)))
for num in range(2, limit):
steps = known[num]
if steps:
if steps > maximum:
print(num, "\t", steps)
maximum = steps
else:
start_num = num
steps = 0
while num != 1:
if num < start_num:
steps += known[num]
break
while num & 1:
num += (num >> 1) + 1
steps += 2
while num & 1 == 0:
num >>= 1
steps += 1
if steps > maximum:
print(start_num, "\t", steps)
maximum = steps
while start_num < limit:
assert known[start_num] == 0
known[start_num] = steps
start_num <<= 1
steps += 1
coll(1000000000)
1992年撰写的一份技术报告提供了许多加速此类搜索的方法:"3x+1 Search Programs", by Leavens and Vermeulen。例如,@ Jim Mischel&#34;根据以前的峰值切断了#34;这个想法基本上就是论文的引理20。
另一个:对于2的简单因子,请注意,您几乎总是可以&#34;甚至忽略起始号码。为什么:让s(n)
表示达到1所需的步骤数。您正在寻找s()
值的新峰值。假设在n
找到了最近的峰值,并且您正在考虑使用i
的偶数整数n < i < 2*n
。然后特别是i/2 < n
,所以s(i/2) < s(n)
(根据&#34的定义;峰值&#34;以及在n
达到新的峰值)。但s(i) == s(i/2) + 1
,s(i) <= s(n)
:i
不能成为新的高峰。
因此,在n
找到新峰值后,您可以跳过所有偶数整数,但不包括2*n
。
本文还有许多其他有用的想法 - 但它们并非 容易; - )
答案 1 :(得分:4)
你只需要缓存奇数。在你的程序中考虑当你开始编写一个数字时会发生什么。
如果您使用起始编号X,并执行mod 4
,则最终会遇到以下四种情况之一:
这可能会使你的程序稍微减慢一点,因为你有两个除以2的分数,但它会使你的缓存容量增加一倍。
您可以通过仅保存x mod 4 == 3
的数字的序列长度来再次使缓存容量翻倍,但代价是处理时间更长。
那些只能让你在缓存空间中线性增加。你真正需要的是一种修饰缓存的方法,这样你就不必保存那么多结果。以一些处理时间为代价,您只需要缓存产生迄今为止发现的最长序列的数字。
考虑到当您计算出27步有111步时,您已保存:
starting value, steps
1, 0
2, 1
3, 7
6, 8
7, 16
9, 19
18, 20
25, 23
27, 111
所以当你看到28时,你除以2得到14.搜索你的缓存,你会看到14到1的步数不能超过19(因为没有数字少于18需要超过19步)。所以最大可能的序列长度是20.但是你已经有了最多111个。所以你可以停止。
这可能会花费您更多的处理时间,但它会大大扩展您的缓存。您一直只有44个条目,一直到837799.请参阅https://oeis.org/A006877。
值得注意的是,如果您对这些数字进行对数散点图,则会得到非常接近的直线近似值。请参阅https://oeis.org/A006877/graph。
您可以通过保留第二个缓存来组合方法,对于大于具有当前最大值的数字的数字,可以使该数字低于当前最大值所花费的步数。因此,在上述情况下,27具有当前最大值,您将为数字35存储26,因为它需要6次操作(106,53,160,80,40,20)才能获得35到20。表告诉您,它不能超过20个步骤才能达到1,最多可能有26个步骤。因此,如果任何其他值减少到35,则将当前步数添加到26,如果该数字小于111,则表示您可能无法使用此数字获得新的最大值。如果数字大于111,则必须继续计算整个序列。
每当您找到新的最大值时,都会将生成它的数字添加到第一个缓存中,并清除第二个缓存。
这比较慢(我的直觉是在最坏的情况下它可能会使处理时间加倍),而不是为每个值缓存结果,但它会大大扩展你的范围。
这里的关键是扩展你的射程将以某种速度为代价。这是一种常见的权衡。正如我在上面所指出的,你可以做很多事情来保存每个第n项,这将为你提供一个更大的缓存。因此,如果保存每个第4个值,则缓存基本上是保存每个值的4倍。但是你很快就会达到收益递减的程度。也就是说,比原始缓存大10倍的缓存并不比9倍缓存大很多。
我的建议基本上给出了缓存空间的指数增长,代价是一些处理时间。但它不应该是处理时间的巨大增加,因为在最坏的情况下,具有下一个最大值的数字将是先前最大值的两倍。 (想想27,有111步,有54步,有112步。)需要更多代码来维护,但它应该扩展你的范围,目前只有30位,超过40位。