Ackermann函数(阿克曼函数)的递归、非递归(手动栈模拟)

一、Ackermann函数

A c k ( m , n ) = { n + 1 m = 0 A c k ( m − 1 , 1 ) m > 0 , n = 0 A c k ( m − 1 , A c k ( m , n − 1 ) ) m > 0 , n > 0 Ack(m,n)= \begin{cases} n+1& m=0\\ Ack(m-1,1)& m>0,n=0\\ Ack(m-1,Ack(m,n-1))& m>0,n>0 \end{cases} Ack(m,n)=n+1Ack(m1,1)Ack(m1,Ack(m,n1))m=0m>0,n=0m>0,n>0
Ackermannn函数输出值增长速度非常快,仅是对于(4,3)的输出已大得不能准确计算。

A c k ( 0 , n ) = n + 1 A c k ( 1 , n ) = n + 2 A c k ( 2 , n ) = 2 n + 3 A c k ( 3 , n ) = 2 n + 3 − 3 A c k ( 4 , n ) = 2 2 2 ⋅ ⋅ ⋅ 2 − 3 ( n + 3 个 2 ) Ack(0,n)=n+1\\ Ack(1,n)=n+2\\ Ack(2,n)=2n+3\\ Ack(3,n)=2^{n+3}-3\\ Ack(4,n)=2^{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}-3(n+3个2) Ack(0,n)=n+1Ack(1,n)=n+2Ack(2,n)=2n+3Ack(3,n)=2n+33Ack(4,n)=22223n+32

二、C++实现

1. 递归实现

按照表达式直接写,非常简单。

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

2. 栈模拟递归

观察表达式,显然只有 m = 0 m=0 m=0的情况才能直接求得具体值,因此递归的出口一定 m = 0 m=0 m=0

先将问题具体化,例如求 A c k ( 2 , 1 ) Ack(2,1) Ack(2,1)的值,手动演算一下,每一次取栈顶的时候都同时出栈,栈的变化如图:
栈
先分析不那么特殊的两种情况:
n = 0 n=0 n=0,如蓝框过程。
( m − 1 , 1 ) (m-1,1) (m1,1)入栈。

m > 0 , n > 0 m>0,n>0 m>0,n>0,如红框过程。
由于 n n n的值也需要通过 A c k Ack Ack函数求出,先标记为 − 1 -1 1,即待求值,将 ( m − 1 , − 1 ) (m-1,-1) (m1,1)入栈;
然后将求 n n n的值的 A c k Ack Ack函数入栈,即 ( m , n − 1 ) (m,n-1) (m,n1)入栈。

然后考虑 m = 0 m=0 m=0的情况:
如绿框过程。由于 m = 0 m=0 m=0时,函数求得了具体值,先更新返回值retn + 1
通过栈是否为空判断是中间过程还是递归出口,栈空则说明达到了递归出口。
①中间过程,即前面一个绿框过程。
返回值为之前讨论的“用-1标记的待求的n”,因此更新栈顶的n
②递归出口,即后面的绿框过程。栈空,说明没有待更新、待求值的了,该返回值即最终结果。

#include <stack>

int Ack_non_recursion(int m, int n) {
    struct Ack {    // Ackermann结构体
        int m_, n_;
        Ack(int m, int n) : m_(m), n_(n) {} // 构造函数
    } Node(m, n);

    stack<Ack> s;   // STL栈
    s.push(Node);   // 初始值入栈

    int ret = -1;   // Ackermann函数的返回值

    while(!s.empty()) {
        Node = s.top(); // 取栈顶的值
        s.pop();        // 并且出栈
        if(Node.m_ == 0) {  // 第一种情况,m 等于 0
            ret = Node.n_ + 1;  // 返回值为 n + 1
            if(s.empty())   // 如果栈空了,说明不需要继续向上返回,已经得到了最终结果
                break;      // 跳出循环
            Node = s.top(); // 否则,更新栈顶结点(第三种情况中 用 -1 标记的待计算的 n 的值)
            s.pop();        // 先出栈
            Node.n_ = ret;  // n 修改为返回值
            s.push(Node);   // 再重新入栈
        }
        else if(Node.n_ == 0) { // 第二种情况,n 等于 0
            s.push(Ack(Node.m_ - 1, 1));    // Ack(m - 1, 1) 入栈
        }
        else {  // 第三种情况,m, n 都大于 0
            s.push(Ack(Node.m_ - 1, -1));   // 用 -1 标记 n ,表示还没有计算出值,Ack(m - 1, -1) 入栈
            s.push(Ack(Node.m_, Node.n_ - 1));  // 要计算 n 的值,Ack(m, n - 1) 入栈
        }
    }
    return ret;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hibiki.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值