簡體   English   中英

如何突破這個循環

[英]How to break out of this loop

我正在研究Euler項目編號5.我沒有使用Google搜索,因為這通常會導致SO得到答案。 所以,這就是我所擁有的:

    private int Euler5(int dividend, int divisor)
    {
        if (divisor < 21)
        {
            // if it equals zero, move to the next divisor
            if (dividend % divisor == 0) 
            {
                divisor++;
                return Euler5(dividend, divisor);
            }
            else
            {
                dividend++;
                return Euler5(dividend, 1); // move to the dividend
            }
        }
        // oh hey, the divisor is above 20, so what's the dividend
        return dividend; 
    }

在我看來,這是有道理的。 然而VS2012給了我一個StackOverFlowException,表明我確保我沒有進入無限循環或使用遞歸。 我的問題是,為什么這是一個無限循環? 我有一種感覺,我錯過了一些完全愚蠢的東西。

編輯

由於人們似乎一直在發帖,我會重申我沒有使用谷歌因為害怕絆倒答案。 我不想要問題的答案。 我只想知道為什么我得到了例外。

當然這種邏輯會打擊堆棧。 想想看,如果你要實現這個邏輯來解決找到可被1-10整除的最小數字的問題 ,根據問題陳述 ,你在堆棧中至少有2520個調用:

2520是可以除以1到10中的每個數字而沒有任何余數的最小數字。

對於1-20,答案顯然要大得多,而且你在吹噓堆棧也就不足為奇了。 你應該找到一個非遞歸的解決方案。

我的問題是,為什么這是一個無限循環?

不是。 堆棧的大小有限。 您正在進行過多的遞歸調用,並最終破壞了最大堆棧大小。

我有一種感覺,我錯過了一些完全愚蠢的東西。

你來對了地方

給傑森的答案+1,這清楚地解釋了問題。

現在有一些解決方案! 我知道至少有三種方法可以從算法中刪除遞歸:

  1. 找一個純粹的迭代算法(對於某些問題可能很難);
  2. 將遞歸算法轉換為具有循環的類似算法,並使用Stack <T>(或某種列表)來存儲調用堆棧的等效項。 這與原始空間要求類似,但堆可以比堆棧大得多!
  3. 一系列特殊的遞歸算法是尾遞歸的 這些可以很容易地機械地改變,從不溢出堆棧。 你很幸運,這是你的情況!

如果所有遞歸調用都是尾調用 ,則算法是尾遞歸的 ,這意味着它們是在返回之前完成的最后一件事。 如果您不清楚,請使用Google查找更好的示例。

通過調整參數和使用goto而不是真實的調用,可以很容易地轉換這些算法。 再看一下你的例子:

private int Euler5(int dividend, int divisor)
{
    tail_call:
    if (divisor < 21)
    {
        // if it equals zero, move to the next divisor
        if (dividend % divisor == 0) 
        {
            divisor++;
            goto tail_call; // return Eular5(dividend, divisor);
        }
        else
        {
            dividend++;
            // return Eular5(dividend, 1); // move to the dividend
            divisor = 1;
            goto tail_call;
        }
    }
    // oh hey, the divisor is above 20, so what's the dividend
    return dividend; 
}

噢!嗨! 它的功能完全相同,但具有固定的堆棧大小(沒有調用,只有跳轉)。 現在有人會說:“呃......搞砸了!他們是邪惡的!死了,死了!”。 我想這是少數合法用途之一。 畢竟,如果你的編譯器足夠聰明,它本身就會進行尾調用優化(F#實際上是這樣,C#沒有,JIT可能在x64上執行,而不是在x86上執行)。

但對於那些人我會說:看起來好一點。 因為每個if / else分支的末尾都有一個goto,所以我可以將它完全移出“if”。 現在我有類似“start:if(X){Y(); goto start;}”想想看,它只是一個“while(X)Y()”循環。 所以你剛剛找到函數的迭代版本:

private int Euler5(int dividend, int divisor)
{
    while (divisor < 21)
    {
        // if it equals zero, move to the next divisor
        if (dividend % divisor == 0) 
        {
            divisor++;
        }
        else
        {
            dividend++;
            divisor = 1;
        }
    }
    // oh hey, the divisor is above 20, so what's the dividend
    return dividend; 
}

太好了!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM