it-swarm.asia

Kompleksitas komputasi dari Urutan Fibonacci

Saya mengerti notasi Big-O, tapi saya tidak tahu bagaimana cara menghitungnya untuk banyak fungsi. Secara khusus, saya telah mencoba mencari kompleksitas komputasi dari versi naif dari deret Fibonacci:

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

Apa kompleksitas komputasi dari deret Fibonacci dan bagaimana cara menghitungnya?

282
Juliet

Cukup tanyakan pada diri sendiri berapa banyak pernyataan yang harus dieksekusi untuk F(n) untuk diselesaikan.

Untuk F(1), jawabannya adalah 1 (bagian pertama dari kondisional).

Untuk F(n), jawabannya adalah F(n-1) + F(n-2).

Jadi fungsi apa yang memenuhi aturan ini? Coba an (a> 1):

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

Membagi melalui a(n-2):

sebuah2 == a +1

Pecahkan a dan Anda mendapatkan (1+sqrt(5))/2 = 1.6180339887, atau dikenal sebagai rasio emas .

Jadi butuh waktu yang eksponensial.

114
Jason Cohen

Ada diskusi yang sangat bagus tentang masalah ini khusus di MIT . Pada halaman 5, mereka menyatakan bahwa, jika Anda menganggap bahwa penambahan membutuhkan satu unit komputasi, waktu yang diperlukan untuk menghitung Fib (N) sangat terkait erat dengan hasil Fib (N).

Sebagai hasilnya, Anda dapat melompat langsung ke perkiraan yang sangat dekat dari seri Fibonacci:

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

dan mengatakan, oleh karena itu, bahwa kinerja kasus terburuk dari algoritma naif adalah 

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

PS: Ada diskusi tentang ekspresi bentuk tertutup dari angka Fibonacci N lebih di Wikipedia jika Anda ingin informasi lebih lanjut.

28
Bob Cross

Saya setuju dengan pgaur dan rickerbh, kompleksitas Recursive-fibonacci adalah O (2 ^ n).

Saya sampai pada kesimpulan yang sama dengan alasan yang agak sederhana tetapi saya percaya masih valid.

Pertama, ini semua tentang mencari tahu berapa kali fungsi fibonacci rekursif (F() mulai sekarang) dipanggil saat menghitung nomor Nth fibonacci. Jika dipanggil sekali per angka dalam urutan 0 hingga n, maka kita memiliki O (n), jika dipanggil n kali untuk setiap angka, maka kita mendapatkan O (n * n), atau O (n ^ 2), dan seterusnya.

Jadi, ketika F() dipanggil untuk bilangan n, berapa kali F() dipanggil untuk bilangan tertentu antara 0 dan n-1 bertambah ketika kita mendekati 0.

Sebagai kesan pertama, menurut saya jika kita meletakkannya secara visual, menggambar satu unit per kali F() dipanggil dengan angka tertentu, basah mendapatkan semacam bentuk piramida (yaitu, jika kami memusatkan unit secara horizontal). Sesuatu seperti ini:

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

Sekarang, pertanyaannya adalah, seberapa cepat dasar piramida ini membesar saat n tumbuh?

Mari kita ambil contoh nyata, misalnya F (6)

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

Kita melihat F(0) dipanggil 32 kali, yaitu 2 ^ 5, yang untuk kasus sampel ini adalah 2 ^ (n-1).

Sekarang, kita ingin tahu berapa kali F(x) dipanggil sama sekali, dan kita dapat melihat berapa kali F(0) dipanggil hanya sebagian saja. 

Jika kita secara mental memindahkan semua tanda * dari F(6) ke F(2) ke garis F(1), kita melihat bahwa F(1) dan garis F(0) sekarang sama dengan panjangnya. Yang berarti, total waktu F() dipanggil ketika n = 6 adalah 2x32 = 64 = 2 ^ 6.

Sekarang, dalam hal kompleksitas:

O( F(6) ) = O(2^6)
O( F(n) ) = O(2^n)
26
J.P.

Itu dibatasi pada ujung bawah oleh 2^(n/2) dan pada ujung atas oleh 2 ^ n (seperti yang disebutkan dalam komentar lain). Dan fakta yang menarik dari implementasi rekursif adalah bahwa ia memiliki ikatan Fib (n) asimptotik yang ketat. Fakta-fakta ini dapat diringkas:

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

Ikatan yang rapat dapat dikurangi lebih jauh menggunakan bentuk tertutup jika Anda mau.

9
Dave L.

Jawaban buktinya bagus, tetapi saya selalu harus melakukan beberapa iterasi dengan tangan untuk meyakinkan diri saya sendiri. Jadi saya menggambar pohon panggilan kecil di papan tulis saya, dan mulai menghitung node. Saya membagi jumlah saya menjadi simpul total, simpul daun, dan simpul interior. Inilah yang saya dapatkan:

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

Apa yang segera melompat keluar adalah bahwa jumlah node daun adalah fib(n). Apa yang memerlukan beberapa iterasi lagi untuk diperhatikan adalah bahwa jumlah node interior adalah fib(n) - 1. Oleh karena itu jumlah total node adalah 2 * fib(n) - 1.

Karena Anda menjatuhkan koefisien saat mengklasifikasikan kompleksitas komputasi, jawaban terakhir adalah θ(fib(n)).

9
benkc

Kompleksitas waktu algoritma rekursif dapat diperkirakan dengan lebih baik dengan menggambar pohon rekursi, Dalam hal ini relasi perulangan untuk menggambar pohon rekursi adalah T (n) = T (n-1) + T (n-2) + O (1) perhatikan bahwa setiap langkah membutuhkan O(1) yang berarti waktu konstan, karena hanya satu perbandingan untuk memeriksa nilai n dalam jika blok. Pohon Pengembalian akan terlihat seperti

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

Di sini katakanlah setiap tingkat pohon di atas dilambangkan dengan i Karenanya,

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)

katakanlah pada nilai tertentu dari i, ujung pohon, kasus itu adalah ketika ni = 1, maka i = n-1, yang berarti bahwa ketinggian pohon adalah n-1 . Sekarang mari kita lihat berapa banyak pekerjaan yang dilakukan dilakukan untuk setiap n lapisan dalam pohon. Perhatikan bahwa setiap langkah membutuhkan O(1) waktu seperti yang dinyatakan dalam relasi perulangan.

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

karena i = n-1 adalah ketinggian pekerjaan pohon yang dilakukan pada setiap level

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

Karenanya total pekerjaan yang dilakukan adalah jumlah pekerjaan yang dilakukan pada setiap level, maka itu akan menjadi 2 ^ 0 + 2 ^ 1 + 2 ^ 2 + 2 ^ 3 ... + 2 ^ (n-1) karena i = n-1. Dengan deret geometri jumlah ini adalah 2 ^ n, maka total kompleksitas waktu di sini adalah O (2 ^ n)

5
nikhil kekan
1
nsh3

Versi rekursi naif dari Fibonacci adalah eksponensial dengan desain karena pengulangan dalam perhitungan:

Pada dasarnya Anda menghitung:

F(n) depends on F(n-1) and F(n-2)

F(n-1) depends on F(n-2) again and F(n-3)

F(n-2) depends on F(n-3) again and F(n-4)

maka Anda mengalami di setiap level 2 panggilan rekursif yang membuang banyak data dalam perhitungan, fungsi waktu akan terlihat seperti ini:

T (n) = T(n-1) + T(n-2) + C, dengan konstanta C

T (n-1) = T(n-2) + T(n-3)> T(n-2) lalu

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

...

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

Ini hanya batas bawah yang untuk keperluan analisis Anda harus cukup tetapi fungsi real time adalah faktor konstanta dengan rumus Fibonacci yang sama dan bentuk tertutup dikenal eksponensial dari rasio emas.

Selain itu, Anda dapat menemukan versi Fibonacci yang dioptimalkan menggunakan pemrograman dinamis seperti ini:

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];
}

Itu dioptimalkan dan hanya melakukan langkah-langkah n tetapi juga eksponensial.

Fungsi biaya ditentukan dari ukuran Input hingga jumlah langkah untuk menyelesaikan masalah. Saat Anda melihat versi dinamis Fibonacci (n langkah-langkah untuk menghitung tabel) atau algoritma termudah untuk mengetahui apakah suatu bilangan prima (sqrt (n) untuk menganalisis pembagi angka yang valid dari bilangan tersebut). Anda mungkin berpikir bahwa algoritme ini adalah O(n) atau O(sqrt(n)) tetapi ini tidak benar karena alasan berikut: input ke algoritme Anda adalah angka: n, menggunakan notasi biner ukuran input untuk integer n adalah log2 (n) lalu melakukan perubahan variabel dari

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

mari cari tahu jumlah langkah sebagai fungsi dari ukuran input

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

maka biaya algoritma Anda sebagai fungsi dari ukuran input adalah:

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

dan inilah mengapa biayanya eksponensial.

0
Miguel