Độ phức tạp tính toán của chuỗi Fibonacci


330

Tôi hiểu ký hiệu Big-O, nhưng tôi không biết cách tính toán cho nhiều chức năng. Cụ thể, tôi đã cố gắng tìm ra mức độ phức tạp tính toán của phiên bản ngây thơ của chuỗi Fibonacci:

int Fibonacci(int n)
{
    if (n <= 1)
        return n;
    else
        return Fibonacci(n - 1) + Fibonacci(n - 2);
}

Độ phức tạp tính toán của chuỗi Fibonacci là gì và nó được tính như thế nào?



3
Xem phần biểu mẫu ma trận tại đây: en.wikipedia.org/wiki/Fiborie_number . bằng cách thực hiện ma trận này ^ n (một cách thông minh), bạn có thể tính toán Fib (n) theo O (lg n). Bí quyết là trong việc thực hiện chức năng quyền lực. Có một bài giảng rất hay trên iTunesU về vấn đề chính xác này và cách giải quyết trong O (lg n). Khóa học giới thiệu các thuật toán từ bài giảng 3 của MIT (nó hoàn toàn miễn phí, vì vậy hãy kiểm tra nếu bạn quan tâm)
Aly

1
Cả hai ý kiến ​​trên đều không giải quyết được câu hỏi, đó là về độ phức tạp tính toán của phiên bản ngây thơ (trong mã được đăng), không phải về các phiên bản thông minh hơn như dạng ma trận hoặc tính toán không đệ quy.
Josh Milthorpe

Một video rất hay ở đây nói về cả độ phức tạp giới hạn dưới (2 ^ n / 2) và độ phức tạp giới hạn trên (2 ^ n) của việc thực hiện đệ quy.
RBT

1
Một truy vấn phụ: Việc triển khai chuỗi Fibonacci ngây thơ nên được lặp lại hay đệ quy ?
RBT

Câu trả lời:


374

Bạn mô hình hàm thời gian để tính Fib(n)là tổng thời gian để tính Fib(n-1)cộng với thời gian để tính Fib(n-2)cộng với thời gian để cộng chúng lại với nhau ( O(1)). Điều này giả định rằng các đánh giá lặp đi lặp lại giống nhau Fib(n)mất cùng thời gian - tức là không sử dụng ghi nhớ.

T(n<=1) = O(1)

T(n) = T(n-1) + T(n-2) + O(1)

Bạn giải quyết mối quan hệ lặp lại này (ví dụ: sử dụng các hàm tạo) và bạn sẽ kết thúc bằng câu trả lời.

Ngoài ra, bạn có thể vẽ cây đệ quy, sẽ có chiều sâu nvà trực giác nhận ra rằng hàm này không có triệu chứngO(2n) . Sau đó bạn có thể chứng minh phỏng đoán của mình bằng cảm ứng.

Căn cứ: n = 1 là hiển nhiên

Giả sử , do đóT(n-1) = O(2n-1)

T(n) = T(n-1) + T(n-2) + O(1) bằng với

T(n) = O(2n-1) + O(2n-2) + O(1) = O(2n)

Tuy nhiên, như đã lưu ý trong một bình luận, đây không phải là ràng buộc chặt chẽ. Một sự thật thú vị về chức năng này là T (n) không có giá trị giống như giá trị của Fib(n)cả hai vì được định nghĩa là

f(n) = f(n-1) + f(n-2).

Các lá của cây đệ quy sẽ luôn trả về 1. Giá trị Fib(n)là tổng của tất cả các giá trị được trả về bởi các lá trong cây đệ quy bằng với số lượng lá. Vì mỗi lá sẽ lấy O (1) để tính, T(n)bằng Fib(n) x O(1). Do đó, ràng buộc chặt chẽ cho chức năng này là chính chuỗi Fibonacci (~ ). Bạn có thể tìm hiểu ràng buộc chặt chẽ này bằng cách sử dụng các hàm tạo như tôi đã đề cập ở trên.θ(1.6n)


29
Cũng chứng minh bằng cảm ứng. Đẹp. +1
Andrew Rollings

Mặc dù ràng buộc không chặt chẽ.
Thuyền trưởng Segfault

@Captain Segfault: Vâng. Tôi làm rõ câu trả lời. Bạn sẽ có được ràng buộc chặt chẽ bằng phương pháp GF như tôi đã viết ở trên.
Mehrdad Afshari

Nó lấy StackOverflowException của bạn như một trò đùa. Thời gian theo cấp số nhân có thể nhận thấy khá dễ dàng với các giá trị khá nhỏ cho n.
David Rodríguez - dribeas

