斐波那契数列是数学中的一个经典数列,以其独特的递归性质而闻名。
数列的前两项通常是0和1(或者有时从1开始,当然这个不是强制要求),之后的每一项都是前两项的和。数列的前几项如下所示:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ......
斐波那契数列在自然界、艺术、建筑以及金融领域都有广泛的体现和应用,它还与黄金分割比例有关联。
我们可以用按照这个规律来实现斐波那契数列的计算——步骤:
-
初始化:
确定迭代的起始条件。对于斐波那契数列,前两项分别为F(0)=0和F(1)=1,所以需要初始化两个变量,分别代表当前项和前一项。 -
设定循环:
设定一个循环结构,从初始状态开始,逐步向目标状态推进。对于求解第n项,循环的次数应当是从2到n。 -
更新状态:
在每一次循环中,根据当前的状态计算新的状态。对于斐波那契数列,就是将前两项相加得到当前项。更新变量,将当前项赋值给前一项,将新计算出的当前项赋值给当前项变量。 -
终止条件:
循环应该有一个明确的终止条件。对于求解第n项,当循环计数器达到n时,循环结束。 -
返回结果:
当循环结束后,最后一个计算出的当前项即为所求的斐波那契数列的第n项。
#include <iostream>
using namespace std;
unsigned long fibonacci(unsigned int n) {
// Step 1: 初始化
if (n <= 1) return n;
unsigned long prev = 0, curr = 1;
// Step 2: 设定循环
for (unsigned int i = 2; i <= n; ++i) {
// Step 3: 更新状态
unsigned long next = prev + curr;
prev = curr;
curr = next;
}
// Step 5: 返回结果
return curr;
}
int main() {
unsigned int n;
cout << "请输入想要求解的斐波那契数列的项数:";
cin >> n;
cout << "斐波那契数列的第" << n << "项是:" << fibonacci(n) << endl;
return 0;
}
这是一种非常常见的方法。还有比如递归,动态规划等。可以作为了解。
(扩展)递归
斐波那契数列定义为:F(0) = 0
, F(1) = 1
, F(n) = F(n-1) + F(n-2)
(n > 1)。
unsigned long fibonacci_recursive(unsigned int n) {
if (n <= 1) // 基本情况
return n;
else // 递归情况
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2);
}
只要从高到底不断加就可以。
(扩展)动态规划
动态规划是一种优化的递归方法,主要针对那些具有重叠问题和最优子结构特征的问题。
它通过存储子问题的解来避免重复计算,从而大大提高了效率。
动态规划有两种主要形式:自底向上和自顶向下带备忘录(记忆功能)。
- 自底向上:从最小的子问题开始,逐步构建到最终的解。
- 自顶向下带备忘录:从大问题开始,遇到已解决的子问题就直接使用其解,避免重复计算。
unsigned long fibonacci_dp(unsigned int n) {
if (n <= 1)
return n;
unsigned long fib[n+1];
fib[0] = 0;
fib[1] = 1;
for (unsigned int i = 2; i <= n; i++) {
fib[i] = fib[i-1] + fib[i-2];
}
return fib[n];
}
(扩展)结合递归和动态规划的一种效率高的方法。
unsigned long fibonacci_memo(unsigned int n, unsigned long memo[]) {
if (memo[n] != -1)
return memo[n];
if (n <= 1)
memo[n] = n;
else
memo[n] = fibonacci_memo(n-1, memo) + fibonacci_memo(n-2, memo);
return memo[n];
}
unsigned long fibonacci_memo_topdown(unsigned int n) {
unsigned long memo[n+1];
for (unsigned int i = 0; i <= n; i++)
memo[i] = -1;
return fibonacci_memo(n, memo);
}
这是一种结合了递归与动态规划优点的技术。这种方法的核心思想是在递归的过程中,对已经计算过的结果进行缓存,这样当下次需要同样的结果时,就可以直接从缓存中读取,而不需要重新计算,这极大地提升了效率,尤其是在处理具有大量重复子问题的情况时。
让我们再次以斐波那契数列为例来深入解释这一过程:
假设我们想要计算F(n)
,即第n
个斐波那契数。按照递归的思路,我们会首先尝试计算F(n-1)
和F(n-2)
,然后将这两个结果相加得到F(n)
。但是,在计算F(n-1)
和F(n-2)
的过程中,我们又会遇到更多的子问题,比如F(n-2)
、F(n-3)
等等。如果我们不采取任何措施,那么对于F(n-2)
,我们会在计算F(n-1)
和F(n)
时重复计算两次。
步骤解析:
-
初始化备忘录:创建一个数组或哈希表作为备忘录,用于存储之前计算过的子问题的答案。通常,我们初始化所有值为
-1
或某个特殊标记,表示这些值尚未被计算。 -
递归计算:
- 当我们需要计算某个子问题
F(i)
时,首先检查备忘录中是否已经有了F(i)
的结果。 - 如果有,直接返回该结果;如果没有,按照递归公式计算
F(i)
,并将结果存入备忘录中。
- 当我们需要计算某个子问题
-
返回最终结果:当递归到达基本情况时,直接返回结果,否则返回备忘录中存储的计算结果。
代码实现带注释:
unsigned long fibonacci_memo(unsigned int n, unsigned long memo[]) {
// 如果结果已经计算过,直接返回
if (memo[n] != -1)
return memo[n];
// 基本情况
if (n <= 1)
memo[n] = n;
else {
// 计算并存储结果
memo[n] = fibonacci_memo(n-1, memo) + fibonacci_memo(n-2, memo);
}
// 返回计算结果
return memo[n];
}
unsigned long fibonacci_memo_topdown(unsigned int n) {
unsigned long memo[n+1];
// 初始化备忘录
for (unsigned int i = 0; i <= n; i++)
memo[i] = -1;
// 开始递归计算
return fibonacci_memo(n, memo);
}
这个理解稍微会难一点,初学者可能理解不了。我们可以作为扩展阅读,等后面学到这我们回来再看这个问题。