Ackerman 函数的解法
1.定义
ack(m,n) = n+1 m = 0
ack(m,n) = ack(m-1,1) m!=0 n = 0
ack(m,n) = ack(m-1,ack(m,n-1)) m!=0 n!=0
2.示例
ack(3,0) = (2,1)
= (1,(2,0))
= (1,(1,1))
= (1,(0,(1,0)))
= (1,(0,(0,1))
= (1,(0,2))
= (1,3)
= (0,(1,2))
=(0,(0,(1,1))
...
=(0,(0,3))
...
= 5
3.复杂性分析
待解决,m>5后复杂度极高。
4.最简单的递归解法
//按照函数的递归定义即可
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));
}
5.去掉递归,用栈保存信息
/*
* n = 0 的时候往下递归其实只有一个递归分支,无需保留信息,可以用循环取代的
* 而 对于 m!=0 && n!=0 的情况 注意到是一个迭代递归
* 这其中 注意 我们用到 ack(m,n-1)的返回值作为 ack(m-1,x)的值
* 事实上只需要m入栈,使得我们在进入到 ack(m,n-1)后最后能够返回出来计算 ack(m-1,x) x = ack(m,n-1)
*/
下面给出 带 goto 和不带 goto 语句的两种解法
int ack_goto(int m, int n)
{
int result;
stack<int> stk;
start:
if (m == 0)
{
result = n+1;
if (stk.empty())
{
goto end;
}
else
{
goto qiantao;
}
}
else if (n == 0)
{
m = m-1;
n = 1;
goto start;
}
else
{
m = m;
n = n-1;
stk.push(m); //为了保留当期信息,只需保留m等待 右面嵌套返回结果继续
goto start;
qiantao:
m = stk.top();
stk.pop();
m = m-1;
n = result;
goto start;
}
end:
return result;
}
//机械式的对递归程序的用栈标准的非递归翻译
int ack_norec(int m, int n)
{
stack<int> stk;
stk.push(m);
while (!stk.empty())
{
m = stk.top();
stk.pop();
if (m == 0)
{
n = n+1;
}
else if (n == 0)
{
m = m-1;
n = 1;
stk.push(m);
}
else
{
m = m;
n = n-1;
stk.push(m-1);
stk.push(m);
}
}
return n;
}
5.对于以上基于定义用递归或是用栈消除递归的做法,有没有可能优化呢?
对于示例 ack(3,0) 可以注意到 ack(1,1)出现了两次,对于上面的解法,ack(1,1)也就被计算了两次。
事实上我们可以记录已经做好的中间结果,避免重复的递归,也就是所谓的剪枝。
5.1递归加剪枝
const int mMax = 5;
const int nMax = 1000000;
int ackV[mMax][nMax];
bool got[mMax][nMax]; //注意全部初始为false
int ack2(int m, int n)
{
if (m > mMax || n > nMax)
{
cout << "error input too large" << endl;
exit(1);
}
if (got[m][n] == true)
return ackV[m][n];
if (m == 0)
return n+1;
if (n == 0 )
return ack2(m-1,1);
ackV[m][n] = ack2(m-1,ack2(m,n-1));
got[m][n] = true;
return ackV[m][n];
}
5.2非递归算法的优化
int ack_norec2(int m, int n)
{
if (m > mMax || n > nMax)
{
cout << "error input too large" << endl;
exit(1);
}
stack<int> stk;
stack<int> stk2;
int result;
bool flag = 0;
stk.push(m);
while (!stk.empty())
{
if (n > nMax)
{
cout << "error input too large" << endl;
exit(1);
}
m = stk.top();
stk.pop();
if (flag == 1)
{
if (got[m+1][stk2.top()] == false)
{
ackV[m+1][stk2.top()] = n;
got[m+1][stk2.top()] = true;
}
stk2.pop();
flag = 0;
}
if (got[m][n] == true)
{
n = ackV[m][n]; //退出口
flag = 1; //尽管不需要记录这个结果了但因为n已经被Push了要出栈
continue;
}
if (m == 0)
{
n = n+1; //退出口
flag = 1;
}
else if (n == 0)
{
m = m-1;
n = 1;
stk.push(m);
}
else
{
m = m;
n = n-1;
stk.push(m-1);
stk2.push(n);
stk.push(m);
}
}
return n;
}
6.动态规划,记录前面的结果,空间换时间