Ackerman 函数的解法

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.动态规划,记录前面的结果,空间换时间


   

posted @ 2009-04-22 14:09  阁子  阅读(2371)  评论(0编辑  收藏  举报