一、题目要求
利用循环队列编写k阶斐波那序列的前k+1项(f0,f1,f2……fn)的算法,要求满足:fn <=max而fn+1 >max,其中max为某个约定的常数。所用循环队列的容量仅为k,则在算法执行结束时,留在循环队列中的元素应是所求k阶斐波那契序列中的最后k项fn-k+1, …, fn。
二、分析
首先回顾一下k阶斐波那序列的定义:
f0 = 0, f1 = 0, … , fk-2 = 0, fk-1 = 1;
fn = fn-1 + fn-2 + … + fn-k , n = k , k + 1, …
说白了就是每一项都是前k项之和,同时定义数列前f0-fk-2项为0,fk-1为1。即k阶斐波那序列从第fk-1项开始非0。
因为最后要求在队列里保留阶斐波那契序列中的最后k项,所以对队列的操作函数进行了修改了,增加了参数MAXQSIZE来控制队列的容量。
例如队列的EnQueue函数:
Status EnQueue(SqQueue &Q,QElemType e,int MAXQSIZE) //在队尾插入元素
{
if((Q.rear+1)%(MAXQSIZE+1)==Q.front) return ERROR;//队列满
Q.base[Q.rear]=e;
Q.rear=(Q.rear+1)%(MAXQSIZE+1);
return OK;
}
值得注意的是在初始化函数中申请的空间是MAXQSIZE+1,多一个空间用于判断队列是否满了。因为队列在满和空的情况下都有Q.front=Q.rear。现在多以空间,判断队列是否空只用判断Q.rear-Q.front+MAXQSIZE+1)%(MAXQSIZE+1)的值即可。
队列初始化函数:
Status InitQueue(SqQueue &Q,int MAXQSIZE)
{
Q.base=(QElemType*)malloc((MAXQSIZE+1)*sizeof(QElemType));
if(!Q.base) return ERROR;
Q.front=Q.rear=0;
return OK;
}
队列长度函数:
int QueueLength(SqQueue Q,int MAXQSIZE)
{
return(Q.rear-Q.front+MAXQSIZE+1)%(MAXQSIZE+1);
}
队列的操作函数在后面的完整代码里都有,这里不一一列出了。
解决了基本队列操作函数后,就可以开始了。
前面提到过,每一项都是前k项之和,同时定义数列前f0-fk-2项为0,fk-1为1。
最初的思路:只需要在生成队列前k项后,将前k项加在一起得到第k+1项,然后讲队列中第一项出队,将第k+1项在队尾入队。
核心算法如下:
Status Fibonacci(int k,int max) //3-32 循环队列计算k阶斐波那契数列前n+1项,Fn<=max,Fn+1>max
{
if(k<1||max<0) return ERROR;
if(k==1)
{
printf("数列恒为1,找不到符合要求的项\n");
return ERROR;
}
SqQueue Q;
int MAXQSIZE=k;
InitQueue(Q,MAXQSIZE);
int n=k;
for(int i=0;i<k-1;i++)
{
EnQueue(Q,0,MAXQSIZE);
printf("第f%d项是0\n",i);
}
EnQueue(Q,1,MAXQSIZE);
printf("第f%d项是1\n",MAXQSIZE);
int temp,e;
int j;
for(int i=Q.front,j=0;j<QueueLength(Q,k+1);j++,i=(i+1)%(k+1));
temp=temp+Q.base[i];
while(temp<=max)
{
DeQueue(Q,e,MAXQSIZE);
EnQueue(Q,temp,MAXQSIZE);
printf("第f%d项是%d\n",n,temp);
for(int i=Q.front, j=0;j<QueueLength(Q,k+1);j++,i=(i+1)%(k+1)) temp=temp+Q.base[i];
n++;
}
printf("第f%d项是%d\n",n,temp);
printf("题中要求的第n项为第%d项",n-1);
return OK;
}
需要修改的地方:
我们注意到,在while(temp<=max)前面将是生成队列f0至fk-1项遍历相加,这是不科学的。因为根据定义,数列f0-fk-2项为0,fk-1==1,所以可以删去22-23行的for循环,直接令temp=1;
同时,在后续的操作中,第29行for循环依然是将队列中的所有数相加求和,在k较大的情况下还是很浪费时间的。因此可以利用该数列的一个递推公式进行修改(请读者自行推导):
f(n)=2f(n-1)-f(n-k-1)
在k=3, max=100000000 本函数执行次数 N=1000000,修改后减少两个for循环使用递推的函数用时0.72s,修改前用时1.88s,平均节约一半多的时间。
三、完整代码
经过修改后,完整代码如下。
#include<stdio.h>
#include<iostream>
#include<math.h>
#include<time.h>
#include<stdlib.h>
#define Status int
#define OK 1
#define ERROR -1
//#define OVERFLOW -1
#include <malloc.h>
#include<string.h>
//#define MAXQSIZE 100
#define QElemType int
using namespace std; //双向循环队列,为了满足2-32题目需求,所有队列操作函数需要输入队列MAXQSIZE,实际是MAXQSIZE+1,多一个用于判断队列是否满
typedef struct
{
QElemType *base;
int front;
int rear;
}SqQueue;
Status InitQueue(SqQueue &Q,int MAXQSIZE)
{
Q.base=(QElemType*)malloc((MAXQSIZE+1)*sizeof(QElemType));
if(!Q.base) return ERROR;
Q.front=Q.rear=0;
return OK;
}
int QueueLength(SqQueue Q,int MAXQSIZE)
{
return(Q.rear-Q.front+MAXQSIZE+1)%(MAXQSIZE+1);
}
Status EnQueue(SqQueue &Q,QElemType e,int MAXQSIZE) //在队尾插入元素
{
if((Q.rear+1)%(MAXQSIZE+1)==Q.front) return ERROR;//队列满
Q.base[Q.rear]=e;
Q.rear=(Q.rear+1)%(MAXQSIZE+1);
return OK;
}
Status DeQueue(SqQueue &Q,QElemType &e,int MAXQSIZE) //删除队头元素
{
if(Q.front==Q.rear) return ERROR;//队列空
e=Q.base[Q.front];
Q.front=(Q.front+1)%(MAXQSIZE+1);
return OK;
}
Status PrintQueue(SqQueue Q,int MAXQSIZE)
{
int length=QueueLength(Q,MAXQSIZE);
if(Q.front==Q.rear) return ERROR;//队列空
int j;
for(int i=Q.front,j=0;j<length;j++,i=(i+1)%(MAXQSIZE+1))
printf("%d",Q.base[i]);
}
Status Fibonacci(int k,int max) //3-32 循环队列计算k阶斐波那契数列前n+1项,Fn<=max,Fn+1>max
{
if(k<1||max<0) return ERROR;
if(k==1)
{
printf("数列恒为1,找不到符合要求的项\n");
return ERROR;
}
SqQueue Q;
int MAXQSIZE=k;
InitQueue(Q,MAXQSIZE);
int n=k;
for(int i=0;i<k-1;i++)
{
EnQueue(Q,0,MAXQSIZE);
printf("第f%d项是0\n",i);
}
EnQueue(Q,1,MAXQSIZE);
printf("第f%d项是1\n",MAXQSIZE);
int temp,e;
// int j;
// for(int i=Q.front,j=0;j<QueueLength(Q,k+1);j++,i=(i+1)%(k+1)) 这两行也是效率低下的,此时队列中有k-1个0和一个1,没必要遍历求和,直接令temp=1;
// temp=temp+Q.base[i];
temp=1;
while(temp<=max)
{
DeQueue(Q,e,MAXQSIZE);
EnQueue(Q,temp,MAXQSIZE);
printf("第f%d项是%d\n",n,temp);
temp=2*temp-e; //与下一行算法相比,这里运用简单地推公式f(n)=2f(n-1)-f(n-k-1) 会更加高效
//在k=3, max=100000000 本函数执行次数 N=1000000下,递推用时0.72s,for循环用时1.88s,平均节约一半多的时间
// for(int i=Q.front, j=0;j<QueueLength(Q,k+1);j++,i=(i+1)%(k+1)) temp=temp+Q.base[i];
n++;
}
printf("第f%d项是%d\n",n,temp);
printf("题中要求的第n项为第%d项",n-1);
return OK;
}
int main()
{
/* 队列操作函数验证代码
SqQueue Q;
int e;
int MAXQSIZE=100;
InitQueue(Q,MAXQSIZE);
for(int i=0;i<=5;i++) EnQueue(Q,i,MAXQSIZE);
PrintQueue(Q,MAXQSIZE);
*/
/*// 3-32效率验证代码
int N=10000000;
clock_t start, stop;
double duration;
start=clock();int k=20;
int max=100000000;
for(int i=0;i<N;i++) Fibonacci( k,max);//my function 循环多次计算,不然时间太短测不出来
stop=clock();
duration=((double)(stop-start))/CLK_TCK;
printf("算法花费时间为%f\n",duration);
*/
int k=3;
int max=30;
Fibonacci( k,max);
}