时间复杂度:
O
(
1
)
<
O
(
l
o
g
n
)
<
O
(
n
l
o
g
n
)
<
O
(
n
2
)
<
O
(
n
3
)
<
O
(
2
n
)
<
O
(
n
!
)
<
O
(
n
n
)
O(1)<O(logn)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)
O(1)<O(logn)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
兔子数列
假设第1个月有1对刚诞生的兔子,第2个月进入成熟期,第3个月开始生育兔子,而1对成熟的兔子每月会生1对兔子,兔子永远不死去……那么,由1对初生兔子开始,12个月后会有多少对兔子?
问题分析
太常见的题目了,就不一一每月举例分析了。
这个数列有明显的特点,从第3个月开始,当月的兔子数=上月兔子数+当月新生兔子数,而当月新生的兔子正好是上上月的兔子数。因此,易得
当月的兔子数=上月兔子数+上上月的兔子数
斐波那契数列如下:1,1,2,3,5,8,13,21,34,……
递归表达式:
F
(
n
)
=
{
1
,
n=1
1
,
n=2
F
(
n
−
1
)
+
F
(
n
−
2
)
,
n>2
F(n) = \begin{cases} 1, & \text{n=1} \\[2ex] 1, & \text{n=2} \\[2ex] F(n-1)+F(n-2), & \text{n>2} \end{cases}
F(n)=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧1,1,F(n−1)+F(n−2),n=1n=2n>2
算法1(按照递归表达式设计):
long long Fib1(int n)
{
if( n<1 ) return -1;
if( n==1 || n==2 ) return 1;
return Fib1(n-1)+Fib1(n-2);
}
算法复杂度分析:
假设T(n)表示计算Fib1(n)所需要的基本操作次数,那么:
n=1时,T(n)=1;
n=2时,T(n)=1;
n=3时,T(n)=3;//调用Fib1(2)、Fib1(1)和执行一次加法运算Fib1(2)+Fib1(1)
n>2时,T(n)=T(n-1)+T(n-2)+1;
递归表达式和时间复杂度T(n)之间的关系如下:
F
(
n
)
=
{
1
,
n=1,T(n)=1
1
,
n=2,T(n)=1
F
(
n
−
1
)
+
F
(
n
−
2
)
,
n>2,T(n)=T(n-1)+T(n-2)+1
F(n) = \begin{cases} 1, & \text{n=1,T(n)=1} \\[2ex] 1, & \text{n=2,T(n)=1} \\[2ex] F(n-1)+F(n-2), & \text{n>2,T(n)=T(n-1)+T(n-2)+1} \end{cases}
F(n)=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧1,1,F(n−1)+F(n−2),n=1,T(n)=1n=2,T(n)=1n>2,T(n)=T(n-1)+T(n-2)+1
由此可得:T(n)>=F(n)
又斐波那契数列通项为:
F
(
n
)
=
1
5
(
(
1
+
5
2
)
n
−
(
1
−
5
2
)
n
)
F(n)=\frac {1}{\sqrt5}((\frac{1+\sqrt5}{2})^n-(\frac{1-\sqrt5}{2})^n)
F(n)=51((21+5)n−(21−5)n)
当n趋近于无穷时, F ( n ) ≈ 1 5 ( 1 + 5 2 ) n F(n)\approx\frac {1}{\sqrt5}(\frac{1+\sqrt5}{2})^n F(n)≈51(21+5)n
发现这里的时间复杂度属于爆炸增量函数
算法2(记录前两项的值):
long long Fib2(int n)
{
if( n<1 ) return -1;
int * a = new int[n+1];//定义一个长度为n+1的数组,0空间未使用
a[1] = 1;
a[2] = 1;
for(int i=3;i<=n;++i)
a[i] = a[i-1]+a[i-2];
return a[n];
}
算法复杂度分析:
时间复杂度为O(n),从原来的指数阶降到了多项式阶
空间复杂度为O(n)
但我们需要得到第n个斐波那契数,中间结果只是为了下一次使用,根本不需要记录。因此,我们可以在改进算法。
算法3(迭代法):
long long Fib3(int n)
{
int s1,s2;
if( n<1 ) return -1;
if( n==1 || n==2 ) return 1;
s1 = 1;
s2 = 1;
for(int i=3;i<=n;++i)
{
s2 += s1;//辗转相加法
s1 = s2-s1;//记录前一项
}
return s2;
}
初始值: s 1 = 1 ; s 2 = 1 ; s_1=1;s_2=1; s1=1;s2=1; | 当前解 | 记录前一项 |
---|---|---|
i=3时 | s 2 = s 1 + s 2 = 2 s_2=s_1+s_2=2 s2=s1+s2=2 | s 1 = s 2 − s 1 = 1 s_1=s_2-s_1=1 s1=s2−s1=1 |
i=4时 | s 2 = s 1 + s 2 = 3 s_2=s_1+s_2=3 s2=s1+s2=3 | s 1 = s 2 − s 1 = 2 s_1=s_2-s_1=2 s1=s2−s1=2 |
i=5时 | s 2 = s 1 + s 2 = 5 s_2=s_1+s_2=5 s2=s1+s2=5 | s 1 = s 2 − s 1 = 3 s_1=s_2-s_1=3 s1=s2−s1=3 |
i=6时 | s 2 = s 1 + s 2 = 8 s_2=s_1+s_2=8 s2=s1+s2=8 | s 1 = s 2 − s 1 = 5 s_1=s_2-s_1=5 s1=s2−s1=5 |
…… | …… | …… |
算法使用了若干个辅助变量,迭代辗转相加,每次记录前一项,时间复杂度为O(n),但空间复杂度降到了O(1)。
但其实斐波那契数列时间复杂度还可以降到对数阶O(logn)。
算法4(利用矩阵的乘法):
数学基础推导先写在这里,由于博主本人代码能力还不过关,后期会补上矩阵代码:
(
f
(
n
)
f
(
n
−
1
)
)
=
(
1
1
1
0
)
(
f
(
n
−
1
)
f
(
n
−
2
)
)
=
…
…
=
(
1
1
1
0
)
n
−
2
(
f
(
2
)
f
(
1
)
)
\begin{pmatrix} f(n) \\ f(n-1) \\ \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \\ \end{pmatrix} \begin{pmatrix} f(n-1) \\ f(n-2) \\ \end{pmatrix} =……= \begin{pmatrix} 1 & 1 \\ 1 & 0 \\ \end{pmatrix}^{n-2} \begin{pmatrix} f(2) \\ f(1) \\ \end{pmatrix}
(f(n)f(n−1))=(1110)(f(n−1)f(n−2))=……=(1110)n−2(f(2)f(1))
计算f(n)即计算
(
1
1
1
0
)
n
−
2
\begin{pmatrix} 1 & 1 \\ 1 & 0 \\ \end{pmatrix}^{n-2}
(1110)n−2,而计算矩阵的(n-2)次方,又可以进行分解,即计算
(
1
1
1
0
)
n
−
2
2
\begin{pmatrix} 1 & 1 \\ 1 & 0 \\ \end{pmatrix}^\frac{n-2}{2}
(1110)2n−2的平方,逐步分解下去;由于折半计算矩阵的次方,因而时间复杂度为O(logn) 。
参考文献:
[1] 陈小玉.趣学算法[M].北京:人民邮电出版社,2017:11-14.
[2] beautyofmath.关于斐波那契数列三种解法及时间复杂度分析[EB/OL].https://blog.csdn.net/beautyofmath/article/details/48184331, 2015-09-02/2019-02-01.
[2] ECJTU_ACM_余伟伟.斐波那契数列算法及时间复杂度分析[EB/OL].https://blog.csdn.net/ecjtu_yuweiwei/article/details/47282457, 2015-08-04/2019-02-01.