2015-05-03 2 views
0

Задача: Как ускорить метод рекурсии с использованием других методов.Как увеличить скорость для рекурсии Java

Простой метод последовательности Фибоначчи использует рекурсию для вывода N-го числа ввода.

Любые предложения были бы замечательными, спасибо.

ex; сдача 50 займет около минуты, чтобы наконец получить выход.

Редактирование: изменение текста проблемы, поскольку это была не рекурсия, слишком медленная, это был мой код.

public static long cycle(int x) { 
    if(x<=1) { 
    return x; 
    } else { 
    return cycle(x-1)+cycle(x-2); 
    } 
} 
+3

Memoize что функция! –

+0

Конечно, я большой тип теории. Последовательность Фибоначчи была чем-то, что я планировал реализовать в солнечной энергии, когда был моложе. Жаль, что кто-то еще подумал об этом, прежде чем я смогу что-нибудь с этим сделать. Ха! – Jahhein

+0

Вам нужно использовать рекурсию? Было бы гораздо быстрее просто перебирать последовательность. –

ответ

0

Чтобы увеличить скорость, вам необходимо написать код лучше :).

Сперва проверьте, можно ли улучшить ваш код? Если нет, подумайте о том, с чем вы столкнулись. Предположим, что для вычисления значений более 1000 требуется много времени. Тогда в этом случае вы можете думать о сохранении всех промежуточных значений в массиве, и в этом случае для любого значения от 0 до 999 ответ будет доступен в O (1 раз.

Другие области могут быть связаны с настройкой виртуальной машины Java из которых я не очень в курсе :)

2

Проблема является алгоритм, не «рекурсии в Java». Хотя числа Фибоначчи, математически, обычно определяются рекурсивно:

F(0) = 0 
F(1) = 1 
F(n) = F(n - 2) + F(n - 1) if n >= 2 

или чего-то подобного, то есть неправильно способ ее реализации при написании компьютерной программы для вычисления его. Использование цикла позволяет вычислить F (n) в O (n) времени; использование рекурсии сделает вычисление O (F (n)), которое является экспоненциальным, а не линейным. Проблема в том, что вы многократно вычисляете многие из F (n). Если вы вычислите F (8), вычислив F (7) + F (6), вы вычислите F (7), добавив F (6) + F (5) и так далее вниз; то вы вернетесь наверх и вычислите F (6), который вы уже вычислили, но этот алгоритм потребует, чтобы вы снова вычислили его, а также все остальное меньше. Вот почему программа слишком медленная. Рекурсивная программа будет медленнее, независимо от того, на каком языке вы ее записываете, включая сборку.

2

Давайте рассмотрим фундаментальную проблему здесь. Вы хотите, чтобы ваш код работал быстрее на одном и том же оборудовании, важный программный процесс под названием оптимизация.

Во время оптимизации вам нужно будет профилировать ваше приложение, чтобы определить, что является самым медленным компонентом и улучшить его. В вашем случае я могу с достаточной степенью уверенности предположить, что самым медленным компонентом вашей программы является вызов функции. Заговор-й член Фибоначчи от числа вызовов функции мы получаем:

Image: Exponential growth of method calls

, как вы можете видеть, рост экспоненциальный. Примечание. Существуют математические способы понять это, в частности, как описано на this page. Экспоненциальный рост временной сложности всегда является чем-то, что можно избежать, если ваши намерения состоят в том, чтобы сделать быструю исполняемую функцию (предоставлено, есть функции, которые должны быть экспоненциальными, но это не один из них). Глядя на cycle(50), получив 4 * 10^10 вызовов функций, вы сказали, что закончили почти минуту, что соответствует примерно 666,6 миллиона вызовов функций в секунду (или 1 звонок за 1,5 нс), вряд ли кажется справедливым, чтобы вызвать java slow основываясь на этом.Суть в том, что не имеет значения, насколько хороши или быстренькие методы оптимизации компилятора, он не сможет исправить вашу временную сложность, он может только сделать каждую операцию немного быстрее (прочитайте tail-call optimization, если вы хотите узнать больше) ,

