java.lang.StackOverflowError /// Pascal的三角形

时间:2013-09-24 10:03:30

标签: scala

我正在尝试定义一个过程,该过程采用列c和行r,从0开始计算并返回三角形中该点的数字:

def pascal(c: Int, r: Int): Int = {
    if (c <= 0) 1
    else
        println(pascal(r, c-1) * (r-c)/c)
        pascal(r, c-1) * (r-c)/c
}

当我运行我的代码时:

>>>pascal(1,3) 

我有以下错误:

pascal: (c: Int, r: Int)Int
java.lang.StackOverflowError

我该如何解决?

感谢。

6 个答案:

答案 0 :(得分:9)

要获得pascal三角形的元素,您只需添加prev行的2个元素。

以下代码可以正确执行此操作。

def pascal(c: Int, r: Int): Int = {
    if (c <= 0 || c == r) 1
    else pascal(c, r-1) + pascal(c-1, r-1)
}  

答案 1 :(得分:3)

我认为你不应该在pascal中再次调用println函数,你也应该使用花括号。我已经重写了一点:

def pascal(c: Int, r: Int): Int = {
  var p = 1
  if (c > 0) {
    p = pascal(r, c-1) * (r-c)/c 
    println(p)
  }
  p
}

打印pascal(1,3)

-1
-2

更新:我很想写一个正确的解决方案,这就是我最终的目标:

  def fact(n: Int, r: Int=1): Int = if (n==0) r else fact(n-1, n*r)

  def pascal(c: Int, r: Int): Int = {
    fact(c) / (fact(r) * fact(c-r))
  }

  def main(a: Array[String]) { 
    println(pascal(3, 2))
  }

唯一的问题(我所知道的)是坐标必须正确,索引从0开始。任何超出Pascal三角形范围的东西都会导致除以零,这包括OP给出的{{1 - #1行上只有两个数字。

答案 2 :(得分:3)

您可以尝试使用此版本来计算与Pascal三角形的条目相同的二项式系数:

  def pascal(col: Int, row: Int): Int = {

    val l = if (col > row / 2) col else row - col
    @tailrec
    def go(i: Int, acc: Int): Int = {
      if (i == l + 1) acc
      else go(i + 1, acc * (row - l + i) / i)
    }
    return go(1, 1);
  }

此解决方案使用尾递归,因此StackOverflowError不是威胁。

答案 3 :(得分:2)

你的Pascal的三角形算法是不正确的,但也许你还没有完成它,所以我不打算告诉你如何编写正确的算法,只是如何阻止堆栈溢出。

编辑:maksimov是正确的,因为忘记将你的else序列包装在大括号中,你只是使第一个表达式成为条件,这意味着第二个表达式总是被计算并循环无限地进入堆栈溢出。但是,添加大括号只会将堆栈溢出的100%概率更改为堆栈溢出的可能性,因为对pascal函数的调用不在尾部位置。为了消除堆栈溢出的风险,您必须进行进一步修改,如下所述......

递归函数可以使用tail recursion来避免堆栈溢出。但是,要实现这一点,递归调用必须在tail position。也就是说,递归调用必须是在函数返回之前执行的最终表达式,并且必须在不进行修改的情况下返回其值,一直将堆栈备份到第一次调用。由于您的计算使用pascal(r, c-1) * (r-c)/c,因此您始终在返回修改后的结果之前修改返回的值,因此在尾部位置调用而不是。您需要修改您的函数,以便在它最终返回时提供正确的结果而无需修改。最终结果应具有这种基本形状......

def pascal(c: Int, r: Int): Int = {
  ...
  if (c <= 0) 1 else pascal(..., ...)
}

你会发现这样做的唯一方法是在你的pascal函数中定义一个局部函数,让 进行递归....

def pascal(c: Int, r: Int): Int = {
  def loop(...): Int = ... match {
    case ... => 1
    case ... => x + y
    case ... => loop(...)
    case _ => loop(...)
  }
  ...
  if (c <= 0) 1 else loop(...)
}

这是因为递归函数必须检查状态以找出它是否已解决问题中的最终问题或子任务。状态可以是传递给loop的额外参数,也可以是在pascal顶层定义的本地变量

只有在您将递归安全地移动到本地函数后,才能安全地执行maksimov建议的内容

var p = if (c <= 0) 1 loop(...)
println(p)
p

您可以安全地执行此操作,因为递归调用全部发生在loop内,并且都是(如果您使用我的模板)尾部位置。

要明确:对pascal函数进行两次连续调用并不是导致溢出的原因。代码中的两个顶级调用是顺序的,不会相互干扰;如果一个完成,另一个完成。如果第一个失败,第二个失败(并删除第一个将不会阻止第二个失败)。

答案 4 :(得分:0)

以下是整个代码的示例:

object Main {

  def main(args: Array[String]) {
  println("Pascal's Triangle")

 for (row <- 0 to 10) {
  for (col <- 0 to row)
    print(pascal(col, row) + " ")

    println()
  }
 }


def pascal(c: Int, r: Int): Int = {
 if ( c <= 0 || c == r ){
    return 1
  }

  return pascal(c, r - 1) + pascal(c - 1, r -1)
}

}

答案 5 :(得分:-1)

纯功能编程

def printPascalsTriangle(n: Int): Unit ={

    // n=1 print 1
    // n=2 print 1 1
    // n=3 print 1 2 1   List(prev(0),prev(0+1),prev(1))
    // n=4 print 1 3 3 1 List(prev.head,prev(0+1),prev(1+2),prev.reverse.head)

    def combine(oldList: List[Int], newList: List[Int]) = List(oldList.head) ::: newList ::: List(oldList.reverse.head)

    def g(prevList:List[Int],v:Int) = List(prevList(v-1), prevList(v))

    def pascalLine(prevList: List[Int], n: Int): List[Int] =
    {
      if(n>0)
      prevList match {
        case Nil => {println(List(1));pascalLine(List(1),n-1)}
        case List(1) => {println(List(1,1));pascalLine(List(1,1),n-1)}
        case _ => {
          val newList=combine(prevList,(1 to prevList.length-1).map(x => g(prevList,x)).map(y=>y.sum).toList)
          println(newList)
          pascalLine(newList,n-1)
        }

      }
      else
        Nil
    }
    pascalLine(Nil,n)
}