斐波那契数列,又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用。
在解决斐波那契算法的探索过程中,前人总结出了四种时间复杂度越来越精确的算法。其中包括递归全项、求前两项、通项公式、矩阵乘法四种方式。其中矩阵乘法+最小二乘法解决的方式是目前公认的可省略手工计算最优算法,时间复杂度为log(n)。相比于递归全项的算法是指数型爆炸式增长,求前两项也是正比例函数的时间复杂度而言,这样强大的算法优化的确振奋人心!
以下为详解四大基本算法的C++代码:
// 第一章 数据结构入门.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。编写—JoeyBG
//
#include
#include
#include
#include
using namespace std;
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
// 入门使用技巧:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
/*
第一章 数据结构入门:斐波那契数列的实现程序
以下分别使用最简单的全项相加的模式、存储前面两项的模式以及计算通项公式的模式进行程序编写
各算法分别对应斐波那契计算函数1、2、3
程序会同时给出有关算法运算次数(时间复杂度的参考)
*/
int fib1(int n)
{
if (n > 100)
{
cout << "计算难度超出时间限额,程序将自动停止并退出!" << endl;//超时退出,点击鼠标清屏
getchar();
getchar();
exit(0);
}
int time = 0;
if (n < 1)
{
time++;//此时时间复杂度为1或递归终止总次数,直接退出
return -1;
}
if (n == 1 || n == 2)
{
return 1;
}
return (fib1(n - 1) + fib1(n - 2));//斐波那契的递推公式
}//递推方法建立斐波那契数列全项计算函数,时间复杂度为爆炸式指数增长,经试验,超出100的n值就需要从程序开始一直计算到电脑报废
int fib2(int n)
{
int i = 0;
int a1 = 1;
int time = 0;
int a2 = 1;
int result = 0;
if (n == 1 || n == 2)
{
return 1;
}
else
{
for (i = 0; i < n - 2; i++)
{
result = a1 + a2;
a1 = a2;
a2 = result;
time++;//通过计算得到时间复杂度
if (time > 10000)
{
cout << "计算次数超出时间限额,程序将自动停止并退出!" << endl;//超时退出,点击鼠标清屏
getchar();
getchar();
exit(0);
}
}
}
return result;
}//仅记录前两项数据并求和方法下的函数调用方法,如果计算次数超过10000会直接退出,时间复杂度O(n)
int fib3(int n)
{
int i = 0;
int time =1;//不必计算,直接得到时间复杂度
double result = (pow((sqrt(5) + 1) / 2, n) - pow((1 - sqrt(5)) / 2, n)) / sqrt(5);//斐波那契数列的通项公式
return (int)result;
}//仅记录计算次数n的值并且带入通项公式的方法,时间复杂度O(1),因而不必考虑超出100or10000次退出
class Matrix
{
public:
long matr[2][2];
Matrix(const Matrix& rhs);
Matrix(long a, long b, long c, long d);
Matrix& operator=(const Matrix&);
friend Matrix operator*(const Matrix& lhs, const Matrix& rhs)
{
Matrix ret(0, 0, 0, 0);
ret.matr[0][0] = lhs.matr[0][0] * rhs.matr[0][0] + lhs.matr[0][1] * rhs.matr[1][0];
ret.matr[0][1] = lhs.matr[0][0] * rhs.matr[0][1] + lhs.matr[0][1] * rhs.matr[1][1];
ret.matr[1][0] = lhs.matr[1][0] * rhs.matr[0][0] + lhs.matr[1][1] * rhs.matr[1][0];
ret.matr[1][1] = lhs.matr[1][0] * rhs.matr[0][1] + lhs.matr[1][1] * rhs.matr[1][1];
return ret;
}
};
Matrix::Matrix(long a, long b, long c, long d)
{
this->matr[0][0] = a;
this->matr[0][1] = b;
this->matr[1][0] = c;
this->matr[1][1] = d;
}
Matrix::Matrix(const Matrix& rhs)
{
this->matr[0][0] = rhs.matr[0][0];
this->matr[0][1] = rhs.matr[0][1];
this->matr[1][0] = rhs.matr[1][0];
this->matr[1][1] = rhs.matr[1][1];
}
Matrix& Matrix::operator =(const Matrix& rhs)
{
this->matr[0][0] = rhs.matr[0][0];
this->matr[0][1] = rhs.matr[0][1];
this->matr[1][0] = rhs.matr[1][0];
this->matr[1][1] = rhs.matr[1][1];
return *this;
}
Matrix power(const Matrix& m, int n)
{
if (n == 1)
return m;
if (n % 2 == 0)
return power(m * m, n / 2);
else
return power(m * m, n / 2) * m;
}
long fib4(int n)
{
if (n > 10000000)
{
cout << "计算次数超出时间限额,程序将自动停止并退出!" << endl;//超时退出,点击鼠标清屏
getchar();
getchar();
exit(0);
}
if (n == 1)
{
return 1;
}
else
{
Matrix matrix0(1, 1, 1, 0);
matrix0 = power(matrix0, n - 1);
return matrix0.matr[0][0];
}
}//利用矩阵乘法实现的斐波那契数列求解,并结合数值计算方法中给出的矩阵乘法的二分法迭代方式,此程序的时间复杂度是O(log(n)),计算次数超出[log(10m)]次就自动退出
/*
以下详细解释矩阵乘法的实现原理:
我们将数列写成:
Fibonacci[0] = 0,Fibonacci[1] = 1
Fibonacci[n] = Fibonacci[n-1] + Fibonacci[n-2] (n >= 2)
可以将它写成矩阵乘法形式:
[F(n) ] [1 1] [F(n-1)]
[F(n-1)]=[1 0]X[F(n-2)]
将右边连续的展开就得到:
[F(n) ] [1 1]^(n-1) [F(1)]
[F(n-1)]=[1 0] X[F(0)]
下面就是要用O(log(n))的算法计算:
[1 1]^(n-1)
[1 0]
显然用二分法来求,结合一些面向对象和数值计算的概念,C++代码不难给出如上fib4所示,仅需要添加矩阵乘法和二分法的计算迭代类即可
*/
//主函数如下
int main()
{
cout << "斐波那契数列四大经典算法的分析&学习程序,编写:JoeyBG。" << endl;
cout << "编写&调试:JoeyBG" << endl;
cout << "以下详尽阐释了四种不同算法的来源与复杂度分析,不足之处还望见谅!" << endl;
cout << "--------------------------------------------------------------------" << endl;
startlabel:
int n = 0;
char judge = '\n';
cout << "键入你所需要求得的斐波那契数列的项数n(n>0,且n不要超过100):";
cin >> n;//n的手工输入
if (n <= 0)
{
cout << "不支持计算斐波那契数列的非正数下标项!" << endl;
cout << "--------------------------------------------------------------------" << endl;
goto startlabel;
}//不支持广义斐波那契数列逆向计算,之后我会补上有关的算法空缺
cout << endl;
cout << "1、递推方法建立斐波那契数列全项计算结果为:";
cout << fib1(n) << endl;
cout << "时间复杂度为:";
cout << "O(((1+√5)/2)^" << n << ")" << ",也即O(((1+√5)/2)^n)" << endl;
cout << endl;
cout << "2、仅记录前两项数据并求和方法下的函数调用方法计算结果为:";
cout << fib2(n) << endl;
cout << "时间复杂度为:";
cout << "O(" << n << ")" <<",也即O(n)"<< endl;
cout << endl;
cout << "3、直接代公式法得到的计算结果为:";
cout << fib3(n) << endl;
cout << "时间复杂度为:";
cout << "O(1)"<< endl;
cout << endl;
cout << "4、利用矩阵乘法和二分法算法得到的结果为:";
cout << fib4(n) << endl;
cout << "时间复杂度为:";
cout << "O(log("<> judge;
judgelabel:
if (judge == 'Y' || judge == 'y')
{
system("cls");
goto startlabel;
}
else if (judge == 'N' || judge == 'n')
{
exit(0);
}
else
{
cout << "输入有误,请重新输入。" << endl;
goto judgelabel;
}//通过一个简单的goto语句判断是否继续进行下一项的计算,因此省去了循环语句调试的烦恼,但是严谨度稍有下降
return 0;
}//程序的主函数,目的是执行上面四个计算斐波那契数列项的函数,同时询问判断操作者是否还要继续计算
/*
参考文献:
1、CCBB:斐波那契数列算法分析,开发者知识库,2009.04
2、陈小玉:趣学数据结构,人民邮电出版社,2019.09
*/
在对应数据结构入门章节的北京理工大学徐特立学院非计算机类C++&数据结构课程中,有如下部分习题,解答一并奉上:
1.研究数据结构就是研究( )选择一项:
a. 数据的逻辑结构
b. 数据的逻辑结构、存储结构及其数据在运算上的实现
c. 数据的存储结构
2.数据的( )包括集合、栈、树和图结构4种基本类型。
选择一项:
a. 基本运算
b. 存储结构
c. 逻辑结构
d. 算法描述
3.与数据元素本身的形式、内容、相对位置、个数无关的是数据的( )
选择一项:
a. 逻辑结构
b. 存储实现
c. 运算实现
d. 存储结构
4.数据的( )包括查找、插入、删除、更新、排序等操作类型。
选择一项:
a. 逻辑结构
b. 存储结构
c. 算法描述
d. 基本运算
5.计算机中的算法是指解决某个问题的有限运算序列,它必须具备输入、输出、( )等5个特性。
选择一项:
a. 可行性、有穷性和确定性
b. 确定性、有穷性和稳定性
c. 易读性、稳定性和确定性
d. 可行性、可移植性和可扩充性
/*
题目&解答参考资料:
1、北京理工大学乐学教育平台
2、黑客入门必学——数据结构基础知识,http://baijiahao.baidu.com/s?id=1604864930515803970&wfr=spider&for=pc
*/
通过数据结构入门章节的基础知识, 我们不难总结以下几点:
1、数据:数据是信息的载体,它能够被计算机识别、存储和加工处理,是计算机程序加工的原料。
2、数据元素:数据元素是由若干个数据项组成,数据项是具有独立单位的最小识别单位。例如,一本书的数目信息为一个数据元素,而书目信息的每一项(如书名、作者名等)为一个数据项。数据项是数据的不可分割的最小单位。
3、数据对象:数据对象(Data Object)是性质相同的数据元素的集合。
4、数据结构:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。
5、存储结构:存储结构是数据结构在计算机中的表示。
6、数据类型:数据类型是一个值的集合和定义在这个值集上的一组操作的总称。
7、抽象数据类型:抽象数据类型是指一个数据模型一级定义在该模型上的一组操作,是对一般数据类型的扩展。
顺序存储方法把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由附加指针字段表示。顺序存储结构是一种最基本的存储表示方法,通常借助程序设计语言中的数组来实现。链式存储方法不要求逻辑上相邻的结点在物理位置上也相邻,结点间的逻辑关系是由附加的指针字段表示的。链式存储结构通常借助程序设计语言中的指针类型来实现。
对于一个算法来说,可行性、有穷性和确定性以及BIBO是比较关键的,与数据元素本身的形式、内容、相对位置、个数无关的是数据的逻辑结构,它与数据元素本身的形式、内容、相对位置、个数无关。综上不难得到问题的答案。