1
"Ngoài ra, bạn có thể vẽ cây đệ quy, sẽ có độ sâu n và trực giác chỉ ra rằng hàm này không có triệu chứng O (2n)." - Điều này là hoàn toàn sai. Độ phức tạp thời gian là O (yellow_ratio ^ n). Nó không bao giờ đến gần O (2 ^ n). Nếu bạn có thể vươn tới vô cùng, nó sẽ tiến gần đến O (yellow_ratio ^ n). Đó là những gì một tiệm cận là, khoảng cách giữa hai dòng phải đạt tới 0.
bob

133

Chỉ cần tự hỏi có bao nhiêu câu lệnh cần thực hiện F(n)để hoàn thành.

Đối với F(1), câu trả lời là 1(phần đầu tiên của điều kiện).

Đối với F(n), câu trả lời là F(n-1) + F(n-2).

Vậy chức năng nào thỏa mãn các quy tắc này? Hãy thử một n (a> 1):

a n == a (n-1) + a (n-2)

Chia cho a (n-2) :

a 2 == a + 1

Giải quyết avà bạn nhận được (1+sqrt(5))/2 = 1.6180339887, còn được gọi là tỷ lệ vàng .

Vì vậy, nó cần có thời gian theo cấp số nhân.


8
Chứng minh bằng cảm ứng. Đẹp. +1
Andrew Rollings

2
30 upvote cho một câu trả lời sai? :-) Nó có tuân theo 1 = F (1) = (1 + sqrt (5)) / 2 không? Và những gì về giải pháp khác, (1-sqrt (5)) / 2?
Carsten S

1
Không, 1 không bằng 1 + 1. Hàm làm hài lòng các quy tắc đó được đề cập trong câu hỏi.
molbdnilo

6
Câu trả lời không sai. Nó không có triệu chứng. Các giải pháp khác là tiêu cực vì vậy không có ý nghĩa về mặt vật lý.
Da Teng

10
Ai đó có thể giải thích làm thế nào a ^ n == a ^ (n-1) + a ^ (n-2) thỏa mãn các quy tắc này không? Làm thế nào là nó hài lòng chính xác, xin vui lòng được cụ thể.
thẳng thắn

33

Tôi đồng ý với pgaur và rickerbh, độ phức tạp của đệ quy là O (2 ^ n).

Tôi đi đến kết luận tương tự bởi một lý do khá đơn giản nhưng tôi tin rằng vẫn còn lý do hợp lệ.

Đầu tiên, tất cả chỉ là tìm hiểu xem hàm đệ quy (F () kể từ bây giờ) được gọi bao nhiêu lần khi tính số Nth Dailymotion. Nếu nó được gọi một lần cho mỗi số trong dãy 0 đến n, thì chúng ta có O (n), nếu nó được gọi n lần cho mỗi số, thì chúng ta nhận được O (n * n) hoặc O (n ^ 2), và như thế.

Vì vậy, khi F () được gọi cho một số n, số lần F () được gọi cho một số đã cho trong khoảng từ 0 đến n-1 tăng lên khi chúng ta tiếp cận 0.

Theo ấn tượng đầu tiên, đối với tôi, nếu chúng ta đặt nó theo cách trực quan, vẽ một đơn vị mỗi lần F () được gọi cho một số đã cho, ướt sẽ có một hình dạng kim tự tháp (nghĩa là, nếu chúng ta căn giữa các đơn vị theo chiều ngang ). Một cái gì đó như thế này:

n              *
n-1            **
n-2           ****  
...
2           ***********
1       ******************
0    ***************************

Bây giờ, câu hỏi là, cơ sở của kim tự tháp này mở rộng nhanh như thế nào khi n phát triển?

Hãy lấy một trường hợp thực tế, ví dụ F (6)

F(6)                 *  <-- only once
F(5)                 *  <-- only once too
F(4)                 ** 
F(3)                ****
F(2)              ********
F(1)          ****************           <-- 16
F(0)  ********************************    <-- 32

Chúng ta thấy F (0) được gọi 32 lần, tức là 2 ^ 5, đối với trường hợp mẫu này là 2 ^ (n-1).

Bây giờ, chúng tôi muốn biết F (x) được gọi bao nhiêu lần và chúng tôi có thể thấy số lần F (0) được gọi chỉ là một phần trong số đó.

