斐波那契问题
斐波那契数列,指的是这样一个数列:1、1、2、3、5、8、13、21...,除第1,2位的数为1外,其他数为前两位数字的相加之和。
1.斐波那契数列与经典兔子繁殖问题
一般而言,兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔都不死,那么在有1个月大的一对兔子的条件下一年以后可以繁殖多少对兔子?
我们可以分析一下。
第一个月一对兔子A生出一对小兔B。
第二个月还是原来的一对兔子A生出一对小兔C。
第三个月A生出一对小兔D,B生出一对小兔E。
第四个月A生出一对小兔F,B生出一对小兔G,C生出一对小兔H。
依次类推......如下表所示:
月份 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
每个月新出生的兔子对数 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 | 89 | 144 |
一年后繁殖的兔子对数量为:1+1+2+3+5+8+13+21+34+55+89+144
2.C语言使用递归来实现斐波纳契数列
long fibonacci(int n);
#include <stdio.h>
#include <stdlib.h>
#include "fibonacci.h"
int main(void)
{
int n;
long result;
scanf("%d",&n);
result=fibonacci(n);
printf("%ld",result);
return EXIT_SUCCESS;
}
long fibonacci(int n)
{
if( n>0 && n<=2 )
return 1;
else
return fibonacci(n-1)+fibonacci(n-2);
}
递归带来的好处是使算法变得更加简明,可读性高。然而,使用递归也带来了额外的内存开销.可以考虑使用迭代实现斐波那契。
3.C语言使用迭代来实现斐波那契数列
#include <stdio.h>
#include <stdlib.h>
#include "fibonacci.h"
int main(void)
{
int n;
long result;
scanf("%d",&n);
result=fibonacci(n);
printf("%ld",result);
return EXIT_SUCCESS;
}
long fibonacci(int n)
{
long result,previous_result,temp;
result = previous_result =1;
while(n>2)
{
temp = result;
result += previous_result;
previous_result = temp;
n -= 1;
}
return result;
}
原先自己的第一个思路是使用一个long数组存放斐波那契数列,在最后的第n位再把前两项的数字相加返。但这样做的话会浪费掉很多用不到的数组内存,所以改进为使用3个变量,一个记录前一个结果,一个记录当前结果,一个做为中间的temp变量,作为一个交换的介体。
4.斐波那契查找
斐波那契数列又称为黄金分割数列。为什么叫做黄金分割数列呢?理由是当n趋近于无穷大的时候,后一项和前一项的比值越来越接近黄金分割1.618. 3/2=1.5,5/3=1.666,8/5=1.6,13/8=1.625...
数学背景:数字0.618…更为数学家所关注,它的出现,不仅解决了许多数学难题(如:十等分、五等分圆周;求18度、36度角的正弦、余弦值等),而且还使优选法成为可能。优选法是一种求最优化问题的方法。如在炼钢时需要加入某种化学元素来增加钢材的强度,假设已知在每吨钢中需加某化学元素的量在1000—2000克之间,为了求得最恰当的加入量,需要在1000克与2000克这个区间中进行试验。通常是取区间的中点(即1500克)作试验。然后将试验结果分别与1000克和2000克时的实验结果作比较,从中选取强度较高的两点作为新的区间,再取新区间的中点做试验,再比较端点,依次下去,直到取得最理想的结果。这种实验法称为对分法。但这种方法并不是最快的实验方法,如果将实验点取在区间的0.618处,那么实验的次数将大大减少。这种取区间的0.618处作为试验点的方法就是一维的优选法,也称0.618法。实践证明,对于一个因素的问题,用“0.618法”做16次试验就可以完成“对分法”做2500次试验所达到的效果。因此大画家达·芬奇把0.618…称为黄金数。
#include <stdio.h>
#define MAX_SIZE 30
int fibonacci(int n);
int main (void)
{
/*准备工作*/
int n,i,key;
printf("Please input n:");
scanf("%d",&n);
int list[MAX_SIZE];
printf("Please input your sorted array:");
for(i=1 ; i<=n ;i++)
{
scanf("%d",&list[i]);
}
printf("Please input the key:");
scanf("%d",&key);
/*比较找到不小于n的斐波那契数的位置*/
int k = 1;
while(n>fibonacci(k))
{
k++;
}
/*在n的个数不足找到的斐波那契数,用list【n】补齐*/
for(i=n+1; i<=fibonacci(k); i++)
{
list[i]=list[n];
}
/*定义key的位置*/
int pos = -1;
int low,high,mid;
low = 1;
high = n;
while(low <= high)
{
mid =low + fibonacci(k-1) -1;
if(key < list[mid])
{
high=mid - 1;
k = k-1;
}
else if(key > list[mid])
{
low=mid + 1;
k = k-2;
}
else
{
if(mid<n)
{
pos = mid;
break;
}
else
{
pos = n;
break;
}
}
}
printf("The result position is %d.",pos);
return 0;
}
int fibonacci(int n)
{
int result,previous_result,temp;
result = previous_result = 1;
while(n>2)
{
temp=result;
result+=previous_result;
previous_result=result;
n--;
}
return result;
}
对斐波那契查找可能存在两个比较难理解的地方。
(1)
/*在n的个数不足找到的斐波那契数,用list【n】补齐*/
for(i=n+1; i<=fibonacci(k); i++)
{
list[i]=list[n];
}
由于斐波那契查找是基于斐波那契数列的查找,要求有序表的个数必须是存在于斐波那契数列中的数,所以在数不足的情况下,如n=10,fibonacci(7)=13 > 10,必须将有序表的个数扩增到等于斐波那契数,则list[11]=list[12]=list[13]=list[n]。
(2)
if(key < list[mid])
{
high=mid - 1;
k = k-1;
}
else if(key > list[mid])
{
low=mid + 1;
k = k-2;
}
在key<list[mid] 的情况下 k=k-1,ket > list [mid] 的情况下 k=k-2。这又是为什么呢?
对于斐波那契查找,分割是从mid =low + fibonacci(k-1) -1 开始的,现在数组的长度为fibonacci(k),mid 将数组分为两个部分,前一部分为[1,mid],长度为fiboncaai(k-1),则后一部分的长度为fibonacci(k)-fibonacci(k-1),根据斐波那契的性质,f(k) - f(k-1) = f(k-2),则后一部分的长度为fibonacci(k-2),所以当key > list[mid] 的时候 k = k - 2。
斐波那契的时间复杂度也为O(log n),相对与择半查找进行加法与除法运算(mid=(low + high)/2 ),斐波那契查找只是最简单加减法运算( mid = low + fibonacci(k-1) -1 ),在海量数据的查找过程中,这种细微的差别可能会影响最终的查找效率。