使用高级语言一段时间后(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
中有一个n
和main
的循环),它适用于最大(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)。
谢谢!帮助表示赞赏。
答案 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。这就是移位的作用。