Nếu chúng ta di chuyển tất cả các dòng * từ F (6) sang F (2) thành dòng F (1), chúng ta sẽ thấy rằng các dòng F (1) và F (0) hiện có độ dài bằng nhau. Có nghĩa là, tổng số lần F () được gọi khi n = 6 là 2x32 = 64 = 2 ^ 6.

Bây giờ, về mặt phức tạp:

O( F(6) ) = O(2^6)
O( F(n) ) = O(2^n)

3
F (3) chỉ được gọi 3 lần chứ không phải 4 lần. Kim tự tháp thứ hai là sai.
Avik

2
F (3) = 3, F (2) = 5, F (1) = 8, F (0) = 5. Tôi sẽ sửa nó, nhưng tôi không nghĩ mình có thể cứu vãn câu trả lời này bằng một chỉnh sửa.
Bernhard Barker

31

Có một cuộc thảo luận rất hay về vấn đề cụ thể này tại MIT . Ở trang 5, họ đưa ra quan điểm rằng, nếu bạn giả sử rằng một phép cộng có một đơn vị tính toán, thì thời gian cần thiết để tính Fib (N) có liên quan rất chặt chẽ với kết quả của Fib (N).

Kết quả là, bạn có thể bỏ qua trực tiếp đến xấp xỉ rất gần của chuỗi Fibonacci:

Fib(N) = (1/sqrt(5)) * 1.618^(N+1) (approximately)

và do đó, nói rằng hiệu suất trường hợp xấu nhất của thuật toán ngây thơ là

O((1/sqrt(5)) * 1.618^(N+1)) = O(1.618^(N+1))

Tái bút: Có một cuộc thảo luận về biểu thức dạng đóng của số Fibonacci thứ N tại Wikipedia nếu bạn muốn biết thêm thông tin.


Cảm ơn các liên kết khóa học. Quan sát cũng rất tuyệt
BơiBikeRun

16

Bạn có thể mở rộng nó và có một độ nhớt

     T(n) = T(n-1) + T(n-2) <
     T(n-1) + T(n-1) 

     = 2*T(n-1)   
     = 2*2*T(n-2)
     = 2*2*2*T(n-3)
     ....
     = 2^i*T(n-i)
     ...
     ==> O(2^n)

1
Tôi hiểu dòng đầu tiên. Nhưng tại sao có một ít hơn nhân vật <ở cuối? Làm thế nào bạn có được T(n-1) + T(n-1)?
Quazi Irfan

@QuaziIrfan: D đó là một mũi tên. -> [(không ít hơn). Xin lỗi vì sự nhầm lẫn liên quan đến dòng cuối cùng]. Đối với dòng đầu tiên, tốt ... T(n-1) > T(n-2)Vì vậy, tôi có thể thay đổi T(n-2)và đặt T(n-1). Tôi sẽ chỉ nhận được một ràng buộc cao hơn mà vẫn còn hiệu lực choT(n-1) + T(n-2)
Tony Tannous

10

Nó được giới hạn ở đầu dưới bởi 2^(n/2)và trên đầu trên 2 ^ n (như đã lưu ý trong các bình luận khác). Và một thực tế thú vị của việc thực hiện đệ quy đó là nó có một ràng buộc tiệm cận chặt chẽ của chính Fib (n). Những sự thật có thể được tóm tắt:

T(n) = Ω(2^(n/2))  (lower bound)
T(n) = O(2^n)   (upper bound)
T(n) = Θ(Fib(n)) (tight bound)

Các ràng buộc chặt chẽ có thể được giảm hơn nữa bằng cách sử dụng hình thức đóng của nó nếu bạn muốn.


10

Các câu trả lời bằng chứng là tốt, nhưng tôi luôn phải thực hiện một vài lần lặp lại bằng tay để thực sự thuyết phục bản thân mình. Vì vậy, tôi đã rút ra một cây gọi nhỏ trên bảng trắng của mình và bắt đầu đếm các nút. Tôi chia tổng số của tôi thành tổng số nút, nút lá và nút bên trong. Đây là những gì tôi nhận được:

IN | OUT | TOT | LEAF | INT
 1 |   1 |   1 |   1  |   0
 2 |   1 |   1 |   1  |   0
 3 |   2 |   3 |   2  |   1
 4 |   3 |   5 |   3  |   2
 5 |   5 |   9 |   5  |   4
 6 |   8 |  15 |   8  |   7
 7 |  13 |  25 |  13  |  12
 8 |  21 |  41 |  21  |  20
 9 |  34 |  67 |  34  |  33
10 |  55 | 109 |  55  |  54

Điều ngay lập tức nhảy ra là số lượng nút lá là fib(n). Điều cần thêm một vài lần lặp lại để nhận thấy là số lượng các nút bên trong là fib(n) - 1. Do đó tổng số nút là 2 * fib(n) - 1.

Vì bạn bỏ các hệ số khi phân loại độ phức tạp tính toán, câu trả lời cuối cùng là θ(fib(n)).


(Không, tôi đã không vẽ một cây gọi đủ sâu 10 lần trên bảng trắng của mình. Chỉ cần 5 độ sâu.);)
benkc

Thật tuyệt, tôi đã tự hỏi có bao nhiêu bổ sung Fib đệ quy đã làm. Nó không chỉ thêm 1vào một lần tích lũy duy nhất Fib(n), mà thú vị là nó vẫn chính xác θ(Fib(n)).
Peter Cordes

Lưu ý rằng một số (hầu hết) các triển khai đệ quy dành thời gian thêm 0, mặc dù: các trường hợp cơ sở đệ quy là 01, bởi vì chúng thực hiện Fib(n-1) + Fib(n-2). Vì vậy, có lẽ là 3 * Fib(n) - 2từ câu trả lời liên kết chỉ này là chính xác hơn cho tổng số nút, không 2 * Fib(n) - 1.
Peter Cordes

Tôi không thể nhận được kết quả tương tự trên các nút lá. Bắt đầu từ 0: F (0) -> 1 lá (chính nó); F (1) -> 1 lá (chính nó); F (2) -> 2 lá (F (1) và F (0)); F (3) -> 3 lá; F (5) -> 8 lá; vv
alexlomba87

9

Độ phức tạp thời gian của thuật toán đệ quy có thể được ước tính tốt hơn bằng cách vẽ cây đệ quy, Trong trường hợp này, mối quan hệ lặp lại để vẽ cây đệ quy sẽ là T (n) = T (n-1) + T (n-2) + O (1) lưu ý rằng mỗi bước lấy O (1) có nghĩa là thời gian không đổi, vì nó chỉ có một so sánh để kiểm tra giá trị của n trong if block.Recursion cây sẽ như thế nào

          n
   (n-1)      (n-2)
(n-2)(n-3) (n-3)(n-4) ...so on

Ở đây cho phép mỗi cấp của cây trên được ký hiệu là i do đó,

i
0                        n
1            (n-1)                 (n-2)
2        (n-2)    (n-3)      (n-3)     (n-4)
3   (n-3)(n-4) (n-4)(n-5) (n-4)(n-5) (n-5)(n-6)

giả sử giá trị cụ thể của i, cây kết thúc, trường hợp đó sẽ là khi ni = 1, do đó i = n-1, có nghĩa là chiều cao của cây là n-1. Bây giờ hãy xem có bao nhiêu công việc được thực hiện cho mỗi n lớp trong cây. Lưu ý rằng mỗi bước mất O (1) thời gian như đã nêu trong quan hệ lặp lại.

2^0=1                        n
2^1=2            (n-1)                 (n-2)
2^2=4        (n-2)    (n-3)      (n-3)     (n-4)
2^3=8   (n-3)(n-4) (n-4)(n-5) (n-4)(n-5) (n-5)(n-6)    ..so on
2^i for ith level

vì i = n-1 là chiều cao của công việc cây được thực hiện ở mỗi cấp sẽ là

i work
1 2^1
2 2^2
3 2^3..so on

Do đó, tổng số công việc được thực hiện sẽ tổng hợp các công việc được thực hiện ở mỗi cấp, do đó sẽ là 2 ^ 0 + 2 ^ 1 + 2 ^ 2 + 2 ^ 3 ... + 2 ^ (n-1) kể từ i = n-1. Theo chuỗi hình học, tổng này là 2 ^ n, do đó tổng độ phức tạp thời gian ở đây là O (2 ^ n)


2

Vâng, theo tôi là O(2^n)như trong chức năng này, chỉ có đệ quy là mất thời gian đáng kể (phân chia và chinh phục). Chúng ta thấy rằng, chức năng trên sẽ tiếp tục trong một cây cho đến khi những chiếc lá đang đến gần khi chúng ta đạt đến cấp độ F(n-(n-1))tức là F(1). Vì vậy, ở đây khi chúng ta ghi lại độ phức tạp thời gian gặp phải ở mỗi độ sâu của cây, chuỗi tổng là:

1+2+4+.......(n-1)
= 1((2^n)-1)/(2-1)
=2^n -1

đó là thứ tự của 2^n [ O(2^n) ].


1

Phiên bản đệ quy ngây thơ của Fibonacci là theo cấp số nhân theo thiết kế do sự lặp lại trong tính toán:

Tại thư mục gốc bạn đang tính toán:

F (n) phụ thuộc vào F (n-1) và F (n-2)

F (n-1) phụ thuộc vào F (n-2) một lần nữa và F (n-3)

F (n-2) phụ thuộc vào F (n-3) một lần nữa và F (n-4)

sau đó bạn đang có ở mỗi cuộc gọi đệ quy cấp 2 đang lãng phí rất nhiều dữ liệu trong phép tính, hàm thời gian sẽ như thế này:

T (n) = T (n-1) + T (n-2) + C, với hằng số C

T (n-1) = T (n-2) + T (n-3)> T (n-2) rồi

T (n)> 2 * T (n-2)

...

T (n)> 2 ^ (n / 2) * T (1) = O (2 ^ (n / 2))

Đây chỉ là một giới hạn thấp hơn cho mục đích phân tích của bạn là đủ nhưng hàm thời gian thực là một yếu tố của hằng số theo cùng một công thức Fibonacci và dạng đóng được biết là theo cấp số nhân của tỷ lệ vàng.

Ngoài ra, bạn có thể tìm thấy các phiên bản tối ưu của Fibonacci bằng cách sử dụng lập trình động như thế này:

static int fib(int n)
{
    /* memory */
    int f[] = new int[n+1];
    int i;

    /* Init */
    f[0] = 0;
    f[1] = 1;

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

    return f[n];
}

Điều đó được tối ưu hóa và chỉ thực hiện n bước nhưng cũng theo cấp số nhân.

Các hàm chi phí được xác định từ Kích thước đầu vào đến số bước để giải quyết vấn đề. Khi bạn thấy phiên bản động của Fibonacci ( n bước để tính toán bảng) hoặc thuật toán dễ nhất để biết liệu một số có phải là số nguyên tố ( sqrt (n) để phân tích các ước số hợp lệ của số đó không. bạn có thể nghĩ rằng các thuật toán này là O (n) hoặc O (sqrt (n)) nhưng điều này đơn giản là không đúng vì lý do sau: Đầu vào cho thuật toán của bạn là một số: n , sử dụng ký hiệu nhị phân kích thước đầu vào cho một số nguyên nlog2 (n) sau đó thực hiện thay đổi biến

m = log2(n) // your real input size

hãy tìm ra số bước như một hàm của kích thước đầu vào

m = log2(n)
2^m = 2^log2(n) = n

thì chi phí cho thuật toán của bạn là một hàm của kích thước đầu vào là:

T(m) = n steps = 2^m steps

và đây là lý do tại sao chi phí là một số mũ.


1

Nó là đơn giản để tính toán bằng cách gọi sơ đồ chức năng. Đơn giản chỉ cần thêm các lệnh gọi hàm cho mỗi giá trị của n và xem cách số tăng lên.

Big O là O (Z ^ n) trong đó Z là tỷ lệ vàng hoặc khoảng 1,62.

Cả số Leonardo và số Fibonacci đều tiếp cận tỷ lệ này khi chúng tôi tăng n.

Không giống như các câu hỏi Big O khác, không có sự thay đổi trong đầu vào và cả thuật toán và việc thực hiện thuật toán đều được xác định rõ ràng.

Không cần cho một loạt các toán học phức tạp. Đơn giản chỉ cần sơ đồ ra các lệnh gọi bên dưới và khớp một hàm với các số.

Hoặc nếu bạn quen thuộc với tỷ lệ vàng, bạn sẽ nhận ra nó như vậy.

Câu trả lời này đúng hơn câu trả lời được chấp nhận, tuyên bố rằng nó sẽ tiếp cận f (n) = 2 ^ n. Nó sẽ không bao giờ Nó sẽ tiếp cận f (n) = yellow_ratio ^ n.

2 (2 -> 1, 0)

4 (3 -> 2, 1) (2 -> 1, 0)

8 (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
            (2 -> 1, 0)


14 (5 -> 4, 3) (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
            (2 -> 1, 0)

            (3 -> 2, 1) (2 -> 1, 0)

22 (6 -> 5, 4)
            (5 -> 4, 3) (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
                        (2 -> 1, 0)

                        (3 -> 2, 1) (2 -> 1, 0)

            (4 -> 3, 2) (3 -> 2, 1) (2 -> 1, 0)
                        (2 -> 1, 0)

1
Bạn có thể cung cấp bất kỳ nguồn nào cho tuyên bố đó về tỷ lệ vàng?
Nico Haase

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.