[英]Using Recursion in C#
在使用遞歸時是否有任何一般規則來避免stackoverflows?
您能夠遞歸多少次取決於:
Guid
局部變量的方法將比沒有任何局部變量的方法占用更多的堆棧)如何避免堆棧溢出? 不要遞歸太遠:) 如果你不能合理地確定你的遞歸會在不走很遠的情況下終止(我會擔心“超過 10”,盡管這很安全)然后重寫它以避免遞歸。
這實際上取決於您使用的遞歸算法。 如果它是簡單的遞歸,你可以這樣做:
public int CalculateSomethingRecursively(int someNumber)
{
return doSomethingRecursively(someNumber, 0);
}
private int doSomethingRecursively(int someNumber, int level)
{
if (level >= MAX_LEVEL || !shouldKeepCalculating(someNumber))
return someNumber;
return doSomethingRecursively(someNumber, level + 1);
}
值得注意的是,這種方法實際上只在遞歸級別可以定義為邏輯限制的情況下才有用。 如果這不可能發生(例如分而治之算法),您將必須決定如何平衡簡單性、性能和資源限制。 在這些情況下,一旦達到任意預定義的限制,您可能必須在方法之間切換。 我在快速排序算法中使用的一種有效方法是將其作為列表總大小的比率。 在這種情況下,邏輯限制是條件不再最優的結果。
我不知道有任何硬性設置可以避免堆棧溢出。 我個人盡量確保——
1. 我的基本情況是正確的。
2. 代碼在某個時候達到了基本情況。
如果您發現自己生成了這么多堆棧幀,您可能需要考慮將遞歸展開為循環。
特別是如果您正在執行多個級別的遞歸(A->B->C->A->B...),您可能會發現您可以將其中一個級別提取到循環中並為自己節省一些 memory。
正常的限制,如果在連續調用之間的堆棧上沒有多少剩余的話,大約是 15000-25000 層深。 如果您使用的是 IIS 6+,則為 25%。
大多數遞歸算法都可以迭代表示。
有多種方法可以增加分配的堆棧空間,但我寧願讓你先找到一個迭代版本。 :)
我在這里寫了一篇關於這個的短文。 基本上,我傳遞了一個名為 depth 的可選參數,每次將 go 深入其中時,都會向其添加 1。 在遞歸方法中,我檢查一個值的深度。 如果它大於我設置的值,我會拋出異常。 該值(閾值)將取決於您的應用程序需求。
除了有一個合理的堆棧大小並確保你分而治之,這樣你就可以不斷地處理一個較小的問題,而不是真的。
我只是想到了尾遞歸,但事實證明,C# 不支持它。 但是.Net-Framework 似乎支持它:
http://blogs.msdn.com/abhinaba/archive/2007/07/27/tail-recursion-on-net.aspx
如果您在默認 CLR 下運行,則線程的默認堆棧大小為 1 MB。 但是,其他主機可能會改變這一點。 例如,ASP 主機將默認值更改為 256 KB。 這意味着您的代碼可能在 VS 下運行良好,但在將其部署到真實托管環境時會中斷。
幸運的是,當您使用正確的構造函數創建新線程時,您可以指定堆棧大小。 根據我的經驗,很少有必要,但我見過一個案例,這是解決方案。
您可以編輯二進制文件本身的 PE header 以更改默認大小。 如果您想更改主線程的大小,這很有用。 否則,我建議在創建線程時使用適當的構造函數。
請記住,如果您必須詢問系統限制,那么您可能做錯了什么。
因此,如果您認為在正常操作中可能會出現堆棧溢出,那么您需要考慮一種不同的方法來解決問題。
將遞歸的 function 轉換為迭代的並不困難,尤其是 C# 具有 Generic::Stack 集合。 使用 Stack 類型將使用的 memory 移動到程序的堆而不是堆棧中。 這為您提供了存儲遞歸數據的完整地址范圍。 如果這還不夠,將數據分頁到磁盤並不太難。 但如果你到了這個階段,我會認真考慮其他解決方案。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.