нахождение наибольшего простого множителя с использованием рекурсии в c

написали код того, что я считаю хорошим алгоритмом поиска наибольшего простого множителя для большого числа с использованием рекурсии. Моя программа вылетает из-за любого числа больше 4, присвоенного переменной huge_number. Я плохо разбираюсь в рекурсии, и назначение не допускает никаких циклов.

#include <stdio.h>

long long prime_factor(int n, long long huge_number);

int main (void)
{
    int n = 2;
    long long huge_number =  60085147514;
    long long largest_prime = 0;

    largest_prime = prime_factor(n, huge_number);
    printf("%ld\n", largest_prime);

    return 0;
}

long long prime_factor (int n, long long huge_number)
{
    if (huge_number / n == 1)
        return huge_number;
    else if (huge_number % n == 0)
        return prime_factor (n, huge_number / n);        
    else
        return prime_factor (n++, huge_number);
}

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


person Tristan Pearce    schedule 30.01.2012    source источник


Ответы (4)


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

В то время как ваше разделение huge_number сокращает его довольно быстро, подавляющее большинство рекурсивных вызовов выполняется путем простого увеличения n. Это означает, что вы собираетесь использовать много пространства стека.

Вам тоже будет лучше:

  • используя итеративное решение, при котором вы не взорвете стек (если вы просто хотите решить проблему) (a); или
  • найти более подходящую задачу для рекурсии, если вы просто пытаетесь изучить рекурсию.

(a) Пример такого зверя, смоделированный на основе вашего рекурсивного решения:

#include <stdio.h>

long long prime_factor_i (int n, long long huge_number) {
    while (n < huge_number) {
        if (huge_number % n == 0) {
            huge_number /= n;
            continue;
        }
        n++;
    }
    return huge_number;
}

int main (void) {
    int n = 2;
    long long huge_number =  60085147514LL;
    long long largest_prime = 0;

    largest_prime = prime_factor_i (n, huge_number);
    printf ("%lld\n", largest_prime);

    return 0;
}

Как видно из результатов этого итеративного решения, наибольший коэффициент равен 10976461. Это означает, что последний пакет рекурсий в вашем рекурсивном решении потребует глубины стека в десять миллионов кадров стека, что для большинства сред не так легко.

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

Кроме того, кроме 2, все остальные простые числа нечетные, поэтому вы можете дополнительно сократить пространство поиска вдвое, проверив только два плюс нечетные числа.

Рекурсивное решение, учитывающее эти две вещи, будет:

long long prime_factor_r (int n, long long huge_number) {
    // Debug code for level checking.

    // static int i = 0;
    // printf ("recursion level = %d\n", ++i);

    // Only check up to square root.

    if (n * n >= huge_number)
        return huge_number;

    // If it's a factor, reduce the number and try again.

    if (huge_number % n == 0)
        return prime_factor_r (n, huge_number / n);

    // Select next "candidate" prime to check against, 2 -> 3,
    //   2n+1 -> 2n+3 for all n >= 1.

    if (n == 2)
        return prime_factor_r (3, huge_number);

    return prime_factor_r (n + 2, huge_number);
}

Как видите, я также удалил (на мой взгляд неудобную) конструкцию:

if something then
    return something
else
    return something else

Я предпочитаю код с меньшим отступом, который исходит из:

if something then
    return something
return something else

Но это только личные предпочтения. В любом случае, это снизит ваш уровень рекурсии до 1662 (раскомментируйте отладочный код для проверки), а не до десяти миллионов, довольно значительное сокращение, но все же не идеально. Это нормально в моей среде.

person paxdiablo    schedule 30.01.2012
comment
это назначение, основанное на рекурсии, в котором я не могу использовать какую-либо форму цикла. это то, что по сути меня застряло. - person Tristan Pearce; 30.01.2012
comment
@TristanPearce, вы можете снизить требования к уровню рекурсии с помощью двух простых приемов (1) проверка только 2 и нечетных чисел и (2) проверка только до квадратного корня числа (это те реальный победитель) - смотрите в обновлении. - person paxdiablo; 30.01.2012
comment
Спасибо огромное. Я знал, что есть какое-то отношение к квадратному корню, основываясь на исследованиях в Интернете. В этом гораздо больше смысла, чем проверять все числа. - person Tristan Pearce; 30.01.2012
comment
быстрый вопрос, чтобы избежать предупреждений компилятора. Правильно ли использовать% lld для длинных переменных? - person Tristan Pearce; 30.01.2012
comment
@TristanPearce, да, вы должны использовать lld для длинных типов. - person paxdiablo; 30.01.2012
comment
@TristanPearce Обратите внимание, что достаточно недавний gcc с -O3 преобразовывает (хвостовую) рекурсивную функцию в цикл. И вычисление завершается почти мгновенно, так как ~ 5,5 миллионов итераций - это немного. Конечно, оптимизация извлечения квадратного корня и нечетности по-прежнему хороша, но с приличным компилятором вам не нужно беспокоиться о кадрах стека в таких случаях, как этот. - person Daniel Fischer; 01.02.2012

Вы имели в виду n+1 вместо n++. n++ увеличивает n после его использования, поэтому рекурсивный вызов получает исходное значение n.

person Borealid    schedule 30.01.2012
comment
Я просто попробовал оба способа, но все равно вылетает. используя ++ n, это займет несколько дополнительных секунд. - person Tristan Pearce; 30.01.2012
comment
@TristanPearce Да, ++n будет работать, но имейте в виду, что конкретный экземпляр n, который увеличивается, больше никогда не будет использоваться (поскольку вызов, в котором он является формальным параметром, возвращается сразу после выполнения рекурсивного вызова). - person Borealid; 30.01.2012
comment
@TristanPearce Код действительно правильный с ++n или n+1. Ваш стек просто не может обработать huge_number, который вы ему дали. Попробуйте меньшее число - теперь работает десятки. - person Borealid; 30.01.2012
comment
@TristanPearce Последний совет: попробуйте напечатать n и huge_number в верхней части prime_factor. Таким образом, вы можете наблюдать, как код делает свое дело во время выполнения. - person Borealid; 30.01.2012
comment
Это все равно приведет к вылету из стека просто потому, что огромное количество рекурсий относится к разнообразию n ++, а не к огромному разнообразию / = n. Для последнего факторинга этого числа потребуется около десяти миллионов уровней стека. - person paxdiablo; 30.01.2012
comment
я понятия не имею, как уменьшить количество рекурсивных вызовов на данный момент. Я знаю, что вероятность того, что он будет работать с огромным числом, мала, потому что он вылетает только с 5, чего я не понимаю. - person Tristan Pearce; 30.01.2012
comment
@TristanPearce С предложенным мной изменением он не вылетает с пятью. Но если вы хотите, чтобы он работал с большими числами, используйте алгоритм Евклида вместо того, что у вас есть. - person Borealid; 30.01.2012

Вы переполняете стек, потому что n ++ увеличивает значение пост-инкрементно, выполняя рекурсивный вызов с теми же значениями, что и в текущем вызове.

person Sergey Kalinichenko    schedule 30.01.2012

причина сбоя - переполнение стека. Я добавляю счетчик в вашу программу и выполняю ее (в ubuntu 10.04 gcc 4.4.3), счетчик останавливается на «218287» перед дампом ядра. Лучшее решение - использовать цикл вместо рекурсии.

person Tony Bai    schedule 30.01.2012