我正在学习数据结构和算法,这是一个我坚持的问题。
我必须通过将值存储到内存中来提高递归调用的性能。
但问题是非改进版本似乎比这更快。
有人可以帮帮我吗?
Syracuse数字是由以下规则定义的正整数序列:
syra(1)≡1
syra( n )≡ n + syra( n / 2),如果 n mod 2 = = 0
syra( n )≡ n + syra(( n * 3)+1),否则
import java.util.HashMap;
import java.util.Map;
public class SyraLengthsEfficient {
int counter = 0;
public int syraLength(long n) {
if (n < 1) {
throw new IllegalArgumentException();
}
if (n < 500 && map.containsKey(n)) {
counter += map.get(n);
return map.get(n);
} else if (n == 1) {
counter++;
return 1;
} else if (n % 2 == 0) {
counter++;
return syraLength(n / 2);
} else {
counter++;
return syraLength(n * 3 + 1);
}
}
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
public int lengths(int n) {
if (n < 1) {
throw new IllegalArgumentException();
}
for (int i = 1; i <= n; i++) {
syraLength(i);
if (i < 500 && !map.containsKey(i)) {
map.put(i, counter);
}
}
return counter;
}
public static void main(String[] args) {
System.out.println(new SyraLengthsEfficient().lengths(5000000));
}
}
这是我写的正常版本:
public class SyraLengths{
int total=1;
public int syraLength(long n) {
if (n < 1)
throw new IllegalArgumentException();
if (n == 1) {
int temp=total;
total=1;
return temp;
}
else if (n % 2 == 0) {
total++;
return syraLength(n / 2);
}
else {
total++;
return syraLength(n * 3 + 1);
}
}
public int lengths(int n){
if(n<1){
throw new IllegalArgumentException();
}
int total=0;
for(int i=1;i<=n;i++){
total+=syraLength(i);
}
return total;
}
public static void main(String[] args){
System.out.println(new SyraLengths().lengths(5000000));
}
}
修改
它比非增强版本慢。
import java.util.HashMap;
import java.util.Map;
public class SyraLengthsEfficient {
private Map<Long, Long> map = new HashMap<Long, Long>();
public long syraLength(long n, long count) {
if (n < 1)
throw new IllegalArgumentException();
if (!map.containsKey(n)) {
if (n == 1) {
count++;
map.put(n, count);
} else if (n % 2 == 0) {
count++;
map.put(n, count + syraLength(n / 2, 0));
} else {
count++;
map.put(n, count + syraLength(3 * n + 1, 0));
}
}
return map.get(n);
}
public int lengths(int n) {
if (n < 1) {
throw new IllegalArgumentException();
}
int total = 0;
for (int i = 1; i <= n; i++) {
// long temp = syraLength(i, 0);
// System.out.println(i + " : " + temp);
total += syraLength(i, 0);
}
return total;
}
public static void main(String[] args) {
System.out.println(new SyraLengthsEfficient().lengths(50000000));
}
}
最终解决方案(通过学校自动标记系统标记为正确)
public class SyraLengthsEfficient {
private int[] values = new int[10 * 1024 * 1024];
public int syraLength(long n, int count) {
if (n <= values.length && values[(int) (n - 1)] != 0) {
return count + values[(int) (n - 1)];
} else if (n == 1) {
count++;
values[(int) (n - 1)] = 1;
return count;
} else if (n % 2 == 0) {
count++;
if (n <= values.length) {
values[(int) (n - 1)] = count + syraLength(n / 2, 0);
return values[(int) (n - 1)];
} else {
return count + syraLength(n / 2, 0);
}
} else {
count++;
if (n <= values.length) {
values[(int) (n - 1)] = count + syraLength(n * 3 + 1, 0);
return values[(int) (n - 1)];
} else {
return count + syraLength(n * 3 + 1, 0);
}
}
}
public int lengths(int n) {
if (n < 1) {
throw new IllegalArgumentException();
}
int total = 0;
for (int i = 1; i <= n; i++) {
total += syraLength(i, 0);
}
return total;
}
public static void main(String[] args) {
SyraLengthsEfficient s = new SyraLengthsEfficient();
System.out.println(s.lengths(50000000));
}
}
答案 0 :(得分:2)
忘记那些因为使用Map
而导致你的代码效率低下的答案,这不是它变慢的原因 - 事实是你将计算出的数字的缓存限制为{ {1}}。一旦你删除了这个限制,事情开始变得非常快;这里是您填写详细信息的概念证明:
n < 500
如果您想详细了解该计划中发生的事情以及为什么这么快,请查看这篇关于Memoization的维基百科文章。
另外,我认为您误用了private Map<Long, Long> map = new HashMap<Long, Long>();
public long syraLength(long n) {
if (!map.containsKey(n)) {
if (n == 1)
map.put(n, 1L);
else if (n % 2 == 0)
map.put(n, n + syraLength(n/2));
else
map.put(n, n + syraLength(3*n+1));
}
return map.get(n);
}
变量,在第一次计算值时增加它(counter
),但是在它上面累积(++
)在地图中找到值时这对我来说似乎不对,我怀疑它是否给出了预期的结果。
答案 1 :(得分:-1)
不要使用地图。将临时结果存储在一个字段中(称为累加器)并在循环中进行迭代,直到n = 1.在每个循环之后,累加器将增长n。在每个循环中,你的n将增长3倍+ 1或将减少2倍。希望能帮助你解决你的功课
答案 2 :(得分:-1)
当然它没有那么好,你在map.put和map.get调用中添加了很多开销(散列,存储桶创建等等)。另外,你是自动装箱,它增加了大量的对象创建。我的猜测是地图的开销远大于收益。
尝试使用两个数组。一个用来保存值,并保存标志,告诉你是否设置了值。
int [] syr = new int[Integer.MAX_VALUE];
boolean [] syrcomputed = new boolean[Integer.MAX_VALUE];
并使用它们代替地图:
if (syrcomputed[n]) {
return syr[n];
}
else {
syrcomputed[n] = true;
syr[n] = ....;
}
另外,我认为你可能会遇到一些更大数字的溢出(因为syr接近MAX_INT / 3你肯定会看到它,如果它不能被2整除)。
因此,您也应该在所有计算中使用长类型。
PS:如果您的目的是真正了解递归,则不应将值存储为实例变量,而应将其作为累加器传递:
public int syr(int n) {
return syr(n, new int[Integer.MAX_VALUE], new boolean[Integer.MAX_VALUE]);
}
private int syr(int n, int[] syr, boolean[] syrcomputed) {
if (syrcomputed[n]) {
return syr[n];
}
else {
s = [ block for recursive computation ]
syrcomputed[n] = true;
syr = s;
}
}
在一些函数式语言(scheme,erlang等等)中,这实际上是作为尾调用展开的(这避免了堆栈创建)。即使热点jvm不这样做(至少据我所知),它仍然是一个重要的概念。