避免StackOverflowException
的最佳方法是不使用堆棧。
讓我們擺脫否定的情況,因爲當我們撥打電話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
因爲它是暫時的,所以我們不必擔心伶盜龍屬或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
正等着要處理,我們返回的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;
}
在這一點上,我們已經回答了OP的問題。這需要很長時間才能運行,但它會隨着嘗試的值(m = 4,n = 2)返回。它永遠不會拋出StackOverflowException
,儘管它最終將耗盡內存超過某些值m
和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(n == 0)
{
--m;
n = 1;
goto skipStack;
}
else
{
stack.Push(m - 1);
--n;
goto skipStack;
}
}
return n;
}
這不會幫助我們堆也有意義地堆,但提供的電話號碼循環這個東西會做大值,我們可以刮掉的每一點都是值得的。
消除goto
同時保持最優化就留給讀者做練習:)
順便說一句,我在測試這個太心急了,所以我做了一個使用阿克曼函數的已知特性,當米的作弊形式小於3:
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;
}
有了這個版本,我可以過一小獲得的true
結果爲Ackermann(4, 2) == BigInteger.Pow(2, 65536) - 3
在第二(黑白,發佈版本,在酷睿i7運行)。鑑於非作弊版本在返回此類值爲m
的正確結果時是一致的,我將此作爲前一版本的正確性的合理證據,但我將讓它繼續運行並查看。
編輯:當然,我真的不希望以前的版本在任何合理的時間內返回,但我想我會離開它無論如何運行,看看它的內存使用情況如何。 6小時後,它坐在40MiB以下。我很高興,雖然顯然不切實際,但如果在真機上有足夠的時間,它確實會回來。
編輯:顯然有人認爲Stack<T>
達到2³¹的內部限制也算是一種「堆棧溢出」。我們可以處理也如果我們必須:
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;
}
再次呼籲Ackermann(4, 2)
回報:
這是正確的結果。所使用的堆棧結構永遠不會拋出,所以剩下的唯一限制就是堆(當然還有時間,如果輸入足夠大,則必須使用「宇宙生命週期」作爲度量單位......)。
,因爲它的使用方式類似於一個圖靈機的磁帶,我們想起的是可以足夠大的圖靈機上計算任何可計算函數論文。
哪行不棧溢出發生在哪裏?此外,你見過:http://rosettacode.org/wiki/Ackermann_function#C.23 –
似乎是一個預期的結果http://en.wikipedia.org/wiki/Ackermann_function **它的價值迅速增長,甚至小投入。例如A(4,2)是19,729十進制數字的整數** –
哦,我... http://www.wolframalpha.com/input/?i=Ackerman%284%2C+2%29 –