计算较大的ackermann函数值

时间:2009-06-06 21:59:12

标签: c++ math

我有一些代码:

int CalculateAckermann(int x, int y)
{
    if(!x)
    {
        return y++;
    }
    if(!y)
    {
        return CalculateAckermann(x--,1);
    }
    else
    {
        return CalculateAckermann(x--, CalculateAckermann(x, y--));
    }
}

旨在计算ackermann函数。在相当低的x和y数量之上,应用程序会导致堆栈溢出,因为它会过度递归并导致相当大的数字。我将如何慢慢计算解决方案?

4 个答案:

答案 0 :(得分:21)

如果您希望仅使用封闭形式,那么m< 4的算法很简单。如果你想扩展到tetration,那么我建议你写一个fastpower算法,可能使用二进制方法,然后用这个方法你可以写一个tetration函数。这看起来像是:

int Tetration(int number, int tetrate)
{
    long int product=1;
    if(tetrate==0)
        return product;
    product=number;
    while(tetrate>1)
    {
        product=FastPower(number,product);
        tetrate--;
    }
    return product;
}

然后你可以覆盖最多n == 4的情况,然后使用递归定义和A(5,n)溢出的值以荒谬的速度,所以它真的没关系。虽然你的老师可能不会对这样的算法感到满意,但它的运行速度会快得多。在我的一个离散类中,当我要求编写一个算法来计算斐波纳契数,然后找到它的O(n)时,我写了封闭的形式,然后写了O(1)并得到了充分的信任,一些教授欣赏聪明的答案。

关于Ackerman函数的重要注意事项是它基本上定义了整数上的加法函数的heirachy,A(1,n)是加法,A(2,n)是乘法,A(3,n)是取幂,A(4,n)是tetration,5之后函数增长得太快而不适用。

查看加法,乘法等的另一种方法是:

Φ0 (x, y ) = y + 1
Φ1 (x, y ) = +(x, y )
Φ2 (x, y ) = ×(x, y )
Φ3 (x, y ) = ↑ (x, y )
Φ4 (x, y ) = ↑↑ (x, y )
           = Φ4 (x, 0) = 1 if y = 0
           = Φ4 (x, y + 1) = Φ3 (x, Φ4 (x, y )) for y > 0

(使用前缀表示法,即+(x,y)= x + y,(x,y)= x y。

答案 1 :(得分:6)

IIRC,Ackermann函数的一个有趣特性是评估它所需的最大堆栈深度(在调用级别)与函数的答案相同。这意味着可以通过硬件虚拟内存的限制对实际计算进行严格限制。具有多精度算术包是不够的;你需要更多的比特来存储数字对数的对数,而不是宇宙中的亚原子粒子。

再次,IIRC,你可以得到相对简单的A(1,N),A(2,N)和A(3,N)的闭合公式,沿着以下几行(我似乎记得3在答案中,但细节可能不正确):

  • A(1,N)= 3 + N
  • A(2,N)= 3 * N
  • A(3,N)= 3 ^ N

A(4,N)的公式涉及一些挥动和堆叠指数N-deep。然后,A(5,N)的公式包括堆叠A(4,N)的公式...它变得非常奇怪且非常快速昂贵。

随着公式变得越来越复杂,计算变得完全无法管理。


Ackermann function上的维基百科文章包含“价值表”部分。我的记忆生疏了(但是20年前我上次对此进行了详细研究),它给出了封闭的公式:

  • A(0,N)= N + 1
  • A(1,N)= 2 +(N + 3)-3
  • A(2,N)= 2 *(N + 3)-3
  • A(3,N)= 2 ^(N + 3)-3

A(4,N)= 2 ^ 2 ^ 2 ^ ... - 3(其中2为2的幂,N + 3次)。

答案 2 :(得分:5)

(感觉像是一个家庭作业问题,但无论如何我都会回答......)

详细了解Ackermann功能。例如,WikiPedia article

  

“即使对于小输入,其值也会快速增长。例如,A(4,2)是19,729个十进制数字的整数”。

我怀疑你的32位(或64位,取决于你的架构)整数是溢出的,你因为这些问题而进入无限循环。简单的printf样式调试将显示整数溢出。如果您想实际计算Ackermann函数,则需要使用无限精度bignum库,如GNU MP

另外,请阅读Tail Recursion,以及如何优化它。

答案 3 :(得分:4)

您需要预先递减,而不是递减,或者您只是在每个点将相同的x和y值传递给函数。您可能还想构建一些安全程序以确保没有负参数,因为如果x或y为负数,则未定义Ackerman函数。

int CalculateAckermann(int x, int y)
{
    if (x < 0 || y < 0)
    {
        abort();
    }

    if(x == 0)
    {
        return y+1;
    }
    if(y == 0)
    {
        return CalculateAckermann( x-1,1);
    }
    else
    {
        return CalculateAckermann(x-1, CalculateAckermann(x, y-1));
    }
}