C# 如何防止Ackerman函数溢出堆栈?

C# 如何防止Ackerman函数溢出堆栈?,c#,recursion,stack-overflow,C#,Recursion,Stack Overflow,有没有一种方法可以防止Ackerman函数在相对较小的数字上创建堆栈溢出,即(4,2)。这就是错误所在 {无法计算表达式,因为当前线程位于堆栈中 溢出状态。} 使用备忘录。比如: private static Dictionary<int, int> a = new Dictionary<int, int>(); private static int Pack(int m, int n) { return m * 1000 + n; } private static

有没有一种方法可以防止Ackerman函数在相对较小的数字上创建堆栈溢出,即(4,2)。这就是错误所在

{无法计算表达式,因为当前线程位于堆栈中 溢出状态。}


使用备忘录。比如:

private static Dictionary<int, int> a = new Dictionary<int, int>();

private static int Pack(int m, int n) {
 return m * 1000 + n;
}

private static int Ackermann(int m, int n) {
  int x;
  if (!a.TryGetValue(Pack(m, n), out x)) {
    if (m == 0) {
      x = n + 1;
    } else if (m > 0 && n == 0) {
      x = Ackermann(m - 1, 1);
    } else if (m > 0 && n > 0) {
      x = Ackermann(m - 1, Ackermann(m, n - 1));
    } else {
      x = -1;
    }
    a[Pack(m, n)] = x;
  }
  return x;
}
private static Dictionary a=new Dictionary();
专用静态整数包(整数m,整数n){
返回m*1000+n;
}
私有静态int-Ackermann(int-m,int-n){
int x;
如果(!a.TryGetValue(封装(m,n),输出x)){
如果(m==0){
x=n+1;
}else如果(m>0&&n==0){
x=Ackermann(m-1,1);
}else如果(m>0&&n>0){
x=阿克曼(m-1,阿克曼(m,n-1));
}否则{
x=-1;
}
a[组(m,n)]=x;
}
返回x;
}

然而,这个例子只展示了这个概念,它仍然不会为Ackermann(4,2)给出正确的结果,因为
int
太小,无法容纳结果。您需要一个65536位的整数,而不是32位。

避免堆栈溢出异常的最佳方法是不使用堆栈

让我们去掉否定的情况,因为当我们使用
uint
调用时,它是没有意义的。或者,如果我们在考虑其他可能性之前,将阴性测试作为方法中的第一件事,那么以下内容也将起作用:

首先,我们需要一艘更大的船:

    public static BigInteger Ackermann(BigInteger m, BigInteger n)
    {
        if (m == 0)
            return  n+1;
        if (n == 0)
            return Ackermann(m - 1, 1);
        else
            return Ackermann(m - 1, Ackermann(m, n - 1));
    }
现在,成功至少在数学上是可能的。现在,
n==0
案例是一个非常简单的尾部调用。让我们用手来消除它。我们将使用
goto
,因为它是临时的,所以我们不必担心velociraptors或Dijkstra:

    public static BigInteger Ackermann(BigInteger m, BigInteger n)
    {
    restart:
        if (m == 0)
            return  n+1;
        if (n == 0)
        {
            m--;
            n = 1;
            goto restart;
        }
        else
            return Ackermann(m - 1, Ackermann(m, n - 1));
    }
这将需要更长的时间来吹扫堆栈,但吹扫它,它会。但是看看这个表单,请注意,
m
永远不会通过递归调用的返回来设置,而
n
有时是

扩展这一点,我们可以将其转化为迭代形式,同时只需跟踪
m
的先前值,在递归形式返回的地方,我们在迭代形式中分配给
n
。一旦我们用完了等待处理的
m
s,我们将返回
n
的当前值:

    public static BigInteger Ackermann(BigInteger m, BigInteger n)
    {
        Stack<BigInteger> stack = new Stack<BigInteger>();
        stack.Push(m);
        while(stack.Count != 0)
        {
            m = stack.Pop();
            if(m == 0)
                n = n + 1;
            else if(n == 0)
            {
                stack.Push(m - 1);
                n = 1;
            }
            else
            {
                stack.Push(m - 1);
                stack.Push(m);
                --n;
            }
        }
        return n;
    }
这对堆栈和堆都没有帮助,但考虑到这件事对大值所做的循环数量,我们可以去掉的每一点都是值得的

消除
goto
,同时保持这种优化是留给读者的一个练习:)

顺便说一句,我对测试这个太不耐烦了,所以我做了一个欺骗表单,当m小于3时,它使用Ackerman函数的已知属性:

    public static BigInteger Ackermann(BigInteger m, BigInteger n)
    {
        Stack<BigInteger> stack = new Stack<BigInteger>();
        stack.Push(m);
        while(stack.Count != 0)
        {
            m = stack.Pop();
        skipStack:
            if(m == 0)
                n = n + 1;
            else if(m == 1)
                n = n + 2;
            else if(m == 2)
                n = n * 2 + 3;
            else if(n == 0)
            {
                --m;
                n = 1;
                goto skipStack;
            }
            else
            {
                stack.Push(m - 1);
                --n;
                goto skipStack;
            }
        }
        return n;
    }
再次调用
Ackermann(4,2)
返回:

这是正确的结果。所使用的堆栈结构永远不会抛出,因此剩下的唯一限制是堆(当然还有时间,如果输入足够大,则必须使用“宇宙寿命”作为度量单位…)


由于它的使用方式类似于图灵机的磁带,因此我们想起了这篇论文,即任何可计算的函数都可以在足够大的图灵机上计算。

堆栈溢出发生在哪一行?此外,您是否看到:这似乎是一个预期结果,它的价值增长迅速,即使是小投入。例如,A(4,2)是一个19729位十进制数字的整数。。。您可以跟踪递归深度,并在超过设置的限制时让代码大量道歉。这不是一个错误,这是一个特性,“不是一个真正的问题”是什么意思?他们有一个方法可以为有效的输入抛出异常,他们希望它不会抛出该异常。你能得到多直接?但是你仍然在使用堆栈,它不是内置的堆栈。问题在于避免堆栈溢出,而不是避免
StackOverflowException
。您将其用作堆栈。不管你怎么称呼它或者如何实现它,你仍然在用另一个堆栈替换它,只是为了避免内置的堆栈。你在自相矛盾。您的解决方案不能解决堆栈溢出的问题,它只允许稍高的输入值。正如你在回答中所说的那样,它仍然会溢出,然后在试图使你的解决方案听起来比实际情况更好时会自相矛盾。你没有抓住要点。您只是将一种堆栈替换为另一种堆栈,但它仍然可能溢出。如果目标只是避免
StackOverflowException
,您可以用
throw new ApplicationException()替换代码,问题就解决了。加上1表示“我们不必担心迅猛龙或迪克斯特拉”@Jonhana:那最好改变一下。。。
    public static BigInteger Ackermann(BigInteger m, BigInteger n)
    {
        Stack<BigInteger> stack = new Stack<BigInteger>();
        stack.Push(m);
        while(stack.Count != 0)
        {
            m = stack.Pop();
        skipStack:
            if(m == 0)
                n = n + 1;
            else if(n == 0)
            {
                --m;
                n = 1;
                goto skipStack;
            }
            else
            {
                stack.Push(m - 1);
                --n;
                goto skipStack;
            }
        }
        return n;
    }
    public static BigInteger Ackermann(BigInteger m, BigInteger n)
    {
        Stack<BigInteger> stack = new Stack<BigInteger>();
        stack.Push(m);
        while(stack.Count != 0)
        {
            m = stack.Pop();
        skipStack:
            if(m == 0)
                n = n + 1;
            else if(m == 1)
                n = n + 2;
            else if(m == 2)
                n = n * 2 + 3;
            else if(n == 0)
            {
                --m;
                n = 1;
                goto skipStack;
            }
            else
            {
                stack.Push(m - 1);
                --n;
                goto skipStack;
            }
        }
        return n;
    }
public class OverflowlessStack <T>
{
    internal sealed class SinglyLinkedNode
    {
        //Larger the better, but we want to be low enough
        //to demonstrate the case where we overflow a node
        //and hence create another.
        private const int ArraySize = 2048;
        T [] _array;
        int _size;
        public SinglyLinkedNode Next;
        public SinglyLinkedNode()
        {
            _array = new T[ArraySize];
        }
        public bool IsEmpty{ get{return _size == 0;} }
        public SinglyLinkedNode Push(T item)
        {
            if(_size == ArraySize - 1)
            {
                SinglyLinkedNode n = new SinglyLinkedNode();
                n.Next = this;
                n.Push(item);
                return n;
            }
            _array [_size++] = item;
            return this;
        }
        public T Pop()
        {
            return _array[--_size];
        }
    }
    private SinglyLinkedNode _head = new SinglyLinkedNode();

    public T Pop ()
    {
        T ret = _head.Pop();
        if(_head.IsEmpty && _head.Next != null)
            _head = _head.Next;
        return ret;
    }
    public void Push (T item)
    {
        _head = _head.Push(item);
    }
    public bool IsEmpty
    {
        get { return _head.Next == null && _head.IsEmpty; }
    }
}
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
    var stack = new OverflowlessStack<BigInteger>();
    stack.Push(m);
    while(!stack.IsEmpty)
    {
        m = stack.Pop();
    skipStack:
        if(m == 0)
            n = n + 1;
        else if(m == 1)
            n = n + 2;
        else if(m == 2)
            n = n * 2 + 3;
        else if(n == 0)
        {
            --m;
            n = 1;
            goto skipStack;
        }
        else
        {
            stack.Push(m - 1);
            --n;
            goto skipStack;
        }
    }
    return n;
}