Ackerman 函数如下:

如果用递归算法求 A(m, n) 非常简单:

int Ackerman(m, n)
{if(m==0)return n+1;else if(m>0 && n==0)return Ackerman(m-1, 1);else if(m>0 && n>0)return Ackerman(m-1, Ackerman(m, n-1));
}

那用非递归算法如何求解呢?也有很多方法。我们知道,在一般情况下,将递归算法转化程等价地非递归算法应该设置的数据结构为。所以这次选用栈来非递归地求解 Ackerman 函数。

既然要用到栈,我们必须考虑一个很重要的问题,那就是要把什么入栈。Ackerman 函数中一共涉及三个元素:m 、n 、函数结果值,栈中需要记录其中的一个还是多个元素呢?换句话说,遇到这种函数求值型的题目,我们是将变量(即 m 和 n)入栈,还是将函数结果值(即每层运算的 Ackerman 值)入栈呢?

接下来我们通过一个比 Ackerman(m, n) 简单的 F(n) 来探讨这个问题。

F(n) 函数如下:

F(n) 比 Ackerman(m, n) 简单在于:1)它只有一个变量n;2)它只包含两种情况。

这道题的解法是将 n 入栈,n 的值会一直除以 2 直到 n 的值为 0 为止,而顺序栈将记录这个过程中 n 的所有取值。假设我们要求 F(8) 的值,求解过程可得到下表:

所有 n 值入栈后,对应在栈中的存储形态为:

将所有的 n 值入栈后,再依次出栈,出栈时计算 F(n) 的结果。具体代码如下:

#include "stdio.h"
#include <stdlib.h>#define MAXSIZE 100;
typedef struct
{int data[MAXSIZE];int top;
} Stack;int F(n)
{Stack* S=(Stack*)malloc(sizeof(Stack));S->top=-1;int f=0;//将n值入栈for(int i=n; i>=0; i=i/2){S->top++;S->data[S->top]=i;} //将n值出栈并计算结果while(S->top>0){if(S->data[S->top]==0)f=1;elsef=f*(S->data[S->top];}return f;
}

为什么不将 F(n) 入栈呢,答案很简单,因为一开始我们并不知道 F(n) 的值是多少(如果一开始知道就不用求解了),但我们知道 n 值是多少,所以对函数求值型的题目,我们将其变量入栈。

再回到 Ackerman 函数的问题上来,由于 Ackerman(m, n) 有两个变量,需要将 m 和 n 的值入栈,所以我们不再采用顺序栈而改为采用链栈。与解 F(n) 的方法一样,我们先用一些实际的值代入 m 和 n 求解答案,以找到函数其中的规律。首先先试着人工解出 Ackerman(2, 1) 。下面的解题过程希望大家可以在纸上跟着做。

根据 Ackerman 函数的公式,我们可以列出下面的表格:

将上面表格里的 m 和 n 入栈后,对应在栈中的存储状态为:

上面的表格所列出来的计算过程还不完整,但我们可以看到表格最后一行,已经推算到了 m = 0 的情况,那么对应的这个 A(0, A(1, 0)) 怎么去计算呢,代入 Ackerman 函数,A(0, A(1, 0)) = A(1, 0) + 1,且 A(0, A(1, 0)) 并不是 A(2, 1) 的答案。于是接下去推算:

将上面表格新增的 m 和 n 入栈后,对应在栈中的存储状态为:

现在,我们知道表格倒数第 4 行的 A(0, A(1, 0)) = A(0, 2),那么将多余的数据元素出栈并修改 A(0, A(1, 0)) 对应的数据元素的 n 值,得到下表:

接着又从上面这张表格推算出表格倒数第 5 行的 A(1, A(2,0)) = A(1, 3),重复之前的步骤,将多余的数据元素出栈并修改 A(0, A(1, 0)) 对应的数据元素的 n 值,得到下表:

栈中的存储状态为:

到这里可以总结出一些重复步骤:在推算结果的过程中,一开始,将 m 和 n 的值入栈,然后根据公式不断换算 A(m, n) 结果,并将换算出的 A'(m', n') 中的 m' 和 n' 入栈,直到换算出一个具体数值才停止入栈。

假设 A(m, n) 的 m 和 n 已入栈,那么根据公式换算的 A'(m', n') 的 m' 和 n' 该如何入栈呢。若 m > 0 且 n > 0,则  m' = m - 1 可以正常入栈,而 n' = A''(m, n-1),此时 n' 是一个新的公式,无法入栈,我们可以用一个标志性数值来标识它(比如 -1),就像表格里的橙色星号 一样,表示这个 n' 等待被计算。除 m > 0 且 n > 0 以外的两种情况,m' 和 n' 都可以正常入栈。

这样的重复入栈操作什么时候才停止呢,表格中 A(m, n) 一列的数据推算到具体数值才停止入栈,即 Top 指针指向的数据结构中,m = 0 且 n 已知时停止入栈,开始出栈。对照第二张表格的推算过程,一旦推算出 A(0, 1) = 2 ,这个 2 对应的是表格中最后一个橙色星号 的 n 值,也就是最邻近且等待被计算的 n 值,所以我们得到 A(0, 1) = 2 后,又不断出栈,直到找到那个最邻近且等待被计算的 n,并将 2 重新赋值给这个 n 。

