당신은 그 c % 2 == 0
입니다 가정에 의한 계산 방법은 c % 2 != 0
보다 거짓
최적화가 참이어야 수 있습니다. c * 3 + 1
은 짝수 여야한다고 가정하여 (c * 3 + 1)/2
을 계산하고 numSteps에 2를 더할 수 있습니다. 자바가 꼬리 - 호출 최적화를 가지지 않기 때문에 재귀 대신 루프를 사용할 수 있습니다.
기억을 사용하면 더 큰 개선 효과를 얻을 수 있습니다. 각 각 숫자에 대해 얻은 결과와 그 값을 반환하기 전에 계산 된 숫자를 암기 할 수 있습니다. 예를 들어 암기에 상한을 두는 것이 좋습니다. 계산하려는 마지막 숫자보다 높지 않아야합니다. 이 값을 사용하지 않으면 가장 큰 값의 몇 배가됩니다. 더 고급 대답이 될 것이다 관심을
public class Collatz {
static final int[] CALC_CACHE = new int[2_000_000_000];
static int calculate(long n) {
int numSteps = 0;
long c = n;
while (c != 1) {
if (c < CALC_CACHE.length) {
int steps = CALC_CACHE[(int) c];
if (steps > 0) {
numSteps += steps;
break;
}
}
if (c % 2 == 0) {
numSteps++;
c /= 2;
} else {
numSteps += 2;
if (c > Long.MAX_VALUE/3)
throw new IllegalStateException("c is too large " + c);
c = (c * 3 + 1)/2;
}
}
if (n < CALC_CACHE.length) {
CALC_CACHE[(int) n] = numSteps;
}
return numSteps;
}
public static void main(String args[]) {
long n = 1, maxN = 0, maxSteps = 0;
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() < startTime + 60000) {
for (int i = 0; i < 10; i++) {
int steps = calculate(n);
if (steps > maxSteps) {
maxSteps = steps;
maxN = n;
}
n++;
}
if (n % 10000000 == 1)
System.out.printf("%,d%n", n);
}
System.out.printf("The highest number was: %,d, maxSteps: %,d for: %,d%n", n, maxSteps, maxN);
}
}
인쇄
The highest number was: 1,672,915,631, maxSteps: 1,000 for: 1,412,987,847
은 다중 스레드를 사용합니다. 이 경우 기억을 가진 재귀 사용은 구현하기가 더 쉬웠습니다.
import java.util.stream.LongStream;
public class Collatz {
static final short[] CALC_CACHE = new short[Integer.MAX_VALUE-8];
public static int calculate(long c) {
if (c == 1) {
return 0;
}
int steps;
if (c < CALC_CACHE.length) {
steps = CALC_CACHE[(int) c];
if (steps > 0)
return steps;
}
if (c % 2 == 0) {
steps = calculate(c/2) + 1;
} else {
steps = calculate((c * 3 + 1)/2) + 2;
}
if (c < CALC_CACHE.length) {
if (steps > Short.MAX_VALUE)
throw new AssertionError();
CALC_CACHE[(int) c] = (short) steps;
}
return steps;
}
static int calculate2(long n) {
int numSteps = 0;
long c = n;
while (c != 1) {
if (c < CALC_CACHE.length) {
int steps = CALC_CACHE[(int) c];
if (steps > 0) {
numSteps += steps;
break;
}
}
if (c % 2 == 0) {
numSteps++;
c /= 2;
} else {
numSteps += 2;
if (c > Long.MAX_VALUE/3)
throw new IllegalStateException("c is too large " + c);
c = (c * 3 + 1)/2;
}
}
if (n < CALC_CACHE.length) {
CALC_CACHE[(int) n] = (short) numSteps;
}
return numSteps;
}
public static void main(String args[]) {
long maxN = 0, maxSteps = 0;
long startTime = System.currentTimeMillis();
long[] res = LongStream.range(1, 6_000_000_000L).parallel().collect(
() -> new long[2],
(long[] arr, long n) -> {
int steps = calculate(n);
if (steps > arr[0]) {
arr[0] = steps;
arr[1] = n;
}
},
(a, b) -> {
if (a[0] < b[0]) {
a[0] = b[0];
a[1] = b[1];
}
});
maxN = res[1];
maxSteps = res[0];
long time = System.currentTimeMillis() - startTime;
System.out.printf("After %.3f seconds, maxSteps: %,d for: %,d%n", time/1e3, maxSteps, maxN);
}
}
인쇄
After 52.461 seconds, maxSteps: 1,131 for: 4,890,328,815
참고 : 나는
steps = calculate((c * 3 + 1)) + 1;
에 두 번째 계산 호출을 변경하는 경우가
After 63.065 seconds, maxSteps: 1,131 for: 4,890,328,815
당신이 루프를 대신 사용하는 방법을 확장 할 수 인쇄 calculate 메서드에 대한 재귀? 또한 암기 구현과 관련하여 가장 좋은 방법은 무엇이라고 생각하십니까? – NotGene
루프를 사용하는 @NotGene은 대개 재귀보다 훨씬 쉽습니다. (Java ta 최소)'while (c! = 1)'과 같은 루프를 가지며'calculate (x)'를'c = x; '로 바꾼다. 기억에 관한 간단한 방법은 큰 고정 크기 'int []'를 호출하고'0'은 설정되지 않았다고 가정합니다. –
@NotGene 여러 스레드에 대한 답변을 추가했습니다. 52 초 만에 60 억을 스캔합니다. –