什么是斐波那契数列
斐波那契数列(Fibonacci sequence)是一组数字序列,其中每个数字是它前面两个数字之和。这个数列的起始两个数字通常定义为0和1。
数列展示:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89……
计算过程:
-
第1项:0(起始值)
-
第2项:1(起始值)
-
第3项:0 + 1 = 1
-
第4项:1 + 1 = 2
-
第5项:1 + 2 = 3
-
第6项:2 + 3 = 5
-
以此类推
在数学上,斐波那契数列常被用来演示递归思想。不过在编程中,迭代方式往往更实用。这一点后面会详细说明。
核心实现逻辑
斐波那契数列的生成需要三个关键变量:
-
$n1:前一个数(初始为0) -
$n2:当前数(初始为1) -
$n3:下一个数($n1 + $n2)
每次迭代中,将$n1和$n2向前推进:
-
计算
$n3 = $n1 + $n2 -
输出
$n3 -
将
$n2赋值给$n1 -
将
$n3赋值给$n2 -
重复以上步骤
示例一:使用while循环输出前12个斐波那契数
代码号学习编程的初学者通常先从循环版本开始理解。
<?php
$count = 0; // 已输出的数字个数(不包括前两个)
$first = 0;
$second = 1;
echo "<h3>斐波那契数列前12个数:</h3>";
echo $first . " " . $second . " ";
while ($count < 10) { // 已经输了2个,还需要再输出10个,总共12个
$next = $second + $first;
echo $next . " ";
$first = $second;
$second = $next;
$count = $count + 1;
}
?>
输出:0 1 1 2 3 5 8 13 21 34 55 89
为什么循环条件是$count < 10而不是12:因为前两个数字0和1已经在循环之前输出了,剩下的10个数字需要在循环中生成。这个细节容易搞错,很多人会写成$count < 12导致输出了14个数字。
示例二:使用for循环的简洁写法
for循环在处理已知次数的迭代时更加紧凑。
<?php
$first = 0;
$second = 1;
$terms = 12; // 要输出的项数
echo "斐波那契数列前{$terms}个数:";
echo $first . " " . $second . " ";
for ($i = 3; $i <= $terms; $i++) { // 从第3项开始计算
$next = $first + $second;
echo $next . " ";
$first = $second;
$second = $next;
}
?>
个人经验:用for循环时,我习惯让循环变量从3开始,这样与项数的对应关系更清晰:i=3时生成第3项,i=4时生成第4项……读代码的人一眼就能看出逻辑,不需要额外注释。
示例三:使用递归方法实现斐波那契数列
递归(Recursion)指的是一个函数在其定义内部调用自身。斐波那契数列的数学定义本身就是递归形式:F(n) = F(n-1) + F(n-2)。
<?php
/* 递归函数:返回第n个斐波那契数 */
function fibonacci($n) {
if ($n == 0) {
return 0;
} elseif ($n == 1) {
return 1;
} else {
return fibonacci($n - 1) + fibonacci($n - 2);
}
}
$num = 12;
echo "<h3>使用递归方法输出前12个斐波那契数:</h3>";
for ($i = 0; $i < $num; $i++) {
echo fibonacci($i) . " ";
}
?>
输出:0 1 1 2 3 5 8 13 21 34 55 89
本节课程知识要点
| 知识点 | 说明 |
|---|---|
| 迭代与递归 | 两种实现思路,迭代效率更高,递归更符合数学定义 |
| 状态更新顺序 | 必须先更新$first再更新$second,顺序反了会导致计算错误 |
| 递归基线条件 | $n == 0 和 $n == 1 是终止条件,防止无限递归 |
| 时间复杂度 | 递归版本O(2ⁿ)效率极低,迭代版本O(n) |
迭代与递归的选择建议
递归版本的严重问题:
递归方法虽然代码看起来简洁,但存在一个实际的问题——重复计算。以计算fibonacci(5)为例:
fibonacci(5) = fibonacci(4) + fibonacci(3)
fibonacci(4) = fibonacci(3) + fibonacci(2)
fibonacci(3) = fibonacci(2) + fibonacci(1)
……
fibonacci(3)被计算了2次,fibonacci(2)被计算了3次。当n变大时,重复计算的次数呈指数级增长。计算fibonacci(40)时,函数调用次数可达3亿次以上,页面响应会变得非常缓慢。
我的个人建议:
在PHP中生成斐波那契数列,优先使用迭代(循环)方式。递归方法虽然适合展示算法思想,但在项目中基本不会用它来做数值计算。除非配合记忆化(Memoization)技术——把计算过的结果缓存起来避免重复计算,但这已经超出了基础范围。
什么时候用递归更合适:处理树形结构(比如遍历文件夹目录、解析嵌套评论)、分治算法等场景,递归的代码表达力更强。但就斐波那契数列而言,迭代方式是更实在的选择。
常见问题与排查
问题1:输出结果只有0和1,没有后续数字
-
原因:循环中更新
$first和$second的顺序写反了 -
正确顺序:先更新
$first为新值,再更新$second
问题2:递归版本运行时间过长甚至超时
-
原因:没有使用缓存优化,重复计算量巨大
-
解决:改用迭代版本,或使用数组缓存中间结果
问题3:数值超出整数范围
-
说明:斐波那契数列增长速度很快,第93项就会超过64位整数上限
-
解决:大数场景下使用BCMath扩展的任意精度函数
扩展:使用数组缓存优化递归
如果一定要用递归又想避免重复计算,可以借助数组做记忆化:
$cache = [];
function fibonacciMemo($n, &$cache) {
if ($n <= 1) return $n;
if (!isset($cache[$n])) {
$cache[$n] = fibonacciMemo($n-1, $cache) + fibonacciMemo($n-2, $cache);
}
return $cache[$n];
}
这种方式把计算过的值存起来,避免重复递归,效率能提升很多。
个人经验分享
当年第一次接触递归时觉得这个写法很简洁,有种数学上的美感。但在一个实际项目中用递归计算斐波那契数列到第50项,页面直接超时。那次之后我对递归在数值计算中的应用变得更加谨慎。
后来了一个原则:如果一个问题能用循环清晰解决,就先考虑循环。递归的价值更多体现在处理“未知深度”的结构上,而不是纯粹的数学序列。这个经验分享给正在学习的各位,希望能帮你少走一些弯路。