số fibonacci thứ n theo thời gian tuyến tính


76

Có thuật toán nào để tính số fibonacci thứ n trong thời gian tuyến tính phụ không?


4
Người ta có thể tranh luận rằng nó liên quan đến các thuật toán, vì OP đưa ra một tham chiếu mơ hồ đến độ phức tạp của thuật toán ... Mặc dù vậy, tôi vẫn tò mò về thuật toán.
Matthew Scharley

2
Hai câu trả lời dưới đây có công thức chính xác. Về việc liệu câu hỏi này có liên quan đến lập trình hay không: nó là một phần của khoa học máy tính. Bộ máy được sử dụng để suy ra công thức được gọi là "hàm sinh" và có vai trò quan trọng trong phân tích thuật toán.
azheglov

1
@azheglov: Mặc dù các hàm tạo là hữu ích nhưng chúng không cần thiết để lấy biểu thức dạng đóng cho dãy Fibonacci.
jason

7
Bạn có một vấn đề mà bạn muốn giải quyết vì bất cứ lý do gì và bạn muốn giải quyết nó một cách hiệu quả. Đôi khi cái nhìn sâu sắc được yêu cầu sẽ là một triển khai mới, đôi khi là một thuật toán và đôi khi là toán học. Không cần thiết phải chỉ trích tình huống là "không liên quan đến lập trình" mỗi khi xảy ra.
ShreevatsaR

7
Kích thước của kết quả là tuyến tính tính bằng n. Do đó không có thuật toán này. Tất nhiên điều đó không làm mất hiệu lực của bất kỳ câu trả lời hay ho nào dưới đây tính các số Fibonacci bằng các phép toán số học O (log n).
Accipitridae

Câu trả lời:


66

Số nFibonacci thứ được đưa ra bởi

f(n) = Floor(phi^n / sqrt(5) + 1/2) 

Ở đâu

phi = (1 + sqrt(5)) / 2

Giả sử rằng các hoạt động toán học nguyên thủy ( +, -, */) là O(1)bạn có thể sử dụng kết quả này để tính toán nthứ số Fibonacci trong O(log n)thời gian ( O(log n)vì lũy thừa trong công thức).

Trong C #:

static double inverseSqrt5 = 1 / Math.Sqrt(5);
static double phi = (1 + Math.Sqrt(5)) / 2;
/* should use 
   const double inverseSqrt5 = 0.44721359549995793928183473374626
   const double phi = 1.6180339887498948482045868343656
*/

static int Fibonacci(int n) {
    return (int)Math.Floor(Math.Pow(phi, n) * inverseSqrt5 + 0.5);
}

7
@Json Tôi không phản đối bạn, nhưng những người khác có thể làm như vậy vì câu trả lời của bạn cho thấy rằng số fibonacci thứ N có thể được tính trong thời gian O (log n), điều này là sai. Mã của bạn đang tính toán một giá trị gần đúng. Mã của bạn ít nhất phải là O (n) với độ chính xác tùy ý, vì độ dài của câu trả lời là O (n).
PeterAllenWebb

10
@PeterAllenWebb: Công thức được cung cấp không phải là công thức gần đúng. Số Fibonacci thứ n bằng với tầng của phi^n / sqrt(5) + 1/2nơi đó phi = (1 + sqrt(5)) / 2. Đây là sự thật. Thứ hai, tôi hiểu quan điểm mà những người khác đang đưa ra về độ dài của câu trả lời O(n)nhưng tôi đã thêm một nhận xét vào câu trả lời của mình giả định rằng các phép toán nguyên thủy cần thời gian không đổi (tôi biết là không, trừ khi bạn ràng buộc các đầu vào). Quan điểm của tôi là chúng ta có thể tìm thấy số Fibonacci thứ n trong các O(log n)phép toán số học.
jason 13/10/09

4
@Jason: Giả sử rằng lũy ​​thừa cũng là O (1) làm cho toàn bộ thuật toán là O (1). Điều đó thật tuyệt, tuy nhiên, lũy thừa không phải là O (1) và cũng không phải là các phép toán nguyên thủy khác. Vì vậy, trong ngắn hạn, công thức là tốt, nhưng nó không tính toán kết quả theo thời gian tuyến tính phụ.
yairchu

12
@Jason: Công thức không phải là một xấp xỉ, nhưng là một xấp xỉ (ngoại trừ việc triển khai C # ảo trong đó Math.Pow (…) có độ chính xác vô hạn, trong trường hợp đó mã là O (n)).
ShreevatsaR

14
@Jason: Không. Chạy mã của bạn trên n = 1000 (số Fibonacci 43466 ... 849228875 có 209 chữ số đo được) và cho tôi biết liệu bạn có đúng tất cả các chữ số hay không. Đối với Math.Floor để lấy đúng phần nguyên, nhiều chữ số đó phải được tính toán chính xác bằng Math.Pow. Trên thực tế, khi triển khai C ++ của tôi, ngay cả 16 chữ số F_ {74} = 130496954492865 cũng được tính không chính xác, mặc dù số nguyên 130496954492865 có thể được biểu diễn chính xác (với độ dài dài) và tôi sẽ ngạc nhiên nếu C # có nhiều chữ số hơn hơn thế.
ShreevatsaR

100

Theo sau từ tham chiếu của Pillsy đến lũy thừa ma trận, sao cho ma trận

M = [1 1]
    [1 0] 

sau đó

fib ( n ) = M n 1,2

Nâng ma trận lên lũy thừa bằng cách sử dụng phép nhân lặp lại không hiệu quả lắm.

Hai cách tiếp cận để tính lũy thừa ma trận là chia và chinh phục mang lại M n trong các bước O ( ln n ), hoặc phân rã giá trị riêng là thời gian không đổi, nhưng có thể gây ra lỗi do độ chính xác dấu phẩy động hạn chế.

Nếu bạn muốn một giá trị chính xác lớn hơn độ chính xác của việc triển khai dấu phẩy động, bạn phải sử dụng phương pháp O (ln n) dựa trên mối quan hệ này:

M n = ( M n / 2 ) 2 nếu n chẵn
   = M · M n -1 nếu n lẻ

Phân tích giá trị riêng trên M tìm được hai ma trận UΛ sao cho Λ là đường chéo và

 M   = U  Λ  U -1  
 M n = ( U  Λ  U -1 ) n 
    = U  Λ  U -1  U  Λ  U -1  U  Λ  U -1 ... n lần
    = U  Λ  Λ  Λ ... U -1  
    = U  Λ  n  U -1 
Nâng ma trận đường chéo Λ lên lũy thừa thứ n là một vấn đề đơn giản khi nâng từng phần tử trong Λ lên lũy thừa thứ n , vì vậy điều này đưa ra phương pháp O (1) nâng M lên lũy thừa thứ n . Tuy nhiên, các giá trị trong Λ không có khả năng là số nguyên, vì vậy một số lỗi sẽ xảy ra.

Định nghĩa Λ cho ma trận 2x2 của chúng ta là

Λ = [λ 1 0]
  = [0 λ 2 ]

Để tìm mỗi λ , chúng tôi giải

| M - λ I | = 0

cái nào cho

| M - λ I | = -λ (1 - λ) - 1

λ² - λ - 1 = 0

sử dụng công thức bậc hai

λ = (-b ± √ (b² - 4ac)) / 2a
     = (1 ± √5) / 2
 {λ 1 , λ 2 } = {Φ, 1-Φ} trong đó Φ = (1 + √5) / 2

Nếu bạn đã đọc câu trả lời của Jason, bạn có thể thấy điều này sẽ đi đến đâu.

Giải quyết các eigenvectors X 1X 2 :

nếu X 1 = [ X 1,1 , X 1,2 ]

 M . X 1 1 = λ 1 X 1

 X 1,1 + X 1,2 = λ 1  X 1,1 
 X 1,1       = λ 1  X 1,2

=>
 X 1 = [Φ, 1]
  X 2 = [1-Φ, 1]

Các vectơ này cho U :

Ư = [ X 1,1 , X 2,2 ]
    [ X 1,1 , X 2,2 ]

  = [Φ, 1-Φ]
    [1, 1]

Đảo ngược chữ U bằng cách sử dụng

A    = [ab]
      [cd]
=>
A -1 = (1 / | A |) [d -b]
                   [-ca]

nên U -1 được cho bởi

U -1 = (1 / (Φ - (1 - Φ)) [1 Φ-1]
                               [-1 Φ]
U -1 = (√5) -1   [1 Φ-1]
               [-1 Φ]

Kiểm tra sự tỉnh táo:

UΛU -1 = (√5) -1 [Φ 1-Φ]. [Φ 0]. [1 Φ-1]
                     [1 1] [0 1-Φ] [-1 Φ]

hãy để Ψ = 1-Φ, giá trị riêng khác

vì Φ là một gốc của λ²-λ-1 = 0 
so -ΨΦ = Φ²-Φ = 1
và Ψ + Φ = 1

UΛU -1 = (√5) -1 [Φ Ψ]. [Φ 0]. [1 -Ψ]
                 [1 1] [0 Ψ] [-1 Φ]

       = (√5) -1 [Φ Ψ]. [Φ -ΨΦ]
                 [1 1] [-Ψ ΨΦ]

       = (√5) -1 [Φ Ψ]. [Φ 1]
                 [1 1] [-Ψ -1]

       = (√5) -1 [Φ²-Ψ² Φ-Ψ]
                  [Φ-Ψ 0]

       = [Φ + Ψ 1]    
         [1 0]

       = [1 1] 
         [1 0]

       = M 

Vì vậy, kiểm tra sự tỉnh táo giữ.

Bây giờ chúng ta có mọi thứ chúng ta cần để tính M n 1,2 :

M n = U Λ n U -1 
   = (√5) -1 [Φ Ψ]. [Φ n   0]. [1 -Ψ]
              [1 1] [0 Ψ n ] [-1 Φ]

   = (√5) -1 [Φ Ψ]. [Φ n   -ΨΦ n ]
              [1 1] [-Ψ n    Ψ n Φ]

   = (√5) -1 [Φ Ψ]. [Φ n    Φ n -1 ]
              [1 1] [-Ψ nn -1 ] as ΨΦ = -1

   = (√5) -1n +1n +1       Φ nn ]
              [Φ nn       Φ n -1n -1 ]

vì thế

 fib ( n ) = M n 1,2 
        = (Φ n - (1-Φ) n ) / √5

Mà đồng ý với công thức được đưa ra ở nơi khác.

Bạn có thể suy ra nó từ quan hệ tuần hoàn, nhưng trong kỹ thuật tính toán và mô phỏng, việc tính toán các giá trị riêng và giá trị riêng của ma trận lớn là một hoạt động quan trọng, vì nó mang lại sự ổn định và hài của hệ phương trình, cũng như cho phép nâng ma trận lên lũy thừa một cách hiệu quả.


+1 - Nội dung tuyệt vời, như thường lệ. Bạn đã sử dụng gì để sắp chữ? Mủ cao su?
duffymo

Nó được sao chép từ cuốn sách Đại số của Gilbert Strang, hoặc từ cuốn sách hay khác của Đại số tuyến tính.
alinsoar

1
@alinsoar nó không phải là 'sao chép dán', nhưng được thực hiện như một bài tập để kiểm tra, tôi vẫn có thể nhớ lin a của mình, với một số tham chiếu đến ghi chú khóa học Đại học Mở và wikipedia.
Pete Kirkham

Tôi đã tham gia khóa học Đại số L với Gilbert Strang, và nó giống hệt nhau. Hoàn toàn như vậy, vấn đề biểu thị đệ quy thông qua phân rã ma trận là cổ điển, và có thể tìm thấy trong bất kỳ sách / khóa học văn bản hay nào.
alinsoar

56

Nếu bạn muốn số chính xác (là "bignum", chứ không phải là int / float), thì tôi e rằng

Điều đó là không thể!

Như đã nêu ở trên, công thức cho số Fibonacci là:

fib n = sàn (phi n / √5 + 1 / 2 )

fib n ~ = phi n / √5

Có bao nhiêu chữ số fib n?

numDigits (fib n) = log (fib n) = log (phi n / √5) = log phi n - log √5 = n * log phi - log √5

numDigits (fib n) = n * const + const

đó là O ( n )

Vì kết quả được yêu cầu là của O ( n ), nó không thể được tính trong thời gian ít hơn O ( n ).

Nếu bạn chỉ muốn các chữ số thấp hơn của câu trả lời, thì bạn có thể tính toán theo thời gian tuyến tính con bằng phương pháp lũy thừa ma trận.


2
@yairchu: Hãy để tôi diễn đạt lại điều này, nếu tôi hiểu nó một cách chính xác. Về lý thuyết tính toán fib_n yêu cầu tính n chữ số nên với n bất kỳ sẽ mất O (n) thời gian. Tuy nhiên, nếu fib_n <sizeof (long long) thì chúng ta có thể tính fib_n trong thời gian O (log n) vì kiến ​​trúc máy đang cung cấp cơ chế thiết lập các bit song song. (Ví dụ: int i = -1; yêu cầu đặt 32 bit nhưng trên máy 32 bit, tất cả 32 bit có thể được đặt trong thời gian không đổi.
Sumit

7
@Sumit: Nếu bạn chỉ muốn hỗ trợ các kết quả phù hợp với 32 bit, thì bạn cũng có thể có một bảng tra cứu cho 48 kết quả đầu tiên này của chuỗi. Đó rõ ràng là O (1), nhưng: Thực hiện phân tích big-O cho một giới hạn N là một điều ngớ ngẩn, vì bạn luôn có thể kết hợp bất cứ thứ gì vào hệ số không đổi. Vì vậy, câu trả lời của tôi đề cập đến đầu vào không bị ràng buộc.
yairchu

1
@yairchu: Bạn có thể chứng minh logic của mình cho một ví dụ nổi tiếng chẳng hạn như O(n*log n)sắp xếp dựa trên so sánh của một dãy nsố trong đó mỗi số có các O(log n)chữ số không?
jfs

1
Điều này đúng hay sai tùy thuộc vào những gì bạn định "thời gian" có nghĩa là gì. Để sắp xếp (hoặc tra cứu bảng băm), "thời gian" có nghĩa là số lượng so sánh. Trong câu hỏi, nó có thể có nghĩa là các phép toán số học. Trong câu trả lời này, nó có nghĩa là một cái gì đó giống như các phép toán thông minh bằng chữ số.
Paul Hankin

4
Số nguyên thực sự sẽ có một biểu diễn hữu hạn trong sqrt cơ sở (2), nhưng nó sẽ chỉ bằng 0 đối với các chữ số lẻ, tức là tương đương với cơ số 2. Nếu bất kỳ chữ số lẻ nào trong sqrt cơ sở (2) là khác không, bạn có một số vô tỉ . Một trường hợp mà bạn có thể muốn cơ sở phi là trong ADC khi chuyển đổi tín hiệu liên tục sang tín hiệu tương tự. Afaik đây là ứng dụng "công nghiệp" của cơ sở phi, nơi nó được sử dụng để giảm hạt thô khi làm tròn tín hiệu. Cá nhân tôi mặc dù vậy, tôi đã sử dụng các mã hóa phi và fibonacci cơ sở như một cách thuận tiện về mặt lý thuyết để làm việc với các biểu diễn Fibonacci anyon của nhóm braid.
saolof

34

Một trong những bài tập trong SICP là về điều này, có câu trả lời được mô tả ở đây.

Theo phong cách mệnh lệnh, chương trình sẽ trông giống như

Hàm  Fib ( đếm )
     a ← 1
     b ← 0
     p ← 0
     q ← 1

    Trong khi  đếm > 0 Làm 
        Nếu Chẵn ( đếm ) Sau đó 
             pp ² + q ²
              q ← 2 pq + q ²
              đếmđếm ÷ 2
         Khác 
             abq + aq + ap 
             bbp + aq 
             đếmđếm - 1
         Kết thúc Nếu 
    Kết thúc trong khi

    Trả về  b 
Kết thúc Chức năng

đây là một triển khai bằng Python (được sử dụng với twistedkhuôn khổ).
jfs

"Nếu Chẵn (đếm) Thì" phải là "Nếu Lẻ (đếm) Thì"
Monirul Islam Milon

@MonirulIslamMilon if even(count)là đúng. Dãy số bắt đầu bằng số 0 (số Fibonacci thứ 0 là số 0): 0,1,1,2,3,5,8,13, ...
jfs


Nhận xét muộn, nhưng các biến p và a bị ghi đè trước khi được sử dụng để tính q và b. Để tránh vấn đề này, hãy tính toán trước các số hạng và thay đổi thứ tự của các phép gán p và q: | qq = q · q | q = 2 · p · q + qq | p = p · p + qq | ... | aq = a · q | a = b · q + aq + a · p | b = b · p + aq | .
rcgldr

24

Bạn cũng có thể làm điều đó bằng cách lũy thừa ma trận các số nguyên. Nếu bạn có ma trận

    / 1  1 \
M = |      |
    \ 1  0 /

thì (M^n)[1, 2]sẽ bằng với nsố Fibonacci thứ, nếu []là chỉ số con của ma trận và ^là lũy thừa ma trận. Đối với ma trận có kích thước cố định, phép lũy thừa thành lũy thừa dương có thể được thực hiện trong thời gian O (log n) theo cách tương tự như với số thực.

CHỈNH SỬA: Tất nhiên, tùy thuộc vào loại câu trả lời bạn muốn, bạn có thể sử dụng thuật toán thời gian không đổi. Giống như các công thức khác cho thấy, nsố Fibonacci thứ tăng theo cấp số nhân với n. Ngay cả với các số nguyên không dấu 64 bit, bạn sẽ chỉ cần một bảng tra cứu 94 mục nhập để bao gồm toàn bộ phạm vi.

CHỈNH SỬA THỨ HAI: Làm ma trận theo cấp số nhân với một phân tích riêng trước tiên chính xác tương đương với giải pháp của JDunkerly bên dưới. Các giá trị riêng của ma trận này là (1 + sqrt(5))/2(1 - sqrt(5))/2.


3
Sử dụng phép phân rã eigen của M để tính M ^ n một cách hiệu quả.
Pete Kirkham

1
Phương pháp được đề xuất phù hợp với các phép tính bằng số nguyên (có thể là với số học dài). Cách tiếp cận với phân rã eigen không thú vị: nếu bạn không cần tính toán số nguyên, thì hãy sử dụng công thức từ câu trả lời của Jason.
Konstantin Tenzin

1
@Konstantin Công thức từ câu trả lời của Jason là kết quả được đưa ra bởi sự phân hủy eigen, vì vậy bạn đang tự mâu thuẫn với chính mình.
Pete Kirkham

@Pete Kirkham Công thức đó có thể thu được bằng một số phương pháp: phương trình đặc trưng, ​​phân rã eigen, chứng minh bằng quy nạp. Tôi không chắc, phân hủy eigen là dễ nhất. Trong mọi trường hợp nó là nổi tiếng, và nó dễ dàng hơn để sử dụng nó ngay lập tức
Konstantin Tenzin

5

Wikipedia có một giải pháp dạng đóng http://en.wikipedia.org/wiki/Fibonacci_number

Hoặc trong c #:

    public static int Fibonacci(int N)
    {
        double sqrt5 = Math.Sqrt(5);
        double phi = (1 + sqrt5) / 2.0;
        double fn = (Math.Pow(phi, N) - Math.Pow(1 - phi, N)) / sqrt5;
        return (int)fn;
    }

2
Bạn có thể tránh phải tính toán thành hai cấp số nhân bằng cách sử dụng thực tế là |1 - phi|^n / sqrt(5) < 1/2khi nào nlà một số nguyên không âm.
jason

Không biết điều chỉnh luôn đã sử dụng các hình thức khác, nhưng đó là một tối ưu hóa đẹp
JDunkerley

1
Tính gần đúng của kết quả giải pháp đúng liên quan đến phép nhân ma trận.
cerkiewny

4

Đối với những cái thực sự lớn, hàm đệ quy này hoạt động. Nó sử dụng các phương trình sau:

F(2n-1) = F(n-1)^2 + F(n)^2
F(2n) = (2*F(n-1) + F(n)) * F(n)

Bạn cần một thư viện cho phép bạn làm việc với các số nguyên lớn. Tôi sử dụng thư viện BigInteger từ https://mattmccutchen.net/bigint/ .

Bắt đầu với một mảng các số fibonacci. Sử dụng fibs [0] = 0, fibs [1] = 1, fibs [2] = 1, fibs [3] = 2, fibs [4] = 3, v.v. Trong ví dụ này, tôi sử dụng một mảng gồm 501 đầu tiên (đếm 0). Bạn có thể tìm thấy 500 số Fibonacci khác 0 đầu tiên tại đây: http://home.hiwaay.net/~jalison/Fib500.html . Phải chỉnh sửa một chút để đưa nó vào đúng định dạng, nhưng điều đó không quá khó.

Sau đó, bạn có thể tìm thấy bất kỳ số Fibonacci nào bằng cách sử dụng hàm này (trong C):

BigUnsigned GetFib(int numfib)
{
int n;
BigUnsigned x, y, fib;  

if (numfib < 501) // Just get the Fibonacci number from the fibs array
    {
       fib=(stringToBigUnsigned(fibs[numfib]));
    }
else if (numfib%2) // numfib is odd
    {
       n=(numfib+1)/2;
       x=GetFib(n-1);
       y=GetFib(n);
       fib=((x*x)+(y*y));
    }
else // numfib is even
    {
       n=numfib/2;
       x=GetFib(n-1);
       y=GetFib(n);
       fib=(((big2*x)+y)*y);
   }
return(fib);
}

Tôi đã kiểm tra điều này cho số Fibonacci thứ 25.000 và những thứ tương tự.


Mã này không quá hiệu quả. Hãy tưởng tượng rằng mảng fibs [] chỉ có kích thước 10 và bạn gọi là Fib (101). Fib (101) gọi Fib (51) và Fib (50). Fib (51) gọi Fib (26) và Fib (25). Fib (50) gọi Fib (25) và Fib (24). Vì vậy, Fib (25) được gọi hai lần, đó là một sự lãng phí. Ngay cả với fib lên đến 500, bạn cũng sẽ gặp vấn đề tương tự với Fib (100000).
Eyal

3

Đây là phiên bản đệ quy của tôi đệ quy log (n) lần. Tôi nghĩ rằng nó dễ đọc nhất ở dạng đệ quy:

def my_fib(x):
  if x < 2:
    return x
  else:
    return my_fib_helper(x)[0]

def my_fib_helper(x):
  if x == 1:
    return (1, 0)
  if x % 2 == 1:
    (p,q) = my_fib_helper(x-1)
    return (p+q,p)
  else:
    (p,q) = my_fib_helper(x/2)
    return (p*p+2*p*q,p*p+q*q)

Nó hoạt động vì bạn có thể tính toán fib(n),fib(n-1)bằng cách sử dụng fib(n-1),fib(n-2)nếu n lẻ và nếu n chẵn, bạn có thể tính fib(n),fib(n-1)bằng cách sử dụng fib(n/2),fib(n/2-1).

Trường hợp cơ sở và trường hợp lẻ là đơn giản. Để suy ra trường hợp chẵn, hãy bắt đầu với a, b, c là các giá trị fibonacci liên tiếp (ví dụ: 8,5,3) và viết chúng trong một ma trận, với a = b + c. Để ý:

[1 1] * [a b]  =  [a+b a]
[1 0]   [b c]     [a   b]

Từ đó, chúng ta thấy rằng ma trận của ba số fibonacci đầu tiên, nhân với ma trận của ba số fibonacci liên tiếp bất kỳ, bằng với số tiếp theo. Vì vậy, chúng tôi biết rằng:

      n
[1 1]   =  [fib(n+1) fib(n)  ]
[1 0]      [fib(n)   fib(n-1)]

Vì thế:

      2n                        2
[1 1]    =  [fib(n+1) fib(n)  ]
[1 0]       [fib(n)   fib(n-1)]

Đơn giản hóa bên tay phải dẫn đến trường hợp đồng đều.


Tôi muốn nhấn mạnh ở đây rằng bạn muốn tính F (2n) và F (2n + 1) trong hàm của F (n) và F (n-1). Bạn đã không cho biết bạn muốn làm gì.
alinsoar

1

sử dụng R

l1 <- (1+sqrt(5))/2
l2 <- (1-sqrt(5))/2

P <- matrix(c(0,1,1,0),nrow=2) #permutation matrix
S <- matrix(c(l1,1,l2,1),nrow=2)
L <- matrix(c(l1,0,0,l2),nrow=2)
C <- c(-1/(l2-l1),1/(l2-l1))

k<-20 ; (S %*% L^k %*% C)[2]
[1] 6765

1

Số học điểm cố định không chính xác. Mã C # của Jason đưa ra câu trả lời không chính xác cho n = 71 (308061521170130 thay vì 308061521170129) và hơn thế nữa.

Để có câu trả lời đúng, hãy sử dụng một hệ thống đại số tính toán. Sympy là một thư viện như vậy cho Python. Có một bảng điều khiển tương tác tại http://live.sympy.org/ . Sao chép và dán chức năng này

phi = (1 + sqrt(5)) / 2
def f(n):
    return floor(phi**n / sqrt(5) + 1/2)

Sau đó tính toán

>>> f(10)
55

>>> f(71)
308061521170129

Bạn có thể muốn thử kiểm tra phi.


1

Ngoài việc tinh chỉnh bằng các phương pháp toán học, một trong những giải pháp tối ưu tốt nhất (tôi tin rằng) là sử dụng từ điển để tránh các phép tính lặp lại.

import time

_dict = {1:1, 2:1}

def F(n, _dict):
    if n in _dict.keys():
        return _dict[n]
    else:
        result = F(n-1, _dict) + F(n-2, _dict)
        _dict.update({n:result})
        return result

start = time.time()

for n in range(1,100000):
    result = F(n, _dict) 

finish = time.time()

print(str(finish - start))

Chúng tôi bắt đầu với từ điển tầm thường (hai giá trị đầu tiên của dãy Fibonacci) và liên tục thêm các giá trị Fibonacci vào từ điển.

Mất khoảng 0,7 giây cho 100000 giá trị Fibonacci đầu tiên (CPU Intel Xeon E5-2680 @ 2,70 GHz, RAM 16 GB, HĐH Windows 10-64 bit)


Tuy nhiên, đây là thời gian tuyến tính, câu hỏi đặc biệt hỏi làm thế nào để đạt được thời gian tuyến tính (có thể sử dụng một loại giải pháp dạng đóng).
Romeo Valentin ngày

0

xem thuật toán chia và chinh phục tại đây

Liên kết có mã giả cho phép lũy thừa ma trận được đề cập trong một số câu trả lời khác cho câu hỏi này.


0

Bạn có thể sử dụng phương trình căn bậc hai kỳ lạ để có câu trả lời chính xác. Lý do là $ \ sqrt (5) $ bị rơi ra ở cuối, bạn chỉ cần theo dõi các hệ số với định dạng nhân của riêng bạn.

def rootiply(a1,b1,a2,b2,c):
    ''' multipy a1+b1*sqrt(c) and a2+b2*sqrt(c)... return a,b'''
    return a1*a2 + b1*b2*c, a1*b2 + a2*b1

def rootipower(a,b,c,n):
    ''' raise a + b * sqrt(c) to the nth power... returns the new a,b and c of the result in the same format'''
    ar,br = 1,0
    while n != 0:
        if n%2:
            ar,br = rootiply(ar,br,a,b,c)
        a,b = rootiply(a,b,a,b,c)
        n /= 2
    return ar,br

def fib(k):
    ''' the kth fibonacci number'''
    a1,b1 = rootipower(1,1,5,k)
    a2,b2 = rootipower(1,-1,5,k)
    a = a1-a2
    b = b1-b2
    a,b = rootiply(0,1,a,b,5)
    # b should be 0!
    assert b == 0
    return a/2**k/5

if __name__ == "__main__":
    assert rootipower(1,2,3,3) == (37,30) # 1+2sqrt(3) **3 => 13 + 4sqrt(3) => 39 + 30sqrt(3)
    assert fib(10)==55

0

Dưới đây là một lớp lót tính F (n), sử dụng các số nguyên có kích thước O (n), trong các phép toán số học O (log n):

for i in range(1, 50):
    print(i, pow(2<<i, i, (4<<2*i)-(2<<i)-1)//(2<<i))

Sử dụng số nguyên có kích thước O (n) là hợp lý, vì nó có thể so sánh với kích thước của câu trả lời.

Để hiểu điều này, hãy gọi phi là tỷ lệ vàng (nghiệm lớn nhất của x ^ 2 = x + 1) và F (n) là số Fibonacci thứ n, trong đó F (0) = 0, F (1) = F (2) = 1

Bây giờ, phi ^ n = F (n-1) + F (n) phi.

Chứng minh bằng quy nạp: phi ^ 1 = 0 + 1 * phi = F (0) + F (1) phi. Và nếu phi ^ n = F (n-1) + F (n) phi, thì phi ^ (n + 1) = F (n-1) phi + F (n) phi ^ 2 = F (n-1) phi + F (n) (phi + 1) = F (n) + (F (n) + F (n-1)) phi = F (n) + F (n + 1) phi. Bước khó duy nhất trong phép tính này là bước thay thế phi ^ 2 bằng (1 + phi), theo sau vì phi là tỷ lệ vàng.

Ngoài ra các số có dạng (a + b * phi), trong đó a, b là các số nguyên được đóng dưới phép nhân.

Chứng minh: (p0 + p1 * phi) (q0 + q1 * phi) = p0q0 + (p0q1 + q1p0) phi + p1q1 * phi ^ 2 = p0q0 + (p0q1 + q1p0) phi + p1q1 * (phi + 1) = ( p0q0 + p1q1) + (p0q1 + q1p0 + p1q1) * phi.

Sử dụng cách biểu diễn này, người ta có thể tính toán phi ^ n trong các phép toán số nguyên O (log n) bằng cách sử dụng lũy ​​thừa bằng bình phương. Kết quả sẽ là F (n-1) + F (n) phi, từ đó người ta có thể đọc ra số Fibonacci thứ n.

def mul(p, q):
    return p[0]*q[0]+p[1]*q[1], p[0]*q[1]+p[1]*q[0]+p[1]*q[1]

def pow(p, n):
    r=1,0
    while n:
        if n&1: r=mul(r, p)
        p=mul(p, p)
        n=n>>1
    return r

for i in range(1, 50):
    print(i, pow((0, 1), i)[1])

Lưu ý rằng phần lớn mã này là một hàm lũy thừa chuẩn theo bình phương.

Để đi đến lớp lót bắt đầu câu trả lời này, người ta có thể lưu ý rằng biểu diễn phi bằng một số nguyên đủ lớn X, người ta có thể thực hiện (a+b*phi)(c+d*phi)như phép toán số nguyên (a+bX)(c+dX) modulo (X^2-X-1). Sau đó, powhàm có thể được thay thế bằng powhàm Python chuẩn (thuận tiện bao gồm đối số thứ ba zđể tính toán mô-đun kết quả z. XLựa chọn là 2<<i.


0

Tôi đã xem qua một số phương pháp để tính Fibonacci với độ phức tạp thời gian hiệu quả, sau đây là một số phương pháp:

Phương pháp 1 - Lập trình động Bây giờ ở đây cấu trúc con thường được biết đến do đó tôi sẽ chuyển ngay đến giải pháp -

static int fib(int n) 
{ 
int f[] = new int[n+2]; // 1 extra to handle case, n = 0 
int i; 

f[0] = 0; 
f[1] = 1; 

for (i = 2; i <= n; i++) 
{ 
    f[i] = f[i-1] + f[i-2]; 
} 

return f[n]; 
}

Phiên bản tối ưu hóa không gian ở trên có thể được thực hiện như sau:

static int fib(int n) 
 { 
    int a = 0, b = 1, c; 
    if (n == 0) 
        return a; 
    for (int i = 2; i <= n; i++) 
    { 
        c = a + b; 
        a = b; 
        b = c; 
    } 
    return b; 
} 

Phương pháp 2- (Sử dụng lũy ​​thừa của ma trận {{1,1}, {1,0}})

Đây là một O (n) dựa trên thực tế là nếu chúng ta nhân n lần ma trận M = {{1,1}, {1,0}} với chính nó (nói cách khác là tính lũy thừa (M, n)), thì chúng ta nhận được số Fibonacci thứ (n + 1) làm phần tử ở hàng và cột (0, 0) trong ma trận kết quả. Giải pháp này sẽ có O (n) thời gian.

Biểu diễn ma trận đưa ra biểu thức đóng sau cho các số Fibonacci: fibonaccimatrix

static int fib(int n) 
{ 
int F[][] = new int[][]{{1,1},{1,0}}; 
if (n == 0) 
    return 0; 
power(F, n-1); 

return F[0][0]; 
} 

/*multiplies 2 matrices F and M of size 2*2, and 
puts the multiplication result back to F[][] */
static void multiply(int F[][], int M[][]) 
{ 
int x = F[0][0]*M[0][0] + F[0][1]*M[1][0]; 
int y = F[0][0]*M[0][1] + F[0][1]*M[1][1]; 
int z = F[1][0]*M[0][0] + F[1][1]*M[1][0]; 
int w = F[1][0]*M[0][1] + F[1][1]*M[1][1]; 

F[0][0] = x; 
F[0][1] = y; 
F[1][0] = z; 
F[1][1] = w; 
} 

/*function that calculates F[][] raise to the power n and puts the 
result in F[][]*/
static void power(int F[][], int n) 
{ 
int i; 
int M[][] = new int[][]{{1,1},{1,0}}; 

// n - 1 times multiply the matrix to {{1,0},{0,1}} 
for (i = 2; i <= n; i++) 
    multiply(F, M); 
} 

Điều này có thể được tối ưu hóa để làm việc trong thời gian phức tạp O (Logn). Chúng ta có thể thực hiện phép nhân đệ quy để lấy lũy thừa (M, n) trong phương pháp trước.

static int fib(int n) 
{ 
int F[][] = new int[][]{{1,1},{1,0}}; 
if (n == 0) 
    return 0; 
power(F, n-1); 

return F[0][0]; 
} 

static void multiply(int F[][], int M[][]) 
{ 
int x =  F[0][0]*M[0][0] + F[0][1]*M[1][0]; 
int y =  F[0][0]*M[0][1] + F[0][1]*M[1][1]; 
int z =  F[1][0]*M[0][0] + F[1][1]*M[1][0]; 
int w =  F[1][0]*M[0][1] + F[1][1]*M[1][1]; 

F[0][0] = x; 
F[0][1] = y; 
F[1][0] = z; 
F[1][1] = w; 
} 

static void power(int F[][], int n) 
{ 
if( n == 0 || n == 1) 
  return; 
int M[][] = new int[][]{{1,1},{1,0}}; 

power(F, n/2); 
multiply(F, F); 

if (n%2 != 0) 
   multiply(F, M); 
} 

Phương pháp 3 (O (log n) Time) Dưới đây là một công thức lặp lại thú vị có thể được sử dụng để tìm Số Fibonacci thứ n trong thời gian O (log n).

Nếu n chẵn thì k = n / 2: F (n) = [2 * F (k-1) + F (k)] * F (k)

Nếu n lẻ thì k = (n + 1) / 2 F (n) = F (k) * F (k) + F (k-1) * F (k-1) Công thức này hoạt động như thế nào? Công thức có thể được suy ra từ phương trình ma trận trên. fibonaccimatrix

Lấy định thức cả hai vế, ta được (-1) n = Fn + 1Fn-1 - Fn2 Hơn nữa, vì AnAm = An + m đối với ma trận vuông A bất kỳ, các đồng dạng sau có thể được suy ra (chúng nhận được từ hai hệ số khác nhau của sản phẩm ma trận)

FmFn + Fm-1Fn-1 = Fm + n-1

Bằng cách đặt n = n + 1,

FmFn + 1 + Fm-1Fn = Fm + n

Đặt m = n

F2n-1 = Fn2 + Fn-12

F2n = (Fn-1 + Fn + 1) Fn = (2Fn-1 + Fn) Fn (Nguồn: Wiki)

Để công thức được chứng minh, chúng ta chỉ cần làm như sau Nếu n chẵn thì đặt k = n / 2 Nếu n lẻ thì đặt k = (n + 1) / 2

public static int fib(int n) 
{ 

    if (n == 0) 
        return 0; 

    if (n == 1 || n == 2) 
        return (f[n] = 1); 

    // If fib(n) is already computed 
    if (f[n] != 0) 
        return f[n]; 

    int k = (n & 1) == 1? (n + 1) / 2 
                        : n / 2; 

    // Applyting above formula [See value 
    // n&1 is 1 if n is odd, else 0. 
    f[n] = (n & 1) == 1? (fib(k) * fib(k) +  
                    fib(k - 1) * fib(k - 1)) 
                   : (2 * fib(k - 1) + fib(k))  
                   * fib(k); 

    return f[n]; 
} 

Phương pháp 4 - Sử dụng công thức Trong phương pháp này, chúng ta thực hiện trực tiếp công thức cho số hạng thứ n trong chuỗi Fibonacci. Thời gian O (1) Không gian O (1) Fn = {[(√5 + 1) / 2] ^ n} / √5

static int fib(int n) { 
double phi = (1 + Math.sqrt(5)) / 2; 
return (int) Math.round(Math.pow(phi, n)  
                    / Math.sqrt(5)); 
} 

Tham khảo: http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibFormula.html


0

Trước tiên, chúng ta cần lưu ý rằng các số Fibonacci (F(n))phát triển rất nhanh nvà không thể được biểu diễn bằng 64-bit cho nlớn hơn 93. Vì vậy, một chương trình để tính toán chúng cho cácn nhu cầu sử dụng các cơ chế bổ sung để hoạt động trên những số lớn này. Bây giờ, chỉ xét đến số lượng các phép toán (số lượng lớn), thuật toán để tính toán tuần tự chúng sẽ yêu cầu số lượng các phép toán tuyến tính.

Chúng tôi có thể hưởng lợi từ nhận dạng dưới đây về số Fibonacci:

F(2m) = 2*F(m)*F(m+1) − (F(m))^2

F(2m+1) = (F(m))^2 + (F(m+1))^2

(một ký hiệu như A ^ 2 biểu thị hình vuông của A).

Vì vậy, nếu chúng ta biết F(m)F(m+1), chúng ta có thể trực tiếp tính toán F(2m)F(2m+1).

Hãy xem xét biểu diễn nhị phân của n. Quan sát rằng bắt đầu với x = 1, chúng ta có thể thực hiện x = nbằng cách nhân đôi lặp đi lặp lại và có thể thêm 1 vào x. Điều này có thể được thực hiện bằng cách lặp lại các bit củan và kiểm tra xem nó là 0 hay 1.

Ý tưởng là, chúng tôi có thể duy trì sự F(x)đồng bộ với x. Trong mỗi lần lặp như vậy, khi chúng ta nhân đôi xvà có thể thêm 1 vào x, chúng ta cũng có thể tính giá trị mới F(x)bằng cách sử dụng giá trị trước đó của F(x)F(x+1), với các phương trình trên.

Vì số lần lặp sẽ tính theo logarit n, nên tổng (số lớn) các phép toán cũng theo logarit n.

Để biết thêm chi tiết, vui lòng tham khảo phần "Thuật toán cải tiến" của bài viết này .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.