преобразование рекурсивной функции в итеративную в C - PullRequest
0 голосов
/ 24 мая 2018

Мне нужна итеративная функция, но я могу думать о ней только как о рекурсивной

int f(int m,int n)
{
    if (n == 0)
        return m;
    if (m == 0)
        return n;
    return f(m+1, n-1) + f(m, n-1) + f(m-1, n-1) + f(m-1, n);
}

Проблема в 3-м случае, поскольку я сказал, что могу думать только о рекурсивной.Я использую C.

Ответы [ 3 ]

0 голосов
/ 24 мая 2018

Чтобы получить более подробную информацию об идее Нико Шертлера, вам действительно нужно вычислить значения в диагональном порядке (поскольку в формуле есть m+1).Чтобы сделать это более понятным, давайте введем еще одну функцию g, такую, что

g(k, n) = f(k-n, n)

или, иначе говоря,

f(m, n) = g(m+n, n)

А теперь давайте перепишем рекурсивную формулу для f в терминах g:

g(m+n, n) = g(m+n, n-1) + g(m+n-1, n-1) + g(m+n-2, n-1) + g(m+n-1, n)

или

g(k, n) = g(k, n-1) + g(k-1, n-1) + g(k-2,  n-1) + g(k-1, n)

, и теперь вы можете видеть, что здесь и k, и n никогда не растут, поэтому вы можете вычислить g(k,n) если у вас есть только все значения для двух предыдущих строк, то есть g(k-1, i) и g(k-2, i) + предыдущие значения в текущей строке.

Если вы поместите это в код, вы можете получить что-то вроде этого:

#include <stdio.h>

int f(int m,int n)
{
    if (n == 0)
        return m;
    if (m == 0)
        return n;
    return f(m+1, n-1) + f(m, n-1) + f(m-1, n-1) + f(m-1, n);
}

int f2(int m, int n) {
    // covers bad cases such as m = n = 0
    if (m == 0)
        return n;
    if (n == 0)
        return m;
    int buf1[m + n + 1];
    int buf2[m + n + 1];
    int buf3[m + n + 1];
    int* curLine = buf1;
    int* prevLine1 = buf2;
    int* prevLine2 = buf3;
    // init first two lines with f(0,0), f(1,0) and f(0,1)
    prevLine1[0] = 0; 
    curLine[0] = 1;  
    curLine[1] = 1;
    for (int i = 2; i <= m + n; i++) {
        // cycle buffers to avoid dynamic allocation for each line
        // curLine -> prevLine1 -> prevLine2 -> curLine
        int* tmp = prevLine2;
        prevLine2 = prevLine1;
        prevLine1 = curLine;
        curLine = tmp;
        curLine[0] = curLine[i] = i;  // f(0,i) and f(i,0)
        for (int j = 1; j < i; j++) {
            curLine[j] = curLine[j - 1]  // m+1, n-1
                    + prevLine1[j] //m-1, n
                    + prevLine1[j - 1] //m, n-1
                    + prevLine2[j - 1]; //m-1, n-1
        }
    }
    return curLine[n];
}

int main()
{
    for(int n = 0; n < 6; n++)
       for(int m = 0; m < 6; m++) {
           int fv = f(m,n);
           int f2v = f2(m,n);
           printf("n = %d, m = %d, f = %d f2 = %d\n", n, m, fv, f2v);
       }

    return 0;
}

См. онлайн-демонстрацию , показывающую, что выходные данные f и f2 одинаковы.


Обновление (более простой код)

На самом деле вам не нужно идти по диагонали.Да, есть зависимость от m+1, но для n зависимость только для n и n-1.Так что достаточно сделать внешний цикл на n (вместо m) и просто перейти во внутренний цикл до m+n-i (а не просто до m), и тогда вам понадобится только одинпредыдущая строка значений f.Код выглядит следующим образом:

int f3(int m, int n){
    // covers bad cases such as m = n = 0
    if (m == 0)
        return n;
    if (n == 0)
        return m;
    int buf1[m + n + 1];
    int buf2[m + n + 1];
    int* curLine = buf1;
    int* prevLine = buf2;

    // f(i, 0)
    for (int i = 0; i <= m + n; i++)
        curLine[i] = i;

    for (int i = 1; i <= n; i++) {
        // swap buffers to avoid dynamic allocation for each line
        int* tmp = prevLine;
        prevLine = curLine;
        curLine = tmp;
        curLine[0] = i; // f(0, i)
        for (int j = 1; j <= n + m - i; j++) {
            curLine[j] = curLine[j - 1] //m-1, n
                    + prevLine[j + 1]   //m+1, n-1
                    + prevLine[j]       //m, n-1
                    + prevLine[j - 1];  //m-1, n-1

        }
    }

    return curLine[m];
}

Обновленная демоверсия с f2 и f3 здесь здесь

0 голосов
/ 24 мая 2018

Используйте динамическое программирование, как предлагали другие.

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

int matrix[20][20];

ЗатемПодумайте, какие значения вам понадобятся для вычисления общего решения.Например, если вам запрошено f(4, 2), будут доступны следующие значения:

  0 1 2 3 4 5 6
0 # # # # # # #
1 # # # # # #
2 # # # # #

После этого подумайте, как можно заполнить все эти значения матрицы максимально быстрым способом, заканчиваяв запрашиваемом решении на (4, 2).

Это ожидаемая производительность:

--------------------------------------------------------
Benchmark                  Time           CPU Iterations
--------------------------------------------------------
f_original/1/1             9 ns          9 ns   72315573
f_original/8/1            69 ns         69 ns   10191593
f_original/1/8        337200 ns     337086 ns       1840
f_original/8/8     272417747 ns  272221086 ns          2
f_stack/1/1                9 ns          9 ns   80751042
f_stack/8/1               51 ns         50 ns   12440282
f_stack/1/8           243803 ns     242299 ns       2779
f_stack/8/8        177525517 ns  177187353 ns          5
f_memoization/1/1        229 ns        229 ns    3681680
f_memoization/8/1        255 ns        254 ns    2457598
f_memoization/1/8        517 ns        517 ns    1116021
f_memoization/8/8       1161 ns       1159 ns     554004
f_dynamic/1/1              7 ns          7 ns   90095265
f_dynamic/8/1             16 ns         16 ns   41905352
f_dynamic/1/8             52 ns         52 ns   11358252
f_dynamic/8/8            128 ns        127 ns    5244860

Объяснение:

  • f_original:Функция, которую вы опубликовали.
  • f_stack: подход, предложенный @basak.Это верный ответ на вопрос, так как он не использует рекурсию (т.е. вы предоставляете свой собственный стек вместо того, чтобы использовать тот, который предоставляется вызовами функции).
  • f_memoization: То же, что и оригинал, нос памяткой.Не верный ответ, поскольку он все еще рекурсивный.
  • f_dynamic: Подход динамического программирования.

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

После этого вы можете оптимизировать дальше.Например, если вы не планируете повторно использовать промежуточные значения, вы можете избежать сохранения всей матрицы - вместо этого сохраните последние две строки, как указывает @SergGr:

f_optimized/1/1            8 ns          8 ns   76327765
f_optimized/8/1           16 ns         15 ns   42463467
f_optimized/1/8           44 ns         43 ns   12702290
f_optimized/8/8           90 ns         89 ns    7176011

Еще больше,если ваш int достаточно мал (например, 32-разрядный), то вы можете просто предварительно вычислить большинство значений - для m >= 3 есть только значения 399, которые соответствуют 32-разрядному целому числу со знаком, поэтомуВы можете оставить их всех.Это примерно 1.5 KiB данных, и тогда у вас есть функция O(1), которая возвращает 2 ns.

0 голосов
/ 24 мая 2018

Вы можете использовать стек, в котором хранится пара целых чисел (m, n), где каждый раз, когда вы извлекаете элемент из стека, вы отбрасываете следующее. Если m или n был равен нулю, то вместо того, чтобы выталкивать обратнодалее, вы добавляете m или n (в зависимости от того, что было равно нулю) к вашей текущей сумме.

(m+1, n-1) 
(m, n-1) 
(m-1, n-1) 
(m-1, n)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...