Основная проблема, с которой вы сталкиваетесь, заключается в том, что вы много раз повторяете одни и те же числа Фибоначчи, подумайте, сколько раз вы пересчитываете cycle(2). Вот откуда происходит экспоненциальная сложность, много раз повторяя одни и те же числа Фибоначчи, что бесполезно.

Самый простой способ решить проблему - через итерацию, но вы выразили свою незаинтересованность в итераторе.

Следующий простой способ - использовать таблицу поиска (LUT), в которой хранятся предварительно вычисленные числа Фибоначчи в массиве для быстрого доступа. Проблема с этим подходом может быть легко показано ниже:

Image: Reduced exponential graph of method calls

Диаграмма выше показывает эффекты LUT н до 10. в то время как мы снизили мощность экспоненты, мы все же имеем дело с экспоненты, которые не решают проблему полностью (предположим, что вам нужно сделать cycle(100), что тогда?).

Решение должно использовать запоминание как указано в комментарии пользователя 1.618. Это означает, что сохраните результат каждого термина Фибоначчи, когда вы его вычисляете, генерируя LUT по мере того, как вы идете, никогда не переучивая результат дважды.

На следующем графике показано влияние этого:

Image: Linear graph of method calls

на cycle(50), вам нужно 97 вызовов методов, которые со скоростью 1,5 нс/вызов завершится в 145,5 нс, быстрее вы можете моргнуть (это, вероятно, не будет так быстро из-за дополнительного времени, смотрящего на LUT, а также не разогревая).

Реализация этого в Java, мы получаем:

private static long[] fib_numbers; 

public static long cycle(int x){ 
    if(fib_numbers == null || fib_numbers.length <= x){ 
     fib_numbers = new long[x + 1]; 
    } 

    return cycle_rec(x); 
} 

private static long cycle_rec(int x) { 
    if(x <= 1){ 
     return x; 
    }else if(fib_numbers[x] != 0){ 
     // previously computed result exists, return directly 

     return fib_numbers[x]; 
    }else{ 
     // compute and store cycle(x) 
     fib_numbers[x] = cycle_rec(x-1)+cycle_rec(x-2); 

     return fib_numbers[x]; 
    } 
} 

массив fib_numbers держит LUT мы динамически генерировать на вызов метода. Мы публикуем открытый метод cycle(), чтобы настроить массив для использования перед внутренним вызовом cycle_rec() (наша рекурсивная функция).

+0

Спасибо за ваш комментарий. Я еще раз анализирую ответ каждого, чтобы найти свое решение. Прошлой ночью я попытался направить мое заявление. Однако мне нужно оценить до 5000! Так что это намного сложнее, чем я ожидал. Не сдавайся! – Jahhein

+0

@JacobHein Используя описанную технику запоминания, мой компьютер вычисляет цикл (5000) примерно за 26 миллисекунд (хотя вам нужно будет начать использовать BigInteger, поскольку число выходит за пределы длинного). – initramfs

+0

О, ничего себе. Я полностью забыл об этой возможности. Чтобы уточнить это, это проблема UVAonline 495. Итак, чтобы решить эту часть моей проблемы, могу ли я сохранить ее в списке массивов Integer? Я не уверен, что BigInteger, являющийся объектом, а также объект ArrayList, который может быть объектом типа BigInteger, может быть в массиве ArrayList. – Jahhein

0

Проблема заключается в том, что количество вызовов метода равно решению, так как все необходимо разложить на 1 + 1 + 1 + и т. Д. Это O(e^n), что очень плохо для производительности.

Более быстрое решение - использовать итерацию. Это намного быстрее, чем рекурсия, даже с запоминанием в первый раз. Если вы хотите запоминанию, вы можете также генерировать все возможные значения, которые вы можете хранить в long при пуске (занимает около 3 мс) и после этого сделать поиск массива (занимает около 5 нс)

static final long[] fibonacci = new long[92]; 
static { 
    fibonacci[1] = fibonacci[2] = 1; 
    for(int i = 3; i < fibonacci.length; i++) 
     fibonacci[i] = fibonacci[i-1] + fibonacci[i-2]; 
} 

// takes about 5 ns. 
public static long cycle(int x) { return fibonacci[i]; } 

Другое решение является вычисление чисел фибоначчи. Это только оценка с double, но имеет почти O (1) раз.

static final double sqrt5 = Math.sqrt(5); 
static final double c1 = (1 + sqrt5)/2; 
static final double c2 = (1 - sqrt5)/2; 

static double estimateFibonacci(int n) { 
    double v = (Math.pow(c1, n) - Math.pow(c2, n))/sqrt5; 
    return v < Long.MAX_VALUE ? Math.round(v) : v; 
} 

public static void main(String... args) { 
    for (int i = 0; i < 100; i++) 
     System.out.println(i + ": " + estimateFibonacci(i)); 
} 

печатает

0: 0.0 
1: 1.0 
2: 1.0 
3: 2.0 
4: 3.0 
5: 5.0 
6: 8.0 
7: 13.0 
8: 21.0 
9: 34.0 
10: 55.0 
11: 89.0 
12: 144.0 
13: 233.0 
14: 377.0 
15: 610.0 
16: 987.0 
17: 1597.0 
18: 2584.0 
19: 4181.0 
20: 6765.0 
21: 10946.0 
22: 17711.0 
23: 28657.0 
24: 46368.0 
25: 75025.0 
26: 121393.0 
27: 196418.0 
28: 317811.0 
29: 514229.0 
30: 832040.0 
31: 1346269.0 
32: 2178309.0 
33: 3524578.0 
34: 5702887.0 
35: 9227465.0 
36: 1.4930352E7 
37: 2.4157817E7 
38: 3.9088169E7 
39: 6.3245986E7 
40: 1.02334155E8 
41: 1.65580141E8 
42: 2.67914296E8 
43: 4.33494437E8 
44: 7.01408733E8 
45: 1.13490317E9 
46: 1.836311903E9 
47: 2.971215073E9 
48: 4.807526976E9 
49: 7.778742049E9 
50: 1.2586269025E10 
51: 2.0365011074E10 
52: 3.2951280099E10 
53: 5.3316291173E10 
54: 8.6267571272E10 
55: 1.39583862445E11 
56: 2.25851433717E11 
57: 3.65435296162E11 
58: 5.91286729879E11 
59: 9.56722026041E11 
60: 1.54800875592E12 
61: 2.504730781961E12 
62: 4.052739537881E12 
63: 6.557470319842E12 
64: 1.0610209857723E13 
65: 1.7167680177565E13 
66: 2.7777890035288E13 
67: 4.4945570212853E13 
68: 7.2723460248141E13 
69: 1.17669030460994E14 
70: 1.90392490709135E14 
71: 3.0806152117013E14 
72: 4.98454011879265E14 
73: 8.06515533049395E14 
74: 1.30496954492866E15 
75: 2.111485077978055E15 
76: 3.416454622906716E15 
77: 5.527939700884771E15 
78: 8.944394323791488E15 
79: 1.447233402467626E16 
80: 2.3416728348467744E16 
81: 3.7889062373144008E16 
82: 6.1305790721611752E16 
83: 9.9194853094755776E16 
84: 1.60500643816367552E17 
85: 2.59695496911123328E17 
86: 4.2019614072749088E17 
87: 6.7989163763861427E17 
88: 1.10008777836610509E18 
89: 1.77997941600471936E18 
90: 2.8800671943708247E18 
91: 4.6600466103755448E18 
92: 7.540113804746369E18 
93: 1.2200160415121914E19 
94: 1.9740274219868283E19 
95: 3.19404346349902E19 
96: 5.168070885485849E19 
97: 8.362114348984869E19 
98: 1.3530185234470719E20 
99: 2.189229958345559E20 
Смежные вопросы