《logN的时间内算fibonacci number》
来源:互联网 发布:nginx php 优化 编辑:程序博客网 时间:2024/05/16 08:10
logN的时间内算fibonacci number
三、矩阵法
算Fibonacci数精确值的最快的方法应该就是矩阵法,看过的人都觉得这个方法很好。如果你跟我一样,曾经为记住这个方法中的矩阵而烦恼,那今天就来看看怎么进行推导。其实方法非常简单,想清楚了也就自然而然地记住了。
我们把Fibonacci数列中相邻的两项:F(n)和F(n - 1)写成一个2x1的矩阵,然后对其进行变形,看能得到什么:
[FnFn−1]=[Fn−1+Fn−2Fn−1]=[1×Fn−1+1×Fn−21×Fn−1+0×Fn−2]=[1110]×[Fn−1Fn−2]
是不是非常自然呢?把等式最右边继续算下去,最后得到:
[FnFn−1]=[1110]n−1×[F1F0]=[1110]n−1×[10]
因此要求F(n),只要对这个二阶方阵求n - 1次方,最后取结果方阵第一行第一列的数字就可以了。
看起来有点儿化简为繁的感觉,但关键点在于,幂运算是可以二分加速的。设有一个方阵a,利用分治法求a的n次方,有:
an={an/2×an/2a(n−1)/2×a(n−1)/2×a,if x iseven, if x isodd
可见复杂度满足T(n) = T(n / 2) +O(1),根据Master定理可得:T(n) = O(log n)。
在实现的时候,可以用循环代替递归实现这里的二分分治,好处是降低了空间复杂度(用递归的话,空间复杂度为O(log n))。下面的Python程序直接利用的numpy库中的矩阵乘法(当然这个库也实现了矩阵的幂运算,我把它单独写出来是为了强调这里的分治算法)。
斐波那契数列Fibonacci
http://blog.csdn.net/ju136/article/details/8062078
- 最简单的处理方式
当n<=50的时候,是可以利用64位int长整型。long long来进行处理。
[cpp] view plaincopy
- #include <stdio.h>
- #include <stdlib.h>
- long long a[71];
- void init(void) {
- a[0] = 0;
- a[1] = 1; a[2] = 2;
- for (int i = 3; i < 71; ++i) {
- a[i] = a[i-1] + a[i-2];
- }
- }
- int main(void) {
- int n;init();
- while (scanf("%d", &n) != EOF) {
- printf("%lld\n", a[n]);
- }
- return 0;
- }
- //注意打表及长整型。
很简单,就把上面那个题给秒掉了。
高效的处理
如果你还对前面讲到的斐波那契数有印象的话,可以轻易地得到。
[ FnFn-1] = [F2 F1] pow(A, n - 2);
A =[1 1
1 0];
值得注意的是,针对于原始的斐波那契数而言,其递推之后是到[F1 F0] 截止,所以其指数是n-1。而在这里是到[F2F1] 截止。所以指数为n-2。
此外,假设T = pow(A, n-2);
与前面不同的是,Fn = F2*T[0][0] + F1*T[1][0];
从而可以写出如下代码:
[cpp] view plaincopy
- #include <stdio.h>
- #include <stdlib.h>
- typedef struct _node
- {
- long long a, b;
- long long c, d;
- } node;
- void _multiple(node *x, node *y) {
- node temp;
- temp.a = x->a*y->a + x->b*y->c;
- temp.b = x->a*y->b + x->b*y->d;
- temp.c = x->c*y->a + x->d*y->c;
- temp.d = x->c*y->b + x->d*y->d;
- *x = temp;
- }
- long long fib(int n)
- {
- if (0 == n) return 0;
- if (1 == n) return 1;
- if (2 == n) return 2;
- node odd; odd.a = odd.d = 1; odd.c = odd.b = 0; //单位矩阵
- node temp; temp.a = temp.b = temp.c = 1; temp.d = 0; // A矩阵
- n -= 2;
- while (n) {
- if (n&1) _multiple(&odd, &temp);
- _multiple(&temp, &temp);
- n >>= 1;
- }
- return odd.a * 2 + odd.c;
- }
- int main()
- {
- int n;
- while (scanf("%d", &n) != EOF) {
- printf("%lld\n", fib(n));
- }
- return 0;
- }
大整数的处理
这里,还是可以利用前面提到的大整数的加法模板来进行运算。
[cpp] view plaincopy
- #include <iostream>
- #include <string>
- #include <stdio.h>
- #include <stdlib.h>
- using namespace std;
- string &_del_zeros_before_dot(string &a)
- {
- if (a.length() <= 0 || a[0] != '0') return a;
- int i = 0;
- while (i < a.length() && a[i] == '0') ++i;
- a = a.substr(i, a.length() - i);
- return a;
- }
- string &_string_add_string(const string &a, const string &b, string &res)
- {
- int sum_value = 0, add_bit = 0;
- const int alen = a.length(), blen = b.length();
- res = "0" + (alen > blen ? a : b);
- for (int i = alen-1, j = blen-1, k = res.length() - 1;
- i >= 0 || j >= 0 || add_bit > 0;
- --i, --j, --k){
- sum_value = (i>=0 ? a[i]-48: 0) + (j>=0 ? b[j]-48: 0) + add_bit;
- add_bit = sum_value / 10;
- res[k] = sum_value%10 + '0';
- }
- if (res[0] == '0') res = res.substr(1, res.length() - 1);
- return res;
- }
- string fib(int n) {
- if (0 == n) return "0";
- if (1 == n) return "1";
- if (2 == n) return "2";
- string a_2 = "1", b_1 = "2", ret = "0";
- for (int i = 3; i <= n; ++i) {
- _string_add_string(a_2, b_1, ret); // fn = fn-2 + fn-1;
- a_2 = b_1;
- b_1 = ret;
- }
- return ret;
- }
- int main(void) {
- int n;
- while (scanf("%d", &n) != EOF) {
- printf("%s\n", fib(n).c_str());
- }
- return 0;
- }
扩展-1
首先题意变成:如果一下子可以走1,2,3步呢。也就是多了一种走法。根据惯性思维
Fn = Fn-1 + Fn-2 + Fn-3
且
F1 = 1
F2 = 2
F3 = F1 + F2 + 1 = 4
由于有了递推公式。可以轻易地写出如下代码:
[cpp] view plaincopy
- #include <stdio.h>
- long long step(int n) {
- int i = 0;
- long long a = 1, b = 2, c = 4, ret;
- if (0 == n) return 0;
- if (1 == n) return 1;
- if (2 == n) return 2;
- if (3 == n) return 4;
- for (i = 4; i <= n; ++i) {
- ret = a + b + c;
- a = b;
- b = c;
- c = ret;
- }
- return ret;
- }
- int main(void)
- {
- int n;
- while (scanf("%d", &n) != EOF) {
- printf("%lld\n", step(n));
- }
- return 0;
- }
同样也可以写成大数模板形式
[cpp] view plaincopy
- #include <iostream>
- #include <string>
- #include <stdio.h>
- #include <stdlib.h>
- using namespace std;
- string &_del_zeros_before_dot(string &a)
- {
- if (a.length() <= 0 || a[0] != '0') return a;
- int i = 0;
- while (i < a.length() && a[i] == '0') ++i;
- a = a.substr(i, a.length() - i);
- return a;
- }
- string &_string_add_string(const string &a, const string &b, string &res)
- {
- int sum_value = 0, add_bit = 0;
- const int alen = a.length(), blen = b.length();
- res = "0" + (alen > blen ? a : b);
- for (int i = alen-1, j = blen-1, k = res.length() - 1;
- i >= 0 || j >= 0 || add_bit > 0;
- --i, --j, --k){
- sum_value = (i>=0 ? a[i]-48: 0) + (j>=0 ? b[j]-48: 0) + add_bit;
- add_bit = sum_value / 10;
- res[k] = sum_value%10 + '0';
- }
- if (res[0] == '0') res = res.substr(1, res.length() - 1);
- return res;
- }
- string fib(int n) {
- if (0 == n) return "0";
- if (1 == n) return "1";
- if (2 == n) return "2";
- if (3 == n) return "4";
- string a = "1", b = "2", c = "4", ret = "0", temp="0";
- for (int i = 4; i <= n; ++i) {
- _string_add_string(a, b, temp); // temp = fn-2 + fn-3;
- _string_add_string(temp, c, ret); // ret = temp + fn-1;
- a = b;
- b = c;
- c = ret;
- }
- return ret;
- }
- int main(void) {
- int n;
- while (scanf("%d", &n) != EOF) {
- printf("%s\n", fib(n).c_str());
- }
- return 0;
- }
那么我们考虑,如何写成O(lgN)的效率呢。
同样,我们也可以写出矩阵的表达式。
[Fn Fn-1 Fn-2] = [Fn-1Fn-2 Fn3] * A;
可以得出A的值为
1 1 0
1 0 1
1 0 0
那么,在这种情况下,如果层次递推,我们可以得到
[Fn Fn-1 Fn-2] = [F3 F2F1] * pow(A, n-3);
如果设T = pow(A, n-3);
那么Fn = F3*T[0,0] + F2*T[1,0] + F1*T[2,0];
因此,也可以写出如下的代码。
[cpp] view plaincopy
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #define MX 3
- typedef struct _node
- {
- long long a[MX][MX];
- } node;
- void _multiple(node *x, node *y) {
- node temp;
- long long (*a)[MX] = x->a;
- long long (*b)[MX] = y->a;
- long long (*c)[MX] = temp.a;
- for (int row = 0; row < MX; ++row) {
- for (int col = 0; col < MX; ++col) {
- long long s = 0;
- for (int i = 0; i < MX; ++i) {
- s += a[row][i] * b[i][col];
- }
- c[row][col] = s;
- }
- }
- *x = temp;
- }
- long long fib(int n)
- {
- if (0 == n) return 0;
- if (1 == n) return 1;
- if (2 == n) return 2;
- if (3 == n) return 4;
- node odd;
- //设置odd为单位矩阵
- memset(odd.a, 0, sizeof(odd.a));
- for (int i = 0; i < MX; ++i) {
- odd.a[i][i] = 1;
- }
- node temp; // A矩阵
- memset(temp.a, 0, sizeof(temp.a));
- for (int i = 0; i < MX; ++i) {
- temp.a[i][0] = 1;
- if (i + 1 < MX) temp.a[i][i+1] = 1;
- }
- n -= 3;
- while (n) {
- if (n&1) _multiple(&odd, &temp);
- _multiple(&temp, &temp);
- n >>= 1;
- }
- return 4*odd.a[0][0] + 2*odd.a[1][0] + odd.a[2][0];
- }
- int main()
- {
- int n;
- while (scanf("%d", &n) != EOF) {
- printf("%lld\n", fib(n));
- }
- return 0;
- }
扩展-2
那么接下的问题是
假设A上台阶,一次可以跨1层,2层,3层..或m层,问A上n层台阶,有多少种走法?
其中,m和n都是正整数,并且 m<= n, m <= 10, n <= 50
由于m, n并不清楚。
不过对于1<= x <= m的走法,应该是很清楚的。也就是Fx = Fx-1 + Fx-2 + ...... + F1 + 1
而对于大于m的而言,则是Fy= Fy-1 + Fy-2 + Fy-3 + .... + Fy-m;
公式都出来了,就不用去想了。
[cpp] view plaincopy
- #include <stdio.h>
- #include <iostream>
- #include <list>
- using namespace std;
- long long fib(unsigned int m, int n) {
- list<long long> l;
- long long a = 0, s = 0, ret;
- for (int i = 1; i <= m && i <= n; ++i) {
- a = s + 1;
- l.push_back(a);
- s += a;
- }
- if (n <= m) return l.back();
- for (int i = m + 1; i <= n; ++i) {
- s = 0;
- for (list<long long>::iterator iter = l.begin();
- iter != l.end(); ++iter) {
- s += *iter;
- }
- ret = s;
- l.pop_front();
- l.push_back(ret);
- }
- return ret;
- }
- int main(void) {
- int m, n;
- while (scanf("%d%d", &m, &n) != EOF){
- printf("%lld\n", fib(m, n));
- }
- return 0;
- }
如果觉得上面的方法不好理解。没关系。直接采用打表法。
[cpp] view plaincopy
- #include <stdio.h>
- int m, n;
- long long a[51];
- long long fib(int m, int n) {
- long long s = 0;
- a[0] = 0;
- for (int i = 1; i <=m && i <= n; ++i) {
- a[i] = s + 1;
- s += a[i];
- }
- if (n <= m) return a[n];
- for (int i = m + 1; i <= n; ++i) {
- s = 0;
- for (int j = 1; j <= m; ++j) {
- s += a[i-j];
- }
- a[i] = s;
- }
- return a[n];
- }
- int main(void)
- {
- while (scanf("%d%d", &m, &n) != EOF &&0 <= m && m <=n && n <= 50) {
- printf("%lld\n", fib(m, n));
- }
- return 0;
- }
这里也顺便给出大整数求解的方案,也就是当n超过50的时候的处理方式。
[cpp] view plaincopy
- #include <iostream>
- #include <string>
- #include <stdio.h>
- #include <stdlib.h>
- #include <list>
- using namespace std;
- string &_del_zeros_before_dot(string &a)
- {
- if (a.length() <= 0 || a[0] != '0') return a;
- int i = 0;
- while (i < a.length() && a[i] == '0') ++i;
- a = a.substr(i, a.length() - i);
- return a;
- }
- string &_string_add_string(const string &a, const string &b, string &res)
- {
- int sum_value = 0, add_bit = 0;
- const int alen = a.length(), blen = b.length();
- res = "0" + (alen > blen ? a : b);
- for (int i = alen-1, j = blen-1, k = res.length() - 1;
- i >= 0 || j >= 0 || add_bit > 0;
- --i, --j, --k){
- sum_value = (i>=0 ? a[i]-48: 0) + (j>=0 ? b[j]-48: 0) + add_bit;
- add_bit = sum_value / 10;
- res[k] = sum_value%10 + '0';
- }
- if (res[0] == '0') res = res.substr(1, res.length() - 1);
- return res;
- }
- string fib(unsigned int m, int n) {
- list<string> l;
- string a = "0", s = "0", ret, temp;
- for (int i = 1; i <= m && i <= n; ++i) {
- _string_add_string(s, "1", a);
- l.push_back(a);
- _string_add_string(s, a, temp);
- s = temp;
- }
- if (n <= m) return l.back();
- for (int i = m + 1; i <= n; ++i) {
- s = "0";
- for (list<string>::iterator iter = l.begin();
- iter != l.end(); ++iter) {
- _string_add_string(s, *iter, temp);
- s = temp;
- }
- ret = s;
- l.pop_front();
- l.push_back(ret);
- }
- return ret;
- }
- int main(void) {
- int m, n;
- while (scanf("%d%d", &m, &n) != EOF) {
- printf("%s\n", fib(m, n).c_str());
- }
- return 0;
- }
可能你还希望找出一个针对于存在m的情况下的高效率的解法。那好吧。这里给个示例。原理与前面O(lgN)的原理几乎一样。
只不过你需要注意一下A矩阵的表达形式。其他的没什么不一样的。还有就是最后有个相乘的情况。
[cpp] view plaincopy
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- typedef struct _node {
- long long **a;
- }node;
- long long **_init(int m) {
- long long **t = NULL;
- t = (long long **)malloc(sizeof(long long*)*m);
- for (int i = 0; i < m; ++i){
- t[i] = (long long *)malloc(sizeof(long long) * m);
- for (int j = 0; j < m; ++j) {
- t[i][j] = 0;
- }
- }
- return t;
- }
- void _destroy(long long **t, int m) {
- for (int i = 0; i < m; ++i) {
- free(t[i]);
- }
- free(t);
- }
- void _multiple(node *x, node *y, int m) {
- node temp; temp.a = _init(m);
- long long **a = x->a;
- long long **b = y->a;
- long long **c = temp.a;
- for (int row = 0; row < m; ++row) {
- for (int col = 0; col < m; ++col) {
- long long s = 0;
- for (int i = 0; i < m; ++i) {
- s += a[row][i] * b[i][col];
- }
- c[row][col] = s;
- }
- }
- _destroy(x->a, m);
- x->a = temp.a;
- }
- long long fib(int m, int n)
- {
- long long ret;
- long long *front = (long long *) malloc(sizeof(long long)*(m + 1));
- long long s = 0;
- front[0] = 0;
- for (int i = 1; i <= m && i <= n; ++i) {
- front[i] = s + 1;
- s += front[i];
- }
- if (n <= m) {
- ret = front[n];
- free(front);
- return ret;
- }
- node odd; odd.a = _init(m);
- //设置odd为单位矩阵
- for (int i = 0; i < m; ++i) {
- odd.a[i][i] = 1;
- }
- //A矩阵.
- node temp; temp.a = _init(m);
- for (int i = 0; i < m; ++i) {
- temp.a[i][0] = 1;
- if (i + 1 < m) temp.a[i][i+1] = 1;
- }
- n -= m;
- while (n) {
- if (n&1) _multiple(&odd, &temp, m);
- _multiple(&temp, &temp, m);
- n >>= 1;
- }
- ret = 0;
- for (int i = 1; i <= m; ++i) {
- ret += front[i] * odd.a[m-i][0];
- }
- _destroy(odd.a, m);
- _destroy(temp.a, m);
- return ret;
- }
- int main()
- {
- int m, n;
- while (scanf("%d%d", &m, &n) != EOF) {
- printf("%lld\n", fib(m, n));
- }
- return 0;
- }
扩展-3
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
测试链接:http://ac.jobdu.com/problem.php?cid=1039&pid=5
这个只是第二个扩展的特殊情况。也就是当m==n的时候的情况。
[cpp] view plaincopy
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- long long fib(int m)
- {
- long long ret;
- long long *front = (long long *) malloc(sizeof(long long)*(m + 1));
- long long s = 0;
- front[0] = 0;
- for (int i = 1; i <= m; ++i) {
- front[i] = s + 1;
- s += front[i];
- }
- ret = front[m];
- free(front);
- return ret;
- }
- int main()
- {
- int m, n;
- while (scanf("%d", &n) != EOF) {
- printf("%lld\n", fib(n));
- }
- return 0;
- }
这个就没有更高效的办法了。除了再加入大整数之外。~~大整数就不用再加了。都差不多的。
当然,如果再深入地看一下,会发现F(n) = 2^(n-1);
那这个代码就更好写了。
[cpp] view plaincopy
- #include <stdio.h>
- long long pow(long long x, long long n) {
- long odd = 1;
- while (n) {
- if (n&1) odd *= x;
- x *= x;
- n >>= 1;
- }
- return odd;
- }
- int main(void) {
- int n;
- while (scanf("%d", &n) != EOF) {
- printf("%lld\n", pow(2, n-1));
- }
- return 0;
- }
- 《logN的时间内算fibonacci number》
- 计算fibonacci数列logn的算法
- Fibonacci in O(logn)
- O(logn)求Fibonacci数列
- O(logN)求Fibonacci序列
- O(logn)求Fibonacci数列
- O(logn)求Fibonacci数列
- Fibonacci 数列O(logn)解法
- 在O(logn)时间内找到数组中离每个数最近而又比它大的数的下标
- Fibonacci Number
- Fibonacci number
- Fibonacci Number
- Fibonacci Number
- Fibonacci number
- O(logn)时间复杂度求Fibonacci数列
- O(logn)求Fibonacci数列[算法]
- O(logn)时间复杂度求Fibonacci数列
- O(logn)求Fibonacci数列[算法]
- MinGW下构建Bullet
- C 语言程序设计实践 7.6 函数计算
- 遥感影像的分块读取和显示
- 《25道常见算法面试题》
- 人老了,但是却没有钱养老
- 《logN的时间内算fibonacci number》
- SQL SERVER 2005 递归查询备忘
- Android多线程Thread Runnable Handler AsyncTask等之间的关系
- leetcode: Edit Distance
- 4.Lucene3.案例介绍,创建索引,查询等操作验证
- sql server 常用函数备忘
- iOS开发之OC语法基础(五)--字符串
- ngrok的使用和在(window)本地部署微信服务器
- 【MFC自绘窗口】 窗口设计第一课