然后重复入栈步骤,重复出栈步骤,直至得到最终结果。

比求解 F(n) 更复杂的是,求解 F(n) 只需要一次性入栈,一次性出栈,而求解 Ackerman 函数的入栈过程中也需要出栈操作。接下来我们接着之前的表格继续推算。

好的,现在已经算出结果了,A(2, 1) = 5 。

既然栈中的每个数据元素需要记录 m 和 n 的值,就不采用顺序栈而采用链栈了。

typedef int DataType;
typedef struct node
{DataType m, n;struct node* next;
} LinkStack;

我们再来用代码捋一遍思路。给定一个 A(m, n),然后入栈 / 出栈进行计算,假设现在计算进行到中途,栈中已经有了一些数据元素,此时 Top 指针指向数据元素 [ m = M, n = N ] ,那么我们接下来怎么继续计算呢,是入栈还是出栈呢?

这取决于 Top 指针指向的数据元素。

如果 M > 0 && N = 0,则将数据元素 [ m = M-1, n = 1 ] 入栈。

//A(m,n)=A(m-1,1)
if(Top->m>0 && Top->n==0)
{LinkStack* p=(LinkStack*)malloc(sizeof(LinkStack));p->m=Top->m-1;p->n=1;p->next=Top;Top=p;
}

如果 M > 0 && N > 0,则将数据元素 [ m = M-1, n = -1 ] 和数据元素 [ m = M, n = N-1 ] 入栈,其中 [ m = M-1, n = -1 ] 是当前 Top 栈顶的 A(M, N) 所换算的 A(M-1, A(M, N-1)),而 A(M, N-1) 有待计算,还不能将该元素的 n 赋值为确切数值,所以先用 -1 做标记;另一个入栈的 [ m = M, n = N-1 ] 就是需要计算的 A(M, N-1),即 [ m = M-1, n = -1 ] 中的 n 值。

//A(m,n)=A(m-1,A(m,n-1))
if(Top->m>0 && Top->n>0)
{LinkStack* p=(LinkStack*)malloc(sizeof(LinkStack));LinkStack* q=(LinkStack*)malloc(sizeof(LinkStack));p->m=Top->m-1;p->n=-1;q->m=Top->m;q->n=Top->n - 1;p->next=Top;Top=p;q->next=Top;Top=q;
}

如果 M = 0 && N != -1 ,就可以计算当前 A(M, N) = N + 1,这个值其实是栈中所有待计算的 n 值中最邻近 Top 栈顶的 n 值然后开始出栈,记录这个值,然后开始不断出栈,直到找到那个 n,并将该值赋给它。

//A(m,n)=n+1
if (Top->m==0 && Top->n>=0)
{DataType x=Top->n+1;while(Top!=NULL && Top->n>=0){//出栈LinkStack* p=Top;Top=Top->next;free(p);}if(Top==NULL){result=x;break;} else if(Top->n<0){Top->n=x;}
} 

完整的代码如下(代码中将入栈出栈的过程模拟地显示出来):

#include "stdio.h"
#include <stdlib.h>typedef int DataType;
typedef struct node
{DataType m, n;struct node* next;
} LinkStack;void Show(LinkStack* p)
{printf("m:%d    n:%d\n", p->m, p->n);
}DataType Akm(DataType m, DataType n)
{printf("以下模拟进出栈过程:")LinkStack* Top = (LinkStack*)malloc(sizeof(LinkStack));Top->m = m; Top->n = n;Top->next = NULL;Show(Top);DataType result = 0;while (Top != NULL){//akm(m,n)=n+1if (Top->m == 0 && Top->n >= 0){DataType x = Top->n + 1;while (Top != NULL && Top->n >= 0){//出栈LinkStack* p = Top;Top = Top->next;free(p);printf("pop\n");}if (Top == NULL){result = x;break;} else if (Top->n < 0){Top->n = x;}} //akm(m,n)=akm(m-1,1)else if (Top->m > 0 && Top->n == 0){LinkStack* p = (LinkStack*)malloc(sizeof(LinkStack));p->m = Top->m - 1;p->n = 1;p->next = Top;Top = p;Show(Top);}//akm(m,n)=akm(m-1,akm(m,n-1))else if (Top->m > 0 && Top->n > 0){LinkStack* p = (LinkStack*)malloc(sizeof(LinkStack));LinkStack* q = (LinkStack*)malloc(sizeof(LinkStack));p->m = Top->m - 1;p->n = -1;q->m = Top->m;q->n = Top->n - 1;p->next = Top;Top = p;Show(Top);q->next = Top;Top = q;Show(Top);}}return result;
}void main()
{DataType r = Akm(2, 1);printf("结果为:%d\n", r);
}

如何使用栈非递归地求解Ackerman函数相关推荐

  1. 【数据结构】栈与递归例题:Ackerman函数

    题目:已知Ackerman函数的定义如下: (1)写出递归算法: (2)写出非递归算法: (3)根据递归算法,画出求akm(2,1)时栈的变化过程. 部分概念: 递归函数:一个直接调用自己或通过一系列 ...

  2. 无栈非递归中序遍历非线索化二叉树

    试设计一个非递归算法,按中根顺序遍历非线索二叉树,但不得用任何辅助. 在执行算法期间,允许改变LLINK和RLINK的值. 如何不用辅助栈非递归遍历二叉树呢? 这里给出了一个比较方便的算法,其基本思路 ...

  3. 编写函数 int fac(int x)计算 x!的值。在主函数中输入 n 和 m 的值,通过调用函数 fac 计算m Cn 的值(要求分别用递归和非递归的方法编写函数 fac)

    编写函数 int fac(int x)计算 x!的值.在主函数中输入 n 和 m 的值,通过调用函数 fac 计算m Cn 的值(要求分别用递归和非递归的方法编写函数 fac) 递归: #includ ...

  4. 递归:我不用栈 非递归:栈使我快乐

    偶尔敲代码,今天看树的遍历方式递归和非递归方式实现,碰到了一个关于栈的问题. 栈 栈的定义:栈是限定仅在表头进行插入和删除操作的线性表.要搞清楚这个概念,首先要明白"栈"原来的意思 ...

  5. 实验六(函数) 2.编写函数 int fac(int x)计算 x!的值。在主函数中输入 n 和 m 的值,通过调用函数 fac 计算Cnm 的值(要求分别用递归和非递归的方法编写函数 fac)。

    (Cnm是m在上n在下) 非递归法: #include<stdio.h>long fac(long);int main(){long m,n,a=1,b;scanf("%ld%l ...

  6. 迷宫的非递归求解 C语言 数据结构课程设计

    非递归求解迷宫问题 问题解决的实现 运行环境说明: 正常数据测试 有疑问看这里 源码及课程设计报告 更新一下以往的课程设计,希望能给相同课程设计的同学提供一个不一样的思路. 问题解决的实现 问题描述: ...

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

    目录 一.Ackermann函数 二.C++实现 1. 递归实现 2. 栈模拟递归 一.Ackermann函数 Ack(m,n)={n+1m=0Ack(m−1,1)m>0,n=0Ack(m−1, ...

  8. 3. ackerman函数的递归实现算法用java语言_ackerman递归

    定义: n+1        n=0 A(m,n)={A(m-1,1) m=0 A(m-1,A(m,n-1)) n>0,m>0 #include #include using namesp ...

  9. 二叉树非递归先序遍历

    二叉树的递归先序遍历很简单,假设二叉树的结点定义如下: 1 struct BinaryTreeNode 2 { 3 int m_nValue; 4 BinaryTreeNode* m_pLeft; 5 ...

最新文章

  1. 已知环境静态障碍物避障_我女儿如何教我无障碍环境
  2. 树上启发式合并问题 ---- D. Tree and Queries[树上启发式合并+树状数组]
  3. r语言remarkdown展示图_R语言—自动报告Markdown笔记
  4. LeetCode MySQL 1495. Friendly Movies Streamed Last Month
  5. Linux系统下通过命令行对mysql数据进行备份和还原
  6. Spring-beans-FactoryBean
  7. BZOJ2425: [HAOI2010]计数
  8. android 设置系统屏幕亮度
  9. Cesium:搭建运行环境
  10. openjdk 配置linux环境变量,linux中使用openjdk配置java环境变量
  11. 19-Spring Security资源服务器配置详解
  12. OSX 黑苹果 ps 2 键盘 驱动
  13. Stata:面板分位数回归
  14. matlab ramp函数,一文教你快速搞懂 FOC ramp function 斜坡函数的作用和实现
  15. 计算机控制台咋用,怎么使用控制台?饥荒海难控制台使用教程
  16. 传世单机版怎么建立服务端?
  17. Rmxprt Maxwell 生成2D和3D全模型方法
  18. java 三个字母组合_每天AC系列(三):电话号码的字母组合
  19. 中国不可能有金融危机而只有经济危机
  20. (解决)虚拟机黑屏,界面显示:/dev/sda1: clean, xxxxx/yyyyyy files, aaaaaa/bbbbbbb blocks....

热门文章

  1. 影视后期制作非编系统(一)
  2. 一次帮用户解决源码被敲诈事件
  3. RGB565调色板Ver1.0.0
  4. ubuntu下ftp的设置及登录
  5. oracle数据备份 full,oracle数据库备份 full
  6. 罗斯蒙特导波雷达液位计设置_Rosemount/罗斯蒙特5300导波雷达液位计的优势!
  7. 分布式系统 (大规模分布式系统原理解析和架构实践)
  8. vs2019 C++自带了内存问题检测工具:AddressSanitizer(ASan)
  9. ffmpeg通过组播udp推ts流
  10. Hyperledger Fabric 1.2.1启用CouchDB作为状态数据库