要实现所谓的Fibonacci查找,首先来实现Fibonacci数列类。
Fibonacci数列最早由数学家Leonardoda Fibonacci提出,它指的是一种满足如下递推关系的数列:
直观印象
根据此定义,可以直接利用二分递归计算第1
2
3
4
5_int64 fib(int n)
{
return (2>n)?
(_int64) n : fib(n-1)+fib(n-2);
}
这一实现的正确性显而易见,而且代码简洁自然。不幸的是,它的效率十分低下,时间复杂度高达指数形式。
为确切界定该算法的复杂度,不妨将计算
所需的时间记作 ,可得 递推式如下: 若令
,则有: 我们发现,
的递推形式与 完全一致,只是起始项不同: 亦即,
整体上相对于F(n)提前了一个单元。由此可知:
优化策略
为了消除递归算法中重复的递归实例,一种自然的思路是:借助一定量的辅助空间,在各子问题求解之后,及时记录保存下来。
比如,可以从原问题出发自顶而下,每遇到一个子问题,都首先检查它是否计算过,以期通过直接调阅记录获得解答,从而避免直接计算。也可以从递归基出发,自底而上递推得出各子问题的解,直至最终原问题的解。前者即所谓的制表(tabulation)或记忆(memoization)策略,后者即所谓的动态规划(dynamic programming)策略。
线性递归
为应用上述制表的策略,首先需从改造
原
即可得到效率更高的线性递归版1
2
3
4
5
6
7
8
9_int64 fib (int n,_int64& prev)
{
if (n == 0) {prev = 1; return 0;} //到达递归基,直接取值:fib(-1) = 1, fib(0) = 0
else
{
_int64 prevPrev; prev = fib(n-1,prevPrev); //递归计算前两项
return prevPrev + prev; //其和即为正解
}
}
原二分递归版本中对应于
该算法呈线性递归模式,递归的深度线性正比于输入
迭代
反观以上线性递归版
若将以上逐层返回的过程,等效地视作从递归基出发,按规模自小而大求解各子问题的过程,即刻采用动态规划的策略,将以上算法进一步改写为迭代版。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class fib
{
private:
_int64 f,g;
public:
fib(_int64 n) //初始化为不小于n的最小fibonacci项
{··
f=1;
g=0;
while(g<n) next();
}
_int64 get() //获取当前Fibonacci数
{
return g;
}
void next() //转入下一项
{
g+=f;
f=g-f;
return g;
}
void prev() //转入上一项
{
f=g-f;
g-=f;
return g;
}
};
这里仅使用了两个中间变量