不计算C的Ackermann函数(4,1)-带缓存的Ackermann

时间:2019-12-18 19:38:14

标签: c

使用高级语言一段时间后(MATLAB,R),我对C变得非常感兴趣,并且正在使用它来尝试更好地理解软件和硬件之间的交互。 我写了一个非常简单的递归函数来计算Ackermann:

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

    if (m > 0 && n == 0)
    {
        return ackermann((m - 1), 1);
    }

    if (m > 0 && n > 0)
    {
        return ackermann((m - 1), ackermann(m, (n - 1)));
    }
}

(当然,在m中有一个nmain的循环),它适用于最大(4,1)的值。我知道这是一个很大的值,超出此范围进行计算是不可行的。我在驱动程序循环中添加了一些打印语句,以了解发生了什么:

 for (int m = 0; m < 5; m++)
    {
        for (int n = 0; n < (6 - m); n++)
        {
            printf("\nCalculating Ackermann of (%d, %d)...\n", m, n);
            printf("Ackermann(%d, %d) = %d \n", m, n, ackermann(m, n));
        }
    }

它位于Calculating Ackermann of (4, 1)...处约5秒钟左右,然后程序停止。尽管我不能肯定地说它没有耗尽资源,但它似乎并没有占用内存。

我去寻找解决方案,并在一个名为RosettaCode.com的网站上偶然发现了此代码。作者指出,它会缓存一些已知值以允许计算一些较大的值。

非常大胆,但是有人可以解释一下这是怎么回事吗?我之前看过左移<<,我知道malloc用于动态内存分配(所以我假设这是在处理预缓存的变量数组?),但是{{ 1}}完全让我感到困惑(我对此很感兴趣,但不熟悉这些低级内容)。

storage_bits

如您所见,我在// Ackermann function with caching - Understand how the bit-shift works? #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> int m_storageBits = 0; int n_storageBits = 0; int *valCache; long int ackFunc(int m, int n) { int cacheIndex = 0; int ackResult = 0; if(!m) { return (n + 1); } if (n >= 1 << n_storageBits) { printf("%d, %d\n", m, n); cacheIndex = 0; } else { cacheIndex = (m << n_storageBits) + n; if (valCache[cacheIndex]) { return valCache[cacheIndex]; } } if (!n) { ackResult = ackFunc((m - 1), 1); } else { ackResult = ackFunc((m - 1), ackFunc(m, n - 1)); } if (cacheIndex) { valCache[cacheIndex] = ackResult; return ackResult; } return 0; } int main() { int m = 0; int n = 0; // Specify value size that may be saved m_storageBits = 3; m_storageBits = 20; // 2**20, ~1 megabyte valCache = malloc(sizeof(int) * (1 << (m_storageBits + n_storageBits))); printf("\nValue cache before memset: %i", valCache); memset(valCache, 0, sizeof(int) * (1 << (m_storageBits + n_storageBits))); printf("\nValue cache after memset: %i", valCache); for (m = 0; m <= 4; m++) { for (n = 0; n <= 6 - m; n++) { printf("\nCalculating Ackermann of (%d, %d)...\n", m, n); printf("Ackermann(%d, %d) = %d\n", m, n, ackFunc(m, n)); } } return 0; } 周围添加了一些打印语句,但是值看起来是相同的。我尝试了几种不同的格式,但老实说,我不确定如何甚至可以正确输出此格式。根据我从代码中可以理解的内容,我已经更改了一些语法,并用一些更有用的描述符替换了完全无用的一或三字符变量名。

正如我所说,非常大胆地要求某人对此进行梳理并向我解释,但我很沮丧。如果不是,我会很高兴,只是理解为什么我的简化方法会在ack(4,1)上出现问题(这虽然要求很高,但我拥有相当现代的ThinkPad)。

谢谢!帮助表示赞赏。

1 个答案:

答案 0 :(得分:2)

  

正如我所说,非常大胆地要求某人对此进行梳理并向我解释,但我很沮丧。如果没有,我会很高兴地理解为什么我的简化方法会在ack(4,1)上出现问题(虽然要求很高,但是我拥有相当现代的ThinkPad)。

我将使用Python进行说明,因为它具有内置的dict类型。

import sys
sys.setrecursionlimit(16392)

cache = {}


def ackermann(m, n):
    if m == 0:
        return (n + 1, 1)

    if (m, n) in cache:
        return cache[(m, n)]

    if n == 0:
        r = cache[(m, n)] = ackermann(m - 1, 1)
        return r

    r1, e1 = ackermann(m, n - 1)
    r2, e2 = ackermann(m - 1, r1)
    r = cache[(m, n)] = (r2, e1 + e2)
    return r


print(ackermann(4, 1))

代码有点混乱-使用functools.lru_cache进行简化会在我的平台上运行C堆栈,但重要的是它评估A(4,1)跟踪在没有缓存的情况下对ackermann的调用会产生多少。

  

(65533,1431459240)

1,431,459,240是很多电话,即使对于相当现代的ThinkPad。

对于C代码:它在做同样的事情,保持(m,n)到A(m,n)的映射以避免调用数量激增,但是因为C没有内置的在哈希表中,它通过使用一个大内存块作为二维数组来简化事情,其中​​每个索引的最低有效n_storageBits位表示n,其余表示m。这就是移位的作用。

  • A(4,1)被求值
  • (4,1)转换为索引,这样每个受支持的(M,N)对都会产生不同的结果,在这种情况下,选择2 b M + N这样对于任何受支持的N
  • ,N <2 b
  • 索引用于查找结果并将其存储在大小为2 b M + N + 1的数组中,以获取最大支持的M。