2013-08-21 48 views
8

Vấn đề của tôi là tôi thường nhận được một java.lang.StackOverflowError khi tôi sử dụng đệ quy. Câu hỏi của tôi là - tại sao đệ quy gây ra stackoverflow nhiều hơn so với vòng lặp làm, và có cách nào tốt để sử dụng đệ quy để tránh tràn ngăn xếp?java.lang.StackOverflowError do đệ quy

Đây là một nỗ lực để giải quyết problem 107, nó hoạt động tốt cho ví dụ của họ nhưng hết dung lượng ngăn xếp cho chính sự cố đó.

//-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; 
    } 
} 
+0

Bạn có chắc chắn rằng các chuyến du ngoạn của bạn không tự gọi mình là vô hạn? Nó có thể giúp đưa mã của bạn vào. Mã số –

+0

rất phức tạp và vì nó hoạt động với số lượng nhỏ, tôi chắc chắn đây không phải là trường hợp. Mặc dù vậy, việc thêm mã là một ý tưởng hay. Tôi chỉ hy vọng nó sẽ không trôi quá nhiều chủ đề. – user2705335

+0

Khi bạn có các dòng Integer.MAX_VALUE, nó chỉ thực hiện đệ quy đầu tiên là 7 trong mỗi vòng lặp cho một số cấp độ sâu, nhưng nó có (7^(n + 1) -1/6) -n-1 lặp đi lặp lại, hầu hết nếu họ nhấn trường hợp cơ sở. Việc bắt đầu cho các vòng lặp sẽ thêm 6 lần n lần các dòng Integer.MAX_VALUE mặc dù. – Sylwester

Trả lời

16

tại sao đệ quy nguyên nhân stackoverflow rất nhiều hơn vòng làm

Bởi vì mỗi cuộc gọi đệ quy sử dụng một số không gian trên stack. Nếu đệ quy của bạn quá sâu, thì kết quả sẽ là StackOverflow, tùy thuộc vào độ sâu tối đa cho phép trong ngăn xếp.

Khi sử dụng đệ quy, bạn nên cẩn thận và đảm bảo rằng bạn cung cấp base case. Một trường hợp cơ sở trong đệ quy là điều kiện dựa trên đó đệ quy kết thúc, và ngăn xếp bắt đầu để thư giãn. Đây là lý do chính của đệ quy gây ra lỗi StackOverflow. Nếu nó không tìm thấy bất kỳ trường hợp cơ bản, nó sẽ đi vào một đệ quy vô hạn, mà chắc chắn sẽ dẫn đến lỗi, vì Stack là hữu hạn mà thôi.

0

Khi được sử dụng đúng cách, đệ quy sẽ không tạo ra StackOverflowError. Nếu có, thì trường hợp cơ sở của bạn không được kích hoạt và phương thức tiếp tục tự gọi quảng cáo là vô hạn. Mọi cuộc gọi phương thức mà không hoàn thành vẫn còn trên ngăn xếp, và cuối cùng nó tràn.

Nhưng các vòng lặp không liên quan đến các cuộc gọi phương thức, do đó không có gì tích tụ trên ngăn xếp và kết quả là StackOverflowError.

0

Mỗi khi bạn gọi một phương thức, bạn tiêu thụ một "khung" từ ngăn xếp, khung này không được phát hành cho đến khi phương thức trả về, nó không xảy ra giống với các vòng lặp.

3

Trong hầu hết các trường hợp, tràn ngăn xếp xảy ra do phương pháp đệ quy không được xác định, với điều kiện kết thúc không tồn tại hoặc không thể truy cập, làm cho không gian bộ nhớ ngăn xếp hết. Một đệ quy bằng văn bản chính xác không nên tạo tràn ngăn xếp.

