栈溢出
问题描述
用递归做 n + ( n − 1 ) + ( n − 2 ) . . . + 1 n + (n-1) + (n-2) ... + 1 n+(n−1)+(n−2)...+1
public static long sum(long n) {
if (n == 1) {
return 1;
}
return n + sum(n - 1);
}
参数设为
n
=
12000
n = 12000
n=12000 时,发生了栈溢出的现象;
为什么呢?
● 每次方法调用是需要消耗一定的栈内存的,这些内存用来存储方法参数、方法内局部变量、返回地址等等
● 方法调用占用的内存需要等到方法结束时才会释放
● 而递归调用我们之前讲过,不到最深不会回头,最内层方法没完成之前,外层方法都结束不了
代码示例
long sum(long n = 3) {
return 3 + long sum(long n = 2) {
return 2 + long sum(long n = 1) {
return 1;
}}}
在上面的代码中, s u m ( 3 ) sum(3) sum(3) 这个方法内有个需要执行 3 + s u m ( 2 ) 3 + sum(2) 3+sum(2), s u m ( 2 ) sum(2) sum(2) 没返回前,加号前面的 3 3 3 不能释放
在解决这个问题之前先了解一个名词;
问题解决方案
1.尾调用
如果函数的最后一步是调用一个函数,那么称为尾调用,例如
function a() {
return b()
}
下面三段代码不能叫做尾调用
因为最后一步并非调用函数
function a() {
const c = b()
return c
}
虽然调用了函数,但是又用到了数值1
function a() {
return b() + 1
}
虽然调用了函数,但是又用到了外层的变量x
function a(x) {
return b() + x
}
在一些语言的编译器能够对尾调用做优化:例如:
代码示例
function a() {
// 做前面的事
return b()
}
function b() {
// 做前面的事
return c()
}
function c() {
return 1000
}
这时a尾调用b,b尾调用c,而只有执行到c时才会依次的向上返回调用结果,但是编译器一般都会对这种尾调用做优化。
思考:
为何尾递归才能优化?
调用 a 时
● a 返回时发现:没什么可留给 b 的,将来返回的结果 b 提供就可以了,用不着我 a 了,我的内存就可以释放
调用 b 时
● b 返回时发现:没什么可留给 c 的,将来返回的结果 c 提供就可以了,用不着我 b 了,我的内存就可以释放
如果调用 a 时
● 不是尾调用,例如 return b() + 1,那么 a 就不能提前结束,因为它还得利用 b 的结果做加法
尾递归
尾递归是尾调用的一种特例,也就是最后一步执行的是同一个函数
2.使用for循环
递归所产生的的栈溢出问题主要是因为上一层的数据要等待下一层级执行完成之后才可以执行,所以造成了大量的内存占用,使用for循环就可以避免这一问题。
代码示例
long x = 10000000000;
long sum = 0;
for (long n = 0; n >= 1; n--){
sum += n;
}