简介

本文参考:https://blog.csdn.net/sanqima/article/details/48831679
我结合我自己的一些思考写一下我的思路。


从上到下分别标记为1式、2式、3式,这个在代码注解里会用到
下面的函数名为《数据结构教程》(北航的)的页数和题号

递归算法

这个很简单没啥好讲的

//Ackerman递归算法
int P121_3_1(int m, int n)
{if (m == 0) {return n + 1;}if (m != 0 && n == 0) {return P121_3_1(m - 1, 1);}return P121_3_1(m - 1, P121_3_1(m, n - 1));
}

非递归算法

误区

我先讲一下误区:
递归转堆栈的时候,我最开始认为一定要把每一层的操作信息都给记录下来,然后再操作堆栈。例如下面这道题:

// n=0,f(n)=n+1
// n!=0,f(n)=nf(向下取整(n/2))
int P121_2(int n)
{int stack[M] = { 0 }, top = -1, ret = 0;stack[++top] = n;// 现将所有操作放入堆栈中while (n != 0) {n = n / 2;stack[++top] = n;}ret = 1;// 最后再统一操作堆栈while (top != 0) {ret = stack[--top] * ret;}return ret;
}

这个思路在面对上面这种简单题的时候,是没问题的。但是面对Ackerman函数不行。
原因:
放入堆栈的时候,如果是式1和式2,都可以直接得到下一个子函数的自变量,但是碰见式3就麻烦了。
例如
ACK(0,3),可用式1,直接得结果。
ACK(3,0),可用式2,ACK(3,0) = ACK(2,1) , 这样我们可以把 (3,0) 和 (2,1) 两组数分别入栈。
但是ACK(2,2) ,可用式3,ACK(2,2) = ACK(1,ACK(2,1)) 就只能先入栈 (2,1) 而无法直接入栈等式右侧的(1, ACK(2,1))

示例

策略:我们只管眼前的,把眼前的先处理好,如果碰见式3 ,先入栈子函数,不管式3的整体式子。为了编程上的方便,每层需要有4个变量

//[0]: m
//[1]: n
//[2]: 如果完成了计算,则是本层的f(m,n);
//  如果没完成计算,则代表本层是怎么得到.2: 代表2式得到,  3:代表3式得到
//[3]: 是否完成了本层计算,1为完成,0为没有完成
int st[M][4]

所以,这意味着,不能把每一层的操作信息都给记录下来,然后再操作堆栈。而是要一边入栈,一边又要操作堆栈。当遇到 式3 的时候,例如 ACK(1, ACK(2,1)) ,先别管外层,先入栈里面的 (2,1)
以ACK(2,2) 为例,PUSH代表入栈,POP代表出栈,PEEK代表查看而不修改栈顶指针
PUSH([2,2,3,0]) , ACK(2,2) = ACK(1,ACK(2,1)) , 3式
PUSH([2,1,3,0]) , ACK(2,1) = ACK(1 , ACK(2,0)), 3式
PUSH([2,0,2,0]) , ACK(2,0) = ACK(1,1) , 2式
PUSH([1,1,3,0]) , ACK(1,1) = ACK(0,ACK(1,0)) , 3式
PUSH([1,0,2,0]) , ACK(1,0) = ACK(0,1) ,2式
PUSH([0,1]) , ACK(0,1) = 2
至此,碰到了第一个 1 式的结果,开始进行 POP出栈操作


POP([0,1]) , 当前结果 = 2
POP([1,0,2,0]) , 式2, 当前结果= ACK(0,1) = 2
PEEK([1,1,3,0]) , 式3, 当前结果 = ACK(0 , ACK(1,0)) = ACK(0, 2)
遇见 式3 再次进行 PUSH 操作,并且要把 [1,1,3,0] 改成 [1,1,4,0],这代表 式3 已经完成过子函数的计算了。
PUSH([0,2]) , ACK(0,2) = 3, 1式
至此,又碰到 1 式了,开始进行pop操作


POP([0,2]) , 当前结果为 3
POP([1,1,4,0]) ,当前结果为 ACK(1,1) = ACK(0,2) = 3
POP([2,0,2,0]) , 式2, 当前结果为 ACK(2,0) = ACK(1,1) = 3

按照这种操作方式,遇到式3,把子函数推入堆栈中先处理,然后处理完后继续处理式3的整体。

代码示例

#define M 5000
// 这里堆栈大小要大一下,ACK(m,n)的 m 和 n 要在4以内,否则堆栈可能会溢出。
// 利用堆栈计算 Ackerman函数
int P121_3_2(int m, int n)
{//[0]: m,[1]: n//[2]: 如果完成了计算,则是本层的f(m,n);//  如果没完成计算,则代表本层是怎么得到.2: 代表2式得到,  3:代表3式得到//[3]: 是否完成了计算int st[M][4], top = 0;st[0][0] = m;st[0][1] = n;st[0][3] = 0;while (top >= 0){if (st[top][3] == 0) {// a:本层没有完成计算  if (st[top][0] == 0) {// (1)式,完成本层的计算,没必要再存m,n了// 此处不移动指针,指针移动在b处进行st[top][2] = st[top][1] + 1;st[top][3] = 1;}else if (st[top][1] == 0) {// (2)式// 此处需要进栈++top;st[top][0] = st[top - 1][0] - 1;st[top][1] = 1;st[top - 1][2] = 2;st[top][3] = 0;}else {// (3)式// 入栈子函数++top;st[top][0] = st[top - 1][0];st[top][1] = st[top - 1][1] - 1;st[top - 1][2] = 3;st[top][3] = 0;}}else if (top > 0 && st[top][3] == 1) {// b:本层已有结果// st[top][2]不可能==1,只可能是{2,3}if (st[top-1][2] == 2 || st[top - 1][2] == 4) {// (2)式得到,直接将当前结果赋值给上一个结果--top;st[top][2] = st[top+1][2];st[top][3] = 1;}else if (st[top - 1][2] == 3) {// (3)式// 3式得到的话需要再次入栈st[top][0] = st[top - 1][0] - 1;st[top][1] = st[top][2];st[top][3] = 0;// 这里需要把上一个元素的计算方式改成4// 用于在子函数计算完毕后,直接给上一个元素赋值,而不是再走一遍这里st[top - 1][2] = 4;}}if (top == 0 && st[top][3] == 1) {break;}}return st[top][2];
}

Ackerman的非递归算法思路讲解相关推荐

  1. Ackerman函数 非递归 java_Ackerman(nm)函数的非递归算法.doc

    Ackerman(nm)函数的非递归算法 题目:已知Ackerman函数的定义如下: A(1,0)=2n=1,m=0 Ackerman= A(0,m)=1 n=0,m>=0 A(n,0)=n+2 ...

  2. Ackerman函数(递归与非递归算法实现)

    Ackerman函数 阿克曼函数是非原始递归函数的例子:它需要两个自然数作为输入值,输出一个自然数.它的输出值增长速度非常高. 计算Ackerman(m,n)函数递归算法与非递归算法(利用顺序栈) i ...

  3. 数据结构——二叉树的非递归算法

    二叉树的非递归算法 先序遍历非递归算法1 先序遍历非递归算法2 非递归交换左右孩子算法 使用栈来实现二叉树的非递归算法 栈的基本算法 #include<stdio.h> #include& ...

  4. 【数据结构】快速排序非递归算法及其改进

    在学数据结构中排序这一章节的时候,有一道有关快速排序的作业题描述如下: 按下述要求编写快速排序的非递归算法: 定义一个栈(或队列),把整个序列的上.下界入栈(或队列).当栈(或队列)非空时进行如下操作 ...

  5. 遍历二叉树的非递归算法

    编写的方法:根据树中结点的遍历规律及顺序直接写出其非递归算法. 先序非递归算法 [思路] 假设:T是要遍历树的根指针,若T != NULL 对于非递归算法,引入栈模拟递归工作栈,初始时栈为空. 问题: ...

  6. 基于非递归算法的汉诺塔游戏之Python实现

    本文代码涉及到汉诺塔问题的非递归算法,可能不是很好理解,我在代码中加了大量注释,希望能够有所帮助,如果实在难以理解的话,请搜索这个算法并结合下面的代码进行阅读和理解.感谢国防科技大学刘万伟老师提供算法 ...

  7. 回溯法解决01背包-非递归算法-效率低

    http://acm.zua.edu.cn/problem.php?cid=1025&pid=24 解题思路: 物体的个数为Num,背包的体积限制为Volum 物品的体积是v[1].v[2]. ...

  8. 二叉树中序遍历的非递归算法

    根据二叉树的先序遍历结果创建一棵二叉树,即先创建根结点,然后再创建左子树,最后创建右子树,对于左右子树的创建也遵循根左右的原则,所以对于左右子树的创建可以递归调用本函数,此问题是典型的需要用递归算法求 ...

  9. 递归算法转换成非递归算法

    这周,一个同事在开发这样一个功能:把java对象转换成JSON格式的字符串,我知道有开源的jar包,但是他说那个不能处理他目前的需要,所以需要开发一套转换方法.看了他画的流程图,实在看不懂,就给他提了 ...

最新文章

  1. python趣味编程100_《Python游戏趣味编程》 第8章 勇闯地下一百层
  2. 深入了解MyBatis二级缓存
  3. 傅里叶变换音频可视化_快速上手网易云音乐可视化
  4. 太多产品人死于汇报!
  5. 居中的最佳方法 div 在垂直和水平页面上? [重复]
  6. Python如何提取docx中的超链接
  7. sqlserver 登录失败——孤立用户
  8. java基础 (六)面向对象(一)
  9. pandas训练集测试集划分_用pandas划分数据集实现训练集和测试集
  10. linux常用命令,亲测可用
  11. 关于target is null for setProperty的问题总结
  12. 删除Word模板文件
  13. 高级编程语言分类_高级编程语言的分类
  14. 服务器如何防止DDoS攻击?
  15. SpringCloud Gateway堆外内存溢出排查
  16. omv安装mysql插件_Openmediavault第三方插件安装教程
  17. 超细节的对比学习和SimCSE知识点
  18. 怎么用计算机算出出生日期,算农历出生日期计算器,根据出生日期怎么算年龄?...
  19. 优达(Udacity)-机器学习基础-数据集与问题(安然数据集)
  20. 非参数统计:两样本和多样本的Brown-Mood中位数检验;Wilcoxon(Mann-Whitney)秩和检验及有关置信区间;Kruskal-Wallis秩和检验

热门文章

  1. LCPCI 系列通用型 PCI 接口 CAN 卡、PCICAN、PCI CAN
  2. IT专业人士如何高效的学习专业知识
  3. 基于web的照片数码冲印网站
  4. nhibernate 配置mysql_利用NHibernate与MySQL数据库交互
  5. 计算机维修种子,【数据恢复,维护计算机,硬盘和分区,备份和还原
  6. 专业实践记录IIII: 端到端跨语言音色迁移语音合成论文 - 三步走
  7. keras 多层lstm_keras搭建多层LSTM时间序列预测模型
  8. 联想服务器武汉销售,企业级服务器 武汉联想RD630售价12800
  9. 【CS-Notes】工欲善其事必先利其器(Code、Git、Docker、Linux)
  10. (附源码)基于Spring Boot的宠物猫店管理系统的设计与实现 毕业设计140909