【问题标题】:java.lang.StackOverflowError due to recursionjava.lang.StackOverflowError 由于递归
【发布时间】:2013-08-24 11:21:18
【问题描述】:

我的问题是,当我使用递归时,我通常会得到一个 java.lang.StackOverflowError。 我的问题是 - 为什么递归比循环更容易导致堆栈溢出,有没有使用递归来避免堆栈溢出的好方法?

这是解决problem 107 的尝试,它适用于他们的示例,但由于其自身的问题而耗尽了堆栈空间。

//-1 16 12 21 -1 -1 -1 16 -1 -1 17 20 -1 -1 12 -1 -1 28 -1 31 -1 21 17 28 -1 18 19 23 -1 20 -1 18 -1 -1 11 -1 -1 31 19 -1 -1 27 -1 -1 -1 23 11 27 -1
public class tries
{
    public static int n=7,min=Integer.MAX_VALUE;
    public static boolean[][] wasHere=new boolean[n][60000];
    public static void main(String[] args)
    {
        int[] lines=new int[n]; Arrays.fill(lines, -1000); lines[0]=0;
        int[][] networkMatrix=new int[n][n];
        Scanner reader=new Scanner(System.in);
        int sum=0;
        for(int k=0; k<n; k++)
        {
            for(int r=0; r<n; r++)
            {
                networkMatrix[k][r]=reader.nextInt();
                if(networkMatrix[k][r]!=-1) sum+=networkMatrix[k][r];
                Arrays.fill(wasHere[k], false);
            }
        }
        recursive(lines,networkMatrix,0,0);
        System.out.println((sum/2)-min);
    }
    public static void recursive(int[] lines, int[][] networkMatrix, int row,int lastRow)
    {       
        wasHere[row][value((int)use.sumArr(lines))]=true;
        if(min<sum(lines)) return;
        if(isAllNotMinus1000(lines)) min=sum(lines); 
        int[][] copyOfMatrix=new int[n][n];
        int[] copyOfLines;
        for(int i=0; i<n; i++)
        {
            copyOfLines=Arrays.copyOf(lines, lines.length);
            for(int k=0; k<n; k++)  copyOfMatrix[k]=Arrays.copyOf(networkMatrix[k], networkMatrix[k].length);
            if(i!=0&&copyOfMatrix[i][row]!=0) copyOfLines[i]=copyOfMatrix[i][row];
            copyOfMatrix[i][row]=0; copyOfMatrix[row][i]=0;
            if(networkMatrix[row][i]==-1) continue;
            if(wasHere[i][value((int)use.sumArr(copyOfLines))]) continue;
            if(min<sum(copyOfLines)) continue;
            recursive(copyOfLines,copyOfMatrix,i,row);
        }
    }
    public static boolean isAllNotMinus1000(int[] lines)
    {
        for(int i=0; i<lines.length; i++) {if(lines[i]==-1000) return false;}
        return true;
    }
    public static int value(int n)
    {
        if(n<0) return (60000+n);
        return n;
    }
    public static int sum(int[] arr)
    {
        int sum=0;
        for(int i=0; i<arr.length; i++) 
        {
            if(arr[i]==-1000) continue;
            sum+=arr[i];
        }
        return sum;
    }
}

【问题讨论】:

  • 你确定你的递归不是无限地调用自己吗?包含您的代码可能会有所帮助。
  • 代码非常复杂,因为它适用于小数字,我确信情况并非如此。不过,添加代码是个好主意。我只是希望它不会偏离主题。
  • 当你有 Integer.MAX_VALUE 行时,它只在每个 for 循环中进行了 7 的第一次递归,深度为 n 级,但它有 (7^(n+1)-1/6)-要进行 n-1 次迭代,大多数情况下如果它们达到基本情况。不过,开始的 for 循环将添加 6 次 n 次 Integer.MAX_VALUE 行。

标签: java recursion stack-overflow


【解决方案1】:

为什么递归比循环更容易导致堆栈溢出

因为每次递归调用都会占用堆栈上的一些空间。如果您的递归太深,那么它将导致StackOverflow,具体取决于堆栈中允许的最大深度。

使用递归时,您应该非常小心,并确保提供base case。递归的基本情况是递归结束和堆栈开始展开的条件。这是递归导致StackOverflow 错误的主要原因。如果它没有找到任何基本情况,它将进入无限递归,这肯定会导致错误,因为Stack 只是有限的。

【讨论】:

  • 即使定义了基本情况,递归过程也可能导致 StackOverFlow。考虑使用错误拆分进行快速排序
【解决方案2】:

在大多数情况下,堆栈溢出是因为递归方法定义不正确,结束条件不存在或无法到达,导致堆栈内存空间耗尽。正确编写的递归不应产生堆栈溢出。

但是,在某些情况下,如果正确实施方法,即使也会产生堆栈溢出。例如:

  • 快速增长(例如指数)递归。例如:斐波那契函数的朴素递归实现
  • 非常大的输入数据,最终会导致堆栈空间耗尽

底线:这一切都取决于特定情况,不可能一概而论导致堆栈溢出的原因。

【讨论】:

    【解决方案3】:

    每个递归调用都使用堆栈上的一些空间(用于存放特定于该调用的任何内容,例如参数、局部变量等)。因此,如果你进行了太多递归调用(要么没有正确提供基本情况,要么只是尝试进行太多递归调用),那么就没有足够的空间来为它提供空间,你最终会得到一个 @ 987654321@.

    循环没有这个问题的原因是循环的每次迭代不使用自己的唯一空间(即如果我循环n次,我不需要额外的空间来做n+1st循环)。

    【讨论】:

      【解决方案4】:

      您向下的每一级递归,都将状态信息添加到运行时堆栈。此信息存储在激活记录中,并包含诸如哪些变量在范围内以及它们的值是什么等信息。每次循环时,循环都没有额外的激活记录,因此它们占用的内存更少。

      在某些情况下,您的递归可能会深入到导致堆栈溢出,但有一些方法可以帮助防止这种情况发生。在使用递归时,我通常遵循以下格式:

      public obj MyMethod(string params) {
          if (base-case) {
              do something...
          } else {
              do something else...
              obj result = MyMethod(parameters here);
              do something else if needed..
          }
      }
      

      递归可以非常有效,并且可以做循环不能做的事情。有时你只是到了递归是显而易见的决定的地步。让你成为一名优秀程序员的原因是能够在它不是完全显而易见的情况下使用它。

      【讨论】:

        【解决方案5】:

        如果使用得当,递归不会产生StackOverflowError。如果是这样,那么您的基本情况不会被触发,并且该方法会无限地调用自己。每个未完成的方法调用都留在堆栈上,最终溢出。

        但循环本身不涉及方法调用,因此不会在堆栈上建立任何内容,并且不会产生 StackOverflowError

        【讨论】:

        • 你错了。一些繁重的递归方法需要做大工作,这并不代表使用不当。
        【解决方案6】:

        每次调用方法时,都会从堆栈中消耗一个“帧”,该帧在方法返回之前不会释放,循环不会发生同样的情况。

        【讨论】:

          【解决方案7】:

          递归导致堆栈溢出,因为之前的所有调用都在内存中。所以你的方法用新参数调用自己,然后再次调用自己。所以所有这些调用都会堆积起来,通常会耗尽内存。 循环通常将结果存储在一些变量中并调用方法,就像对方法的新调用一样,每次调用后,调用者方法结束并返回结果。

          【讨论】:

            【解决方案8】:

            递归导致堆栈溢出的原因是因为我们无法确定递归应该何时停止,因此函数/方法将“永远”保持调用自身(直到它导致错误)。即使您使用循环,您也会遇到同样的问题,如果您有以下情况:

            bool flag = true;
            while (flag == true){
               count++;
            }
            

            由于flag 始终为真,while 循环将永远不会停止,直到它给出堆栈溢出错误。

            【讨论】:

            • 这是错误的。您在这里拥有的是一个无限循环,它将导致计数溢出(假设它是整数/长整数),但这不可能导致 StackOverflowError。事实上,无限循环被广泛使用。
            【解决方案9】:

            在我看来,由于以下原因导致 StackOverFlow 在递归中出现错误:

            1. 没有正确实现递归,导致无限递归,所以请检查基本情况等。

            2. 如果你的输入很大,最好使用尾递归来避免 StackOverflow。

            【讨论】:

              【解决方案10】:

              这里for looprecursive function 内部使用。当递归函数被调用时,for(int i=0; i&lt;n; i++) i 的值被初始化为零,当它调用自己时,i 的值将再次被初始化为零并且它无限地持续下去。这将导致您出现堆栈溢出错误。

              解决方案:避免在递归函数中使用for loop;而是去 while or do-while 并在递归函数之外初始化 i 的值

              【讨论】:

              • 递归函数(或任何地方)中的 while 循环也可能导致无限循环
              • @varontron 这个无限循环是由初始化引起的。如果你在递归函数之外进行初始化,你最好使用while循环
              猜你喜欢
              • 2013-10-12
              • 2013-05-11
              • 1970-01-01
              • 2022-11-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-11-07
              相关资源
              最近更新 更多