Re: [問題] 關於遞迴加快速度的迷思?

看板C_and_CPP作者 (Schottky)時間12年前 (2013/09/07 20:55), 編輯推噓1(100)
留言1則, 1人參與, 最新討論串8/10 (看更多)
: 推 c2251393:公式解的話有個^n,要計算的話還是快不了吧 : → c2251393:(是說再深入下去感覺會離題XDD 不, 計算 n 次方的複雜度是 O(log N), 和 n 的 bit 數成正比, 在 N 大到某個程度以後, 會比前面討論的 O(N) 方法好太多了... 也就是說 x^n 次方的計算方法並非 for (r=1,c=0; c<n; c++) r*=x; 而是可以利用 x^(2n) = (x^n) * (x^n) 遞迴公式去計算... 令 m=n/2 先計算 x^m 再計算 x^n = x^m*x^m (n是偶數) 或 x^n=x^m*x^m*x (奇數) (不用我說, 看完前文大家應該也知道這種簡單遞迴都是用單層迴圈實作的。) 公式法的問題出在高精確度浮點運算的速度和精確度, 不過這也不是不能改善... * * * 那麼 Fibonacci 整數數列本身也是可以用類似的二分再二分法去計算的, 我一直在奇怪, 前面怎麼都沒有人提到過... 可能是目前用到的 n 太小的關係... 令函數 f(n) = (a, b, c, d) 傳回值是四個係數, 代表以 F[0]=x, F[1]=y 開頭的 Fibonacci 數列, 其最後兩項為 F[n-1]=ax+by, F[n]=cx+dy, 將兩條數列接起來, 則可推得 f(2n-1) = ((a^2+bc),(ab+bd),(ac+cd),(bc+d^2)) 所以我們只需要把數列切成等長的兩半計算, 算完再用上面的公式接起來即可... * * * 以下這段 code 計算 F[10^7] 不到 3 秒, 運算結果有 2,089,877 位數 (十進位) 使用前面討論的迴圈法要 10-20 分鐘, 你的 CPU 再快大概也不會小於 5 分鐘... 大數計算部份使用 GNU MP 的 mpz 整數運算, MinGW 或任何版本的 Linux 應該都很容易抓到或者已內建 libgmp 這個套件... 那麼這個二分法能不能改寫成迴圈呢? 可以的, 我的寫法是 head recursive, 可以輕易改成單層迴圈... 有興趣的話各位不妨試試看... // URL = http://codepad.org/Vn2lmxtz // -------- >8 -------- >8 -------- >8 -------- >8 -------- >8 -------- #include <stdio.h> #include <stdint.h> #include <gmp.h> #include <inttypes.h> void f(uint64_t n, mpz_t a_1, mpz_t b_1, mpz_t a_0, mpz_t b_0) { uint64_t m; mpz_t ma_1, mb_1, ma_0, mb_0; if (n==2) { mpz_set_ui(a_1, 0); mpz_set_ui(b_1, 1); mpz_set_ui(a_0, 1); mpz_set_ui(b_0, 1); } else if (n==3) { mpz_set_ui(a_1, 1); mpz_set_ui(b_1, 1); mpz_set_ui(a_0, 1); mpz_set_ui(b_0, 2); } else { mpz_init(ma_1); mpz_init(mb_1); mpz_init(ma_0); mpz_init(mb_0); m = (n+1)/2; f(m, ma_1, mb_1, ma_0, mb_0); // b_1 = (ma_1+mb_0)*mb_1 // a_0 = (ma_1+mb_0)*ma_0 // a_1 = mb_1*ma_0 + ma_1^2 // b_0 = mb_1*ma_0 + mb_0^2 mpz_add(b_1, ma_1, mb_0); mpz_set(a_0, b_1); mpz_mul(b_1, b_1, mb_1); mpz_mul(a_0, a_0, ma_0); mpz_mul(a_1, mb_1, ma_0); mpz_set(b_0, a_1); mpz_pow_ui(ma_1, ma_1, 2); mpz_add(a_1, a_1, ma_1); mpz_pow_ui(mb_0, mb_0, 2); mpz_add(b_0, b_0, mb_0); if (m*2-1 < n) { mpz_swap(a_1, a_0); mpz_swap(b_1, b_0); mpz_add(a_0, a_1, a_0); mpz_add(b_0, b_1, b_0); } mpz_add(ma_0, a_0, b_0); mpz_clear(ma_1); mpz_clear(mb_1); mpz_clear(ma_0); mpz_clear(mb_0); } } int main(void) { uint64_t n; mpz_t a_1, b_1, a_0, b_0; double d; signed long int exp; n = 10000000; mpz_init(a_1); mpz_init(b_1); mpz_init(a_0); mpz_init(b_0); f(n, a_1, b_1, a_0, b_0); // Output number itself. gmp_printf("F(%" PRIu64 ") = %Zd\n", n, b_0); // Output in x*2^y format. d = mpz_get_d_2exp(&exp, b_0); printf("F(%" PRIu64 ") = %f * 2^%ld\n", n, d, exp); mpz_clear(a_1); mpz_clear(b_1); mpz_clear(a_0); mpz_clear(b_0); return 0; } -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 220.137.2.15

09/08 09:36, , 1F
所謂的矩陣法就是 O(log N) (假設大數操作是 O(1))
09/08 09:36, 1F
文章代碼(AID): #1IAvArBa (C_and_CPP)
討論串 (同標題文章)
本文引述了以下文章的的內容:
完整討論串 (本文為第 8 之 10 篇):
文章代碼(AID): #1IAvArBa (C_and_CPP)