Tuy nhiên, có các tình huống trong đó phương pháp có thể tạo tràn ngăn xếp thậm chí nếu nó được triển khai chính xác. Ví dụ:

  • Tiếp tục phát triển nhanh (nói theo hàm mũ). Ví dụ: việc thực hiện đệ quy ngây thơ của Fibonacci chức năng
  • Một dữ liệu đầu vào rất lớn, mà cuối cùng sẽ làm cho không gian ngăn xếp để bị cạn kiệt

Bottom line: tất cả phụ thuộc vào trường hợp cụ thể, nó không thể khái quát về nguyên nhân gây tràn ngăn xếp.

0

đệ quy gây tràn ngăn khiến tất cả các cuộc gọi trước đó đều có trong bộ nhớ. do đó, phương thức của bạn tự gọi chính nó với các tham số mới, sau đó lại tự gọi chính nó. vì vậy tất cả các cuộc gọi này được xếp chồng lên nhau và thường có thể hết bộ nhớ. vòng lưu trữ các kết quả bình thường trong một số biến và gọi các phương thức giống như một cuộc gọi mới đến phương thức, sau mỗi cuộc gọi, các phương thức người gọi kết thúc và trả về kết quả.

3

Mỗi cuộc gọi đệ quy sử dụng một số khoảng trống trên ngăn xếp (để chỉ bất kỳ thứ gì cụ thể cho một cuộc gọi đó, chẳng hạn như đối số, biến cục bộ, v.v.). Do đó, nếu bạn thực hiện quá nhiều cuộc gọi đệ quy (hoặc bằng cách không cung cấp chính xác trường hợp cơ sở hoặc chỉ bằng cách cố gắng thực hiện quá nhiều cuộc gọi đệ quy), thì không đủ chỗ để cung cấp dung lượng cho tất cả, và bạn kết thúc bằng StackOverflow . Lý do tại sao vòng lặp không có vấn đề này là mỗi lần lặp của vòng lặp không sử dụng không gian riêng của nó (tức là nếu tôi lặp lại n lần, tôi không cần thêm khoảng trống để làm vòng lặp n+1).

0

Lý do tại sao đệ quy gây tràn ngăn xếp là vì chúng tôi không thiết lập khi đệ quy dừng lại và do đó chức năng/phương pháp sẽ tiếp tục tự gọi là "mãi mãi" (cho đến khi nó gây ra lỗi). Bạn sẽ có cùng một vấn đề ngay cả khi bạn đang sử dụng các vòng lặp, nếu bạn có một cái gì đó như sau:

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

Kể từ flag sẽ luôn luôn đúng, vòng lặp while sẽ không bao giờ dừng lại cho đến khi nó mang lại cho bạn những lỗi stack overflow.

+1

Điều này là sai. Những gì bạn có ở đây là một vòng lặp vô hạn mà sẽ gây ra số đếm tràn (giả sử nó là một số nguyên/dài) nhưng không có cơ hội rằng điều này sẽ gây ra một StackOverflowError. Thật vậy, vòng lặp vô hạn được sử dụng rộng rãi. – u6f6o

0

Mọi cấp độ đệ quy mà bạn đi xuống, bạn sẽ thêm thông tin trạng thái vào ngăn xếp thời gian chạy. Thông tin này được lưu trữ trong một bản ghi kích hoạt và chứa thông tin như các biến nào nằm trong phạm vi và giá trị của chúng là gì. Vòng lặp không có hồ sơ kích hoạt thêm mỗi khi bạn lặp lại để chúng mất ít bộ nhớ hơn.

Trong một số trường hợp, đệ quy của bạn có thể đủ sâu khiến cho ngăn xếp tràn, nhưng có nhiều cách để giúp ngăn chặn điều này xảy ra. Khi làm việc với đệ quy, tôi thường làm theo định dạng sau:

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

Đuổi có thể siêu hiệu quả và làm những việc mà vòng lặp không thể thực hiện được. Đôi khi bạn chỉ cần đến một điểm mà đệ quy là quyết định rõ ràng. Điều gì làm cho bạn một lập trình tốt là có thể sử dụng nó khi nó không phải là hoàn toàn obvoius.