+1 для ответа Джейсона, который четко объясняет проблему.
Теперь для некоторого решения! Я знаю, по крайней мере, три способа удалить рекурсию из алгоритма:
- Вместо этого вы можете найти чисто итеративный алгоритм (что может быть трудно для некоторых проблем);
- Преобразование рекурсивного алгоритма в аналогичный с циклом и использование Stack < T> (или какой-то список) для хранения эквивалента стека вызовов. Это имеет аналогичное пространство, как и оригинальное, но куча может расти намного больше, чем стек!
- Специальное семейство рекурсивных алгоритмов: tail-recursive. Их можно легко механически изменить, чтобы никогда не переполнять стек. Тебе повезло, это твой случай!
Алгоритм является хвостовой рекурсией, если все его рекурсивные вызовы хвостовых вызовов, что означает, что они являются последней вещью, которую сделали, прежде чем вернуться. Если вам непонятно, посмотрите на Google лучше.
Такие алгоритмы могут быть легко преобразованы путем настройки параметров и использования goto, а не реального вызова. Посмотрите на свой пример снова:
private int Euler5(int dividend, int divisor)
{
tail_call:
if (divisor < 21)
{
// if it equals zero, move to the next divisor
if (dividend % divisor == 0)
{
divisor++;
goto tail_call; // return Eular5(dividend, divisor);
}
else
{
dividend++;
// return Eular5(dividend, 1); // move to the dividend
divisor = 1;
goto tail_call;
}
}
// oh hey, the divisor is above 20, so what's the dividend
return dividend;
}
О, эй! Это точно такая же функция, но с фиксированным размером стека (нет вызова, только переходы). Теперь некоторые скажут: «Ух ... хэто! Они злые! Умри, умри!». Я бы сказал, что это одно из немногих законных целей. В конце концов, если ваш компилятор достаточно умный, он сам выполнит оптимизацию хвостового вызова (F # на самом деле, C# не делает, JIT может сделать это на x64, а не на x86).
Но для тех людей, которых я сказал бы: выглядите немного лучше. Поскольку в конце каждой ветви if/else есть goto, я могу полностью переместить его за пределы «if». Теперь у меня есть что-то вроде «start: if (X) {Y(); goto start;}« Подумайте об этом, это всего лишь цикл while (X) Y() ». Таким образом, вы только что нашли итеративную версию своей функции:
private int Euler5(int dividend, int divisor)
{
while (divisor < 21)
{
// if it equals zero, move to the next divisor
if (dividend % divisor == 0)
{
divisor++;
}
else
{
dividend++;
divisor = 1;
}
}
// oh hey, the divisor is above 20, so what's the dividend
return dividend;
}
Nice!
Возможно, вы просто дуете в стек даже без бесконечной рекурсии. Многие из этих проблем специально разработаны, чтобы быть достаточно большими, чтобы ваш код должен был масштабироваться достаточно хорошо. Вы, вероятно, просто хотите использовать нерекурсивный подход здесь и реорганизовать его в цикл. – Servy
@Servy: Кажется, у меня есть немного больше, чтобы узнать о стеке и как он работает. Благодарю. – MyCodeSucks
Это все равно вызовет переполнение стека, но при перезагрузке вам не нужно проверять делитель на 1, все целые числа делятся на 1 уже. Вы также можете увеличить свой дивиденд на 2 (или даже 10). Только пару советов, когда вы переходите на петлю – Cemafor