背景:数年前我在学校学习了C++和Java,但是过去的9年左右,由于我的先前职业不需要它,我并没有进行太多的编程。
我决定研究一下欧拉计划,以此来提高我的编程能力,并解决问题14,该问题要求找到长度最长的Collatz序列中介于一百万以内的整数。(Collatz序列按照如下方式进行处理:给定一个起始数字,如果该数字是奇数,则将其乘以3再加1;如果该数字是偶数,则将其除以2。这个过程会一直持续到数字达到1。)
我首先使用 brute force 方法解决了这个问题,代码如下所示。
int n;
long temp; // long is necessary since some Collatz sequences go outside scope of int
int[] n_length = new int[1000000];
for(n = 0; n < 1000000; n++){
temp = n + 1;
n_length[n] = 1;
while (temp > 1){
n_length[n]++;
if (temp % 2 == 0) temp = temp/2;
else temp = 3*temp + 1;
}
}
int max = 0;
int max_index = 0;
for (int i = 0; i < 1000000; i++){
if (n_length[i] > max){
max = n_length[i];
max_index = i;
}
}
System.out.println("The number with the longest Collatz sequence is " + (max_index + 1));
我原来认为这种方法效率会很低,因为它比必要的运行算法更多次。任何属于之前数字的Collatz序列的一部分的数字,其序列已被确定,所以每当它出现在Collatz序列中时,你最终需要计算每个数字的序列。
我决定将每个数字存储在一个映射中,一旦它在Collatz序列中出现,就只需计算一次即可。为了实现这一点,我使用了TreeMap,将数字用作键,关联的Collatz序列长度用作值,并使用递归函数在每个数字第一次出现在Collatz序列中时将其插入到映射中。(请参见下面的代码。)
public static TreeMap<Long, Integer> tm = new TreeMap<Long, Integer>();
public static void main(String[] args) {
tm.put((long)1, 1);
int maxVal = 1;
long keyWithMaxVal = 1;
int maybeMax;
for (long i = 2; i <= 1000000; i++){
if(!(tm.containsKey(i))){
maybeMax = addKey(i);
if (maybeMax >= maxVal){
maxVal = maybeMax;
keyWithMaxVal = i;
}
}
}
System.out.println("The number with the longest Collatz sequence is " + keyWithMaxVal + " with length " + maxVal);
}
public static int addKey(long key){
while (!(tm.containsKey(key))){
if (key % 2 == 0){
tm.put(key, 1 +addKey(key/2));
}
else{
tm.put(key, 1 + addKey(3*key + 1));
}
}
return tm.get(key);
}
我使用TreeMap,因为它会自动按键排序,所以当我遍历for循环时,可以快速检查是否已经插入了键,并且只有在必要时才调用addKey方法添加键。我认为这个算法会快得多。
然而,当我实际运行代码时,惊讶地发现暴力算法瞬间就能得出答案,而递归TreeMap算法花费了更长的时间,大约6秒钟。当我将程序修改为达到500万而不是100万时,差异变得更加明显。我向每个程序中添加了一些代码,以确保第二个程序做的工作比第一个少,的确确定了对于每个键,addKey方法仅被调用一次,而while循环需要迭代的次数等于所有数字Collatz序列的长度之和(即比第二种算法中的方法调用次数更频繁)。
那么,为什么第一个算法比第二个算法快得多?是因为第一个算法中的原始数组需要比第二个算法中的Wrapper对象的TreeMap更少的资源吗?搜索是否已经存在的键的映射比我预期的慢(难道不应该是log时间吗?)?需要大量方法调用的递归方法本质上是较慢的吗?还是我忽略了其他东西?