C语言 如何使递归函数n选择k使用记忆来提高性能?

46qrfjad  于 2022-12-03  发布在  其他
关注(0)|答案(1)|浏览(144)
long choose(int n, int k) {
    if (k == 0 || k == n) {
        return 1L;
    } else {
        long result = (choose(n-1, k) + choose(n-1, k-1));
        return result;
    }
}

这个递归函数在使用大数字时非常慢。我如何使它使用记忆使它更快?

sy5wg1nm

sy5wg1nm1#

你选择一个合适的数据结构来存储你的结果。在这个例子中,我限制了N和K的大小,并使用了一个二维数组(注意:choose()是一个快速增长的函数,因此实际上可能会溢出。)。如果结果被缓存,则返回该结果,否则存储新的计算并返回该结果。

#include <stdio.h>

#define MAX_N 100
#define MAX_K 100

long choose(int n, int k) {
    if(n < 0 || n > MAX_N) {
       printf("n should be between 0 and %d\n", MAX_N);
       return -1;
    }
    if(k < 0 || k > MAX_K) {
       printf("k should be between 0 and %d\n", MAX_K);
       return -1;
    }
    if(n < k) {
        printf("n should be greater or equal to k\n");
        return -1;
    }
    if (k == 0 || k == n) {
        return 1;
    }
    static long memorize[MAX_N+1][MAX_K+1];
    return memorize[n][k] ?
        memorize[n][k] :
        (memorize[n][k] = choose(n-1, k) + choose(n-1, k-1));
}

int main(void) {
    printf("%ld\n", choose(100, 6));
}

和运行示例:

1192052400

real    0m0.002s
user    0m0.002s
sys     0m0.000s

我很好奇,所以这里有一个使用动态分配数组版本。@ auttistic在下面指出mmap()open_memstream()作为替代方案,如果您需要比物理内存更多的空间,mmap()尤其可以使用文件备份存储。

#include <stdio.h>
#include <stdlib.h>

long choose2(int n, int k, size_t k_len, long *memorize) {
    if (k == 0 || k == n) {
        return 1;
    }
    return memorize[n * k_len + k] ?
        memorize[n * k_len + k] :
        (memorize[n * k_len + k] = choose2(n-1, k, k_len, memorize) + choose2(n-1, k-1, k_len, memorize));
}

long choose(int n, int k) {
    if(n < 0) {
        printf("n must be greater than 0\n");
        return -1;
    }
    if(k < 0) {
        printf("k must be great than 0\n");
        return -1;
    }
    if(n < k) {
        printf("n should be greater or equal to k\n");
        return -1;
    }

    long *memorize = calloc((n+1) * (k+1), sizeof(*memorize));
    if(!memorize) {
        printf("calloc failed\n");
        return -1;
    }
    long result = choose2(n, k, k + 1, memorize);
    free(memorize);
    return result;
}

int main(void) {
    printf("%ld\n", choose(100, 6));
}

如果memory是一个vla long (*memorize)[k+1];,它将允许在choose2()中使用更自然的memorize[n][k]语法,这将是一个更漂亮的选择。
gcc支持嵌套函数作为扩展,它在这里很有用,所以k_len和memory可以通过外部函数的闭包而不是参数来访问。

相关问题