为啥我的代码会导致 java 中的堆栈溢出错误?它最终应该终止。 for循环版本不会导致错误

Posted

技术标签:

【中文标题】为啥我的代码会导致 java 中的堆栈溢出错误?它最终应该终止。 for循环版本不会导致错误【英文标题】:Why does my code cause stack overflow error in java? It should terminate eventually. The for loop version does not cause the error为什么我的代码会导致 java 中的堆栈溢出错误?它最终应该终止。 for循环版本不会导致错误 【发布时间】:2014-12-18 16:55:32 【问题描述】:

更新 10/23/2014:我认为递归函数基本上是一个 void 函数,所以返回后不需要更多工作,它应该相当于尾递归版本。它不应该导致堆栈溢出。我实际上也尝试了尾递归非空返回类型,它仍然导致堆栈溢出错误。我想即使它是尾递归的,它仍然取决于编译器解释器如何实现它。

相同代码的for循环版本不会导致堆栈溢出错误。但是下面的代码(递归版本)可以。本质上他们应该做同样的事情。异常错误出现在循环 7000 (i=7000) 附近。

我很好奇为什么递归版本会导致错误,而 for 循环版本不会。我有些怀疑,但不确定。

//recursive version, overflow

public class Solution 
    int n, low = -1, profit = 0;
    int[] a;

    public int maxProfit(int[] prices) 
        n = prices.length;
        if (n == 0 || n == 1)
            return 0;
        a = prices;
        maxProfit(1);
        return profit;
    

    public void maxProfit(int i) 

        if (i == n - 1) 
            if (low != -1 && a[i - 1] <= a[i])
                profit += a[i] - low;
            return;
        
        if (a[i - 1] < a[i] && low == -1) 
            low = a[i - 1];
        
        if (a[i - 1] > a[i] && low != -1) 
            profit += a[i - 1] - low;
            low = -1;
        
        //System.out.print(i+",");
        maxProfit(i + 1);
    

    public static void main(String[] args) 
        Solution sl = new Solution();
        // int[] a =  1, 9, 6, 9, 1, 7, 1, 1, 5, 9, 9, 9 ;
        // int[] a =  4, 4 ;
        // int[] a =  3, 2 ;
        // int[] a =  1, 2, 3, 4, 3 ;
        // int[] a =  3, 2, 1, 0, 4, 5, 6, 7, 10, 4, 9, 7 ;
        int[] a = new int[100001];
        for (int i = 0; i < 100001; i++) 
            a[i] = 100000 - i;
        
        System.out.println();
        System.out.println(sl.maxProfit(a));
    

【问题讨论】:

这可能与递归版本有关,希望能够拥有 100000 帧深的调用堆栈... 当你的基本情况没有被命中时,递归函数往往会导致 ***Error,使堆栈展开......这可能是因为搞砸了基本情况逻辑,而不是朝着基本情况移动,或试图递归太深 【参考方案1】:

到目前为止,没有人真正解释发生了什么......

当你调用一个函数时,它不仅仅是去那里——它必须准确地记住你在哪里以及你的所有变量是什么,这样它才能在你的函数返回时返回到那个确切的位置。

它通过将当前状态存储在堆栈(沿一个方向建立的内存区域)来实现这一点。每次您的应用递归时,堆栈都会变得更深,需要更多内存。

堆栈溢出是指堆栈变得如此之大以至于溢出分配给它的内存。

您的循环版本每次迭代不需要任何额外的内存,因为它不必记住何时返回——它只是循环运行并且可以永远这样做。

对不起,如果这不是您的要求。

【讨论】:

它不会永远循环。它确实有退出条件。 @jesseZhuang 当然你是对的,我根本没有这么说。我刚刚说过迭代解决方案可以永远循环,因为它不会为每次迭代分配额外的空间(循环解决方案),而不是递归实现,它会为每个迭代分配并且不能永远循环。【参考方案2】:

您的递归深度是 100000 次调用。是的,堆栈大小不受限制,您的堆栈不会溢出,但实际上堆栈大小是有限的。 32 位 VM 上的默认堆栈大小为 320k,而 64 位 VM 上的默认堆栈大小为 1024k。在 32 位 vm 上,您的特定函数将在每个调用深度级别使用 64 位堆栈。您传递的整数为 32 位,返回地址为 32 位。 64 位 * 100000 深度 = 8 字节 * 100000 深度 = 800KB(大约)。这比默认堆栈大小大得多。

【讨论】:

【参考方案3】:

认为我在 cmets 中添加了一个答案,不妨让它成为一个合法的答案...

当您的基本情况未命中时,递归函数往往会导致 ***Error,从而使堆栈展开。这可能是因为弄乱了基本案例逻辑,没有转向基本案例,或者试图递归太深。

在您的情况下,这是因为您尝试将调用堆栈设置为 100001 级深。那是巨大的。你试图递归地做太多事情,这就是导致问题的原因。

【讨论】:

【参考方案4】:

你很震惊,因为你的程序没有退出条件。看它进入循环 maxProfit(1),但没有退出循环的选项。

更新: 一旦你的程序进入这个循环,它就没有办法出来了..

public void maxProfit(int i) 

    if (i == n - 1) 
        if (low != -1 && a[i - 1] <= a[i])
            profit += a[i] - low;
        System.out.println("i");
        return;
    
    if (a[i - 1] < a[i] && low == -1) 
        low = a[i - 1];
        System.out.println("i");
    
    if (a[i - 1] > a[i] && low != -1) 
        profit += a[i - 1] - low;
        low = -1;
        System.out.println("i");
    
    //System.out.print(i+",");

    maxProfit(i + 1);
 

【讨论】:

他的程序确实有一个退出条件: if (i == n - 1) if (low != -1 && a[i - 1] 错误:if (i == n - 1) 是退出条件。 @Maxaon3000,我不知道你们在看哪里,一旦达到 maxProfit(int i),程序就无法摆脱它。正确检查代码。 @user2900314:仔细阅读。当 i == 99999 时,递归中最底部的函数返回,展开堆栈。我的意思是,如果堆栈大小足够大,就会发生这种情况。 @user2900314 看,既然编程是一门经验科学,我们不必一直争论。为什么不直接将循环计数器设置为 1000 并运行代码?你会看到它出来了。

以上是关于为啥我的代码会导致 java 中的堆栈溢出错误?它最终应该终止。 for循环版本不会导致错误的主要内容,如果未能解决你的问题,请参考以下文章

Java 中的自调用函数中的堆栈溢出错误(岛数)

为啥这个 BigInteger 值会导致堆栈溢出异常? C#

为啥在某些机器上堆栈溢出,但在另一台机器上出现分段错误?

有人可以指出代码“满足给定总和条件的子序列数”的错误,这会导致堆栈溢出错误

为啥带有 setTimeout 的函数不会导致堆栈溢出

鉴于我将 DataBag 溢出到磁盘,为啥此 Pig UDF 会导致“错误:Java 堆空间”?