【数据结构】阿克曼函数(ackerman)的递归与非递归实现【C++】

阿克曼函数(ackerman)的递归与非递归实现

1.阿克曼函数的定义

在这里插入图片描述

若m=0,返回n+1
若m≠0且n=0,返回Ackermann(m-1,1)
若m≠0且n≠0,返回Ackermann(m-1,Ackermann(m,n-1))

2.递归实现

对于给出了分段形式的函数,使用递归仅仅只是将公式翻译成代码,不多讲,直接上代码:

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

3.非递归实现

在这里插入图片描述
其实当我们用纸笔简单模拟(2,1)的计算过程,就会知道这是一个不断进栈出栈的过程,只不过是两个数字同时进出,而且其中还会有一层等待计算的小递归藏在里面(如:要解决A(1,3),就得先解决A(0,A(1,2)))。这种后进先出的形式既可以用栈来实现,也可以用循环单链表来模拟。

3.1利用栈

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

先分析不那么特殊的两种情况:
①n = 0
则( m − 1 , 1 )入栈。

②m ≠ 0 , n ≠ 0
由于n 的值也需要通过 Ack 函数求出,先标记为− 1,即待求值,将( m − 1 , − 1 )入栈;
然后将求 n的值的 Ack 函数入栈,即( m , n − 1 ) 入栈。

然后考虑m = 0 的情况:
由于m = 0 时,函数求得了具体值,先更新返回值ansn + 1
通过栈是否为空判断是中间过程还是递归出口,栈空则说明达到了递归出口。

①中间过程
返回值为之前讨论的“用==-1标记的待求的n==”,因此更新栈顶的n
②递归出口
栈空,说明没有待更新、待求值的了,该返回值即最终结果。

代码如下:

//利用栈的非递归算法
#include<stack>

long Ackerman(long m, long n) {
	struct Node {
		int m;
		int n;
		Node(int m, int n): m(m), n(n) {};//构造函数
	}node(m, n);//用m,n的值初始化node
	stack<Node> s;//STL栈
	s.push(node);//m,n压入栈中
	int ans = -1;//返回值
	while (!s.empty()) {
		node = s.top();
		s.pop();
		if (node.m == 0) {
			ans = node.n + 1;
			if (s.empty()) { break; }//如果已经空了,就说明计算结束了
			node = s.top(); //取出来
			s.pop(); //取出来
			node.n = ans; //修改n的值,使其变成-1,即标志为未计算
			s.push(node);//更新栈顶
		}
		else if(node.n == 0) {
			s.push(Node(node.m - 1, 1));
		}
		else {
			s.push(Node(node.m - 1, -1));
			s.push(Node(node.m, node.n - 1));
		}
	}
	return ans;
}

3.2利用循环单链表

与栈思路相同,以链表的头指针作为栈顶, 每次入栈就是向链表头部插入一个元素,出栈就是从链表头部删除一个元素

具体实现如下:

//不利用栈的非递归算法
struct LNode {
    int data;    // 数据域
    LNode* next; // 指针域
};
typedef LNode LNode;     // LNode是一个循环单链表的结点
typedef LNode* LinkList; // LinkList是一个循环单链表
// 定义两个List 分别存储 m 和 n 的值. 
LinkList m_ListHead;
LinkList n_ListHead;
// 初始化循环单链表
void InitList(LinkList& L) {
    L = new LNode;
    L->next = L;
}
// 判断循环单链表是否为空
bool Empty(LinkList& L) {
    if (L->next == L) {
        return true;
    }
    else {
        return false;
    }
}
// 使用头插法插入元素 
void push(LinkList ListHead, long num) {
    LinkList List_tmp;
    List_tmp = new LNode;
    List_tmp->data = num;
    List_tmp->next = ListHead->next;

    ListHead->next = List_tmp;
}

//  从头部删除元素 
void pop(LinkList ListHead) {
    LinkList List_tmp;
    if (!Empty(ListHead)) {
        List_tmp = ListHead->next;
        ListHead->next = List_tmp->next;
        free(List_tmp);		// 释放元素
    }
}


long Ackerman(long m, long n) {
	InitList(m_ListHead);//初始化循环链表
	InitList(n_ListHead);
	push(m_ListHead, m);	// 将 m 入栈
	push(n_ListHead, n);	// 将 n 入栈
	while (!Empty(m_ListHead)) 	// 判断 m 链表是否为空
	{
		while (m != 0) {
			if (n == 0) {
				m = m - 1;
				n = 1;
				push(m_ListHead, m);	
				push(n_ListHead, n);	
			}
			else {
				n = n - 1;
				push(m_ListHead, m - 1);	
				push(n_ListHead, -1);	
			}
		}
		n = n + 1;
		while ((!Empty(m_ListHead)) && (n_ListHead->next->data != -1)) {
			pop(m_ListHead);
			pop(n_ListHead);
		}
		if ((!Empty(m_ListHead))) {
			m = m_ListHead->next->data;
			pop(n_ListHead);
			push(n_ListHead, n);
		}
	}
	return n;
}

4.总结

由于阿克曼函数中,每计算一个数,其都高度依赖前一个数计算的结果,
因此像是此类将递归转化为非递归实现的题目的思路,基本上是利用栈来实现。

参考文档:

Ackermann函数(阿克曼函数)的递归、非递归(手动栈模拟)
关于阿克曼函数(akermann)非递归算法的一点见解 c++
结构体struct node的构造函数
经典算法实现 15_阿克曼函数(非递归实现)

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值