如何突破这个循环

时间:2013-06-03 20:01:22

标签: c#

我正在使用Project 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,表明我确保我没有进入无限循环或使用递归。我的问题是,为什么这是一个无限循环?我有一种感觉,我错过了一些完全愚蠢的东西。

修改

由于人们似乎一直在发帖,我会重申我没有使用谷歌这一事实,因为他害怕找到答案。我不想要问题的答案。我只是想知道为什么我会得到例外。

2 个答案:

答案 0 :(得分:10)

当然这种逻辑会破坏筹码。想想看,如果你要实现这个逻辑来解决找到可被1-10整除的最小数字的问题,那么根据problem statement,你在堆栈中至少有2520个调用:< / p>

  

2520是可以除以1到10之间的每个数字的最小数字,没有任何余数。

对于1-20,答案显然要大得多,而且你所吹的堆栈也就不足为奇了。你应该找到一个非递归的解决方案。

  

我的问题是,为什么这是一个无限循环?

不是。堆栈的大小有限。你进行了太多的递归调用,并最终破坏了最大堆栈大小。

  

我有一种感觉,我错过了一些完全愚蠢的东西。

你来到right place

答案 1 :(得分:1)

+1给杰森的答案,这清楚地解释了问题。

现在有一些解决方案!我知道至少有三种方法可以从算法中删除递归:

  1. 找一个纯粹的迭代算法(对于某些问题来说可能很难);
  2. 将递归算法转换为具有循环的类似算法,并使用Stack&lt; T&gt;。 (或某种列表)来存储调用堆栈的等价物。这与原始空间要求类似,但堆可以比堆栈大得多!
  3. 一个特殊的递归算法系列是尾递归。这些可以很容易地机械地改变,从不溢出堆栈。你很幸运,这是你的情况!
  4. 算法是尾递归,如果它的所有递归调用都是尾调用,这意味着它们是在返回之前完成的最后一件事。如果您不清楚,请使用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; 
    }
    
    哦,嘿!它具有完全相同的功能,但具有固定的堆栈大小(没有呼叫,只有跳转)。 现在有人会说:&#34;呃......搞砸了!他们是邪恶的!死了,死了!&#34;。我说这是少数合法用途之一。毕竟,如果您的编译器足够智能,它本身就会进行尾调用优化(F#实际上是这样,C#不会,JIT可能在x64上执行,而不是在x86上执行)。

    但对于那些我说的人:看起来好一点。因为每个if / else分支的末尾都有一个goto,所以我可以将它移到&#34; if&#34;完全。现在我有类似&#34; start:if(X){Y();转到开始; }&#34;想一想,它只是一个&#34;而(X)Y()&#34;环。所以你刚刚找到函数的迭代版本:

    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; 
    }
    

    尼斯!