一,初始递归:阶乘
1,原理
n的阶乘等于n乘以n-1的阶乘,而0的阶乘等于1.
2,代码展示
#include <iostream>
using namespace std;
int fact(int);
int main()
{
cout<<fact(5);
return 0;
}
int fact(int n)
{
if(n==0) return 1;
return n * fact(n-1);
}
二,深化理解:斐波那契数列
1,原理
当n小于等于2时,f(n)= 1;当大于2时,f(n) = f (n-1) + f(n-2).
2,代码展示
a,暴力复杂的方法,复杂度成几何形式增长
#include<iostream>
using namespace std;
int Fibonacci(int);
int main()
{
cout << Fibonacci(30);
return 0;
}
int Fibonacci(int n)
{
if (n == 1 || n == 2) return 1;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
我的电脑在n 等于100时,就半天跑不出来结果了。
b,动态规划
直接使用数组,不使用函数递归。
int Fibonacci(int n)
{
if (n == 1 || n == 2) return 1;
int* dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
if (n == 1 || n == 0) return 1;
for (int i = 2; i < n + 1; i++)
{
dp[i] = dp[i - 1] + dp[i - 2];
}
int result = dp[n];
delete[] dp;
return result;
}
注意点:
1,动态规划设置数组大小
在c语言里你不能用dp[n]来声明一个大小为n的数组。因为数组的大小是在程序运行前就已经确定好了的,所以只能手动设置大小。有两种方法:new 和malloc。
2,数组大小设置为n+1
在斐波那契数列的动态规划实现中,你必须声明一个大小为 n+1
的数组,而不能直接声明大小为 n
的数组。原因如下:
1). 数组下标从 0 开始
数组的下标是从 0 开始的,因此如果你声明一个大小为 n
的数组,它的有效下标范围是 0
到 n-1
,没有包含 n
这个位置。而为了存储第 n
个斐波那契数,你需要一个包含 dp[n]
的数组元素。因此,数组的大小应该为 n+1
,以确保能够存储到 dp[n]
。
2). 存储斐波那契数列的所有元素
在动态规划中,你需要存储从 F(0)
到 F(n)
的所有值,以便逐步计算每个值。如果你只分配了 n
个位置,那么 dp[n]
这一位置会超出数组的范围,导致访问越界错误。
举例:
假设 n = 5
,你需要存储 dp[0]
到 dp[5]
,总共 6 个元素。如果你只分配了一个大小为 n = 5
的数组,那么该数组的有效下标是 0, 1, 2, 3, 4
,而你需要 dp[5]
,这就会访问越界,从而导致错误。
3,结果溢出
如果最后计算出来的结果超过了 int
类型所能表示的范围,通常会出现 整数溢出(integer overflow)。在这种情况下,计算结果会“回绕”,导致存储在 int
变量中的结果变成一个非常大的负数。这是因为 int
类型有一个固定的取值范围,如果超出了这个范围,它会从最小值重新开始,表现为负数。
int
类型的范围
在大多数平台(例如,32 位系统)上,int
类型的取值范围通常是:
- 最小值:
-2,147,483,648
(即 −231−231) - 最大值:
2,147,483,647
(即 231−1231−1)
如果计算结果超出了这个范围,C 语言没有内建的检查机制来防止溢出,而是继续执行,导致溢出行为,并将结果存储在 int
类型的范围内。
举个例子:
假设你的系统上 int
的范围是 -2,147,483,648
到 2,147,483,647
。如果你计算一个很大的斐波那契数(例如第 50 个斐波那契数,12586269025
),它明显超出了 int
的范围,结果会变成负数,导致输出类似于 -2147483647
或其他类似的负数值。
为什么会显示负数?
如果结果超出了 int
能存储的最大值,计算机会“回绕”到最小值,继续在负数范围内计算,最终显示的是一个负数。实际的计算过程如下:
- 计算出一个超出范围的值。
- 由于
int
的最大范围为2,147,483,647
,当结果大于这个值时,数值会“回绕”到负数范围,得到一个负值。
c,空间优化的动态规划
1,原理
类似链表更新节点,我们可以发现求斐波那契值只涉及到前面两个数字,所以使用三个局部变量来实现更新。
2,代码展示
int Fibonacci(int n)
{
if (n == 1 || n == 2) return 1;
int current = 0, prev1 = 1, prev2 = 1;
for (int i = 3; i < n + 1; i++)
{
current = prev1 + prev2;
prev1 = prev2;
prev2 = current;
}
return current;
}
相比于动态规划,这个方法极大地节省空间,减低空间复杂度。
d,矩阵快速幂
涉及到矩阵计算,有点复杂,需要前置知识。
1,原理
2,代码展示
a,矩阵乘法
补充:矩阵怎么作为参数传参
- 使用指针:最常见的方式,适用于动态数组或不确定大小的数组。
- 使用数组类型声明:当数组的大小已知时(如固定行列数),可以直接在函数签名中声明数组的尺寸。
- 指向指针的指针:处理动态二维数组或不规则二维数组时非常有用。
typedef
定义类型:为二维数组定义新的类型,代码更加简洁和可读。
void matrixmultiply(int F[2][2], int M[2][2])
{
int x = F[0][0] * M[0][0] + F[0][1] * M[1][0];
int y = F[0][0] * M[0][1] + F[0][1] * M[1][1];
int z = F[1][0] * M[0][0] + F[1][1] * M[1][0];
int w = F[1][0] * M[0][1] + F[1][1] * M[1][1];
F[0][0] = x;
F[0][1] = y;
F[1][0] = z;
F[1][1] = w;
}
这是对于两行两列矩阵。
b,矩阵的幂
void matrixpowder(int F[2][2], int n)
{
if (n == 0 || n == 1) return;//when n is 0 or 1,the matrix dont need processing
int M[2][2] = { {1,1},{1,0} };
matrixpowder(F, n / 2);
matrixmultiply(F, F);
if (n % 2 != 0) matrixmultiply(F, M);
}
这里用到了递归算法
接下来是解释这串代码:
首先传入的参数F,必须是用于计算斐波那契数列的标准矩阵。数学原理如下:
即求F(n)就是算M的n-1次幂后的矩阵(假设为A)的第一行第一列元素。
然后是if语句判断,当n=1或0时,0是单位矩阵,1是其本身,不做处理。
matrixpowder(F, n / 2);
这行是利用二分法求高幂次的矩阵,对于偶数次幂,二分求F的n/2次幂。一直二分直至n=1或0.后面有矩阵乘法。
matrixmultiply(F, F);
注意这里的F不再是传入的F,而是二分后的F,所以算的是计算矩阵 Fn=Fn/2×Fn/2Fn=Fn/2×Fn/2
最后的代码是用于处理幂是奇数,即
- 对于奇数 n,Fn=Fn−1×Fn=Fn−1×F,即矩阵的 n 次方可以通过先计算 Fn−1 再乘以 F 来得到。
- 这里,
matrixMultiply(F, M)
用于将矩阵 F 再乘上矩阵 M,从而补偿掉少计算的一次矩阵乘法。- 如果 n=5,首先计算 F2 并将其平方得到 F4,然后再与 M 相乘得到 F5。
完整代码展示
#include<iostream>
using namespace std;
void matrixmultiply(int F[2][2], int M[2][2]);
void matrixpowder(int F[2][2], int);
int Fibonacci(int);
int main()
{
cout << Fibonacci(10) << endl;
return 0;
}
void matrixmultiply(int F[2][2], int M[2][2])
{
int x = F[0][0] * M[0][0] + F[0][1] * M[1][0];
int y = F[0][0] * M[0][1] + F[0][1] * M[1][1];
int z = F[1][0] * M[0][0] + F[1][1] * M[1][0];
int w = F[1][0] * M[0][1] + F[1][1] * M[1][1];
F[0][0] = x;
F[0][1] = y;
F[1][0] = z;
F[1][1] = w;
}
void matrixpowder(int F[2][2], int n)
{
if (n == 0 || n == 1) return;//when n is 0 or 1,the matrix dont need processing
int M[2][2] = { {1,1},{1,0} };
matrixpowder(F, n / 2);
matrixmultiply(F, F);
if (n % 2 != 0) matrixmultiply(F, M);
}
int Fibonacci(int n)
{
if (n == 1 || n == 2) return 1;
int F[2][2] = { {1,1},{1,0} };
// the standard matrix for calculating the power of matrix
matrixpowder(F, n - 1);
return F[0][0];
}
优点:
- 时间复杂度:O(logn)O(logn)(通过快速幂)
- 空间复杂度:O(1)O(1)
e,黄金分割法