Big O, bagaimana Anda menghitung / memperkirakannya?

881

Kebanyakan orang dengan gelar di CS pasti akan tahu apa yang Big O adalah singkatan . Ini membantu kita untuk mengukur seberapa baik suatu skala algoritma.

Tapi saya ingin tahu, bagaimana Anda menghitung atau memperkirakan kompleksitas algoritma Anda?

sven
sumber
4
Mungkin Anda sebenarnya tidak perlu meningkatkan kompleksitas algoritme Anda, tetapi Anda setidaknya harus dapat menghitungnya untuk memutuskan ...
Xavier Nodet
5
Saya menemukan penjelasan yang sangat jelas tentang Big O, Big Omega, dan Big Theta: xoax.net/comp/sci/algorithms/Lesson6.php
Sam Dutton
33
-1: Sigh, penyalahgunaan BigOh lainnya. BigOh hanyalah batas atas asimptotik dan dapat digunakan untuk apa saja dan tidak hanya terkait dengan CS. Berbicara tentang BigOh seolah-olah ada satu yang unik tidak ada artinya (Algoritma waktu linier juga O (n ^ 2), O (n ^ 3) dll). Mengatakan itu membantu kita mengukur efisiensi juga menyesatkan. Juga, ada apa dengan tautan ke kelas kompleksitas? Jika Anda tertarik, apakah teknik untuk menghitung waktu berjalan algoritma, bagaimana itu relevan?
34
Big-O tidak mengukur efisiensi; ini mengukur seberapa baik suatu skala algoritma dengan ukuran (itu bisa berlaku untuk hal-hal lain selain ukuran juga tapi itulah yang kami kemungkinan tertarik di sini) - dan itu hanya asimtotik, jadi jika Anda kurang beruntung sebuah algoritma dengan "kecil" besar- O mungkin lebih lambat (jika Big-O berlaku untuk siklus) daripada yang berbeda sampai Anda mencapai angka yang sangat besar.
ILoveFortran
4
Memilih algoritma berdasarkan kompleksitas Big-O-nya biasanya merupakan bagian penting dari desain program. Ini jelas bukan kasus 'optimasi prematur', yang dalam banyak hal merupakan kutipan selektif yang banyak disalahgunakan.
Marquis of Lorne

Jawaban:

1481

Saya akan melakukan yang terbaik untuk menjelaskannya di sini dengan persyaratan yang sederhana, tetapi diingatkan bahwa topik ini membutuhkan siswa saya beberapa bulan untuk akhirnya memahami. Anda dapat menemukan informasi lebih lanjut tentang Bab 2 Struktur Data dan Algoritma di buku Java .


Tidak ada prosedur mekanis yang dapat digunakan untuk mendapatkan BigOh.

Sebagai "buku masak", untuk mendapatkan BigOh dari sepotong kode, pertama-tama Anda harus menyadari bahwa Anda sedang membuat rumus matematika untuk menghitung berapa banyak langkah perhitungan yang dijalankan dengan input ukuran tertentu.

Tujuannya sederhana: untuk membandingkan algoritma dari sudut pandang teoretis, tanpa perlu mengeksekusi kode. Semakin sedikit jumlah langkah, semakin cepat algoritma.

Misalnya, katakan Anda memiliki kode ini:

int sum(int* data, int N) {
    int result = 0;               // 1

    for (int i = 0; i < N; i++) { // 2
        result += data[i];        // 3
    }

    return result;                // 4
}

Fungsi ini mengembalikan jumlah semua elemen array, dan kami ingin membuat rumus untuk menghitung kompleksitas komputasi dari fungsi itu:

Number_Of_Steps = f(N)

Jadi kita punya f(N), fungsi untuk menghitung jumlah langkah komputasi. Input dari fungsi adalah ukuran struktur untuk diproses. Ini berarti bahwa fungsi ini disebut seperti:

Number_Of_Steps = f(data.length)

Parameter Nmengambil data.lengthnilainya. Sekarang kita membutuhkan definisi fungsi yang sebenarnya f(). Ini dilakukan dari kode sumber, di mana setiap baris yang menarik diberi nomor dari 1 hingga 4.

Ada banyak cara untuk menghitung BigOh. Dari titik ini ke depan kita akan mengasumsikan bahwa setiap kalimat yang tidak tergantung pada ukuran data input mengambil konstantaC langkah perhitungan angka .

Kita akan menambahkan jumlah individu dari langkah-langkah fungsi, dan deklarasi variabel lokal atau pernyataan pengembalian tidak tergantung pada ukuran dataarray.

Itu berarti bahwa baris 1 dan 4 masing-masing mengambil jumlah langkah C, dan fungsinya agak seperti ini:

f(N) = C + ??? + C

Bagian selanjutnya adalah mendefinisikan nilai forpernyataan. Ingat bahwa kami menghitung jumlah langkah komputasi, yang berarti bahwa tubuh forpernyataan dieksekusi Nkali. Itu sama dengan menambahkan C, Nkali:

f(N) = C + (C + C + ... + C) + C = C + N * C + C

Tidak ada aturan mekanis untuk menghitung berapa kali tubuh fordieksekusi, Anda perlu menghitungnya dengan melihat apa yang dilakukan kode. Untuk menyederhanakan perhitungan, kami mengabaikan inisialisasi variabel, kondisi dan bagian kenaikan darifor pernyataan.

Untuk mendapatkan BigOh yang sebenarnya, kita perlu analisis fungsi asimptotik . Ini kira-kira dilakukan seperti ini:

  1. Singkirkan semua konstanta C.
  2. Dari f()dapatkan polinomium di dalamnya standard form.
  3. Bagilah persyaratan polinomium dan urutkan berdasarkan laju pertumbuhan.
  4. Usahakan yang tumbuh lebih besar saat Nmendekati infinity.

Kami f()memiliki dua istilah:

f(N) = 2 * C * N ^ 0 + 1 * C * N ^ 1

Mengambil semua Ckonstanta dan bagian yang berlebihan:

f(N) = 1 + N ^ 1

Karena istilah terakhir adalah yang tumbuh lebih besar ketika f()mendekati tak terhingga (pikirkan batas ) ini adalah argumen BigOh, dan sum()fungsinya memiliki BigOh dari:

O(N)

Ada beberapa trik untuk menyelesaikan beberapa trik rumit: gunakan penjumlahan kapan saja Anda bisa.

Sebagai contoh, kode ini dapat dipecahkan dengan mudah menggunakan penjumlahan:

for (i = 0; i < 2*n; i += 2) {  // 1
    for (j=n; j > i; j--) {     // 2
        foo();                  // 3
    }
}

Hal pertama yang perlu Anda tanyakan adalah urutan eksekusi foo(). Sementara yang biasa terjadi O(1), Anda perlu bertanya kepada profesor Anda tentang hal itu. O(1)berarti (hampir, sebagian besar) konstan C, tidak tergantung ukuran N.

The forpernyataan di satu nomor kalimat rumit. Sementara indeks berakhir pada 2 * N, kenaikan dilakukan oleh dua. Itu berarti bahwa langkah pertama forhanya dijalankan N, dan kita perlu membagi hitungannya menjadi dua.

f(N) = Summation(i from 1 to 2 * N / 2)( ... ) = 
     = Summation(i from 1 to N)( ... )

Kalimat nomor dua bahkan lebih rumit karena tergantung pada nilai i. Lihatlah: indeks i mengambil nilai-nilai: 0, 2, 4, 6, 8, ..., 2 * N, dan yang kedua fordijalankan: N kali yang pertama, N - 2 yang kedua, N - 4 yang ketiga ... sampai ke tahap N / 2, di mana yang kedua fortidak pernah dieksekusi.

Pada formula, itu berarti:

f(N) = Summation(i from 1 to N)( Summation(j = ???)(  ) )

Sekali lagi, kami menghitung jumlah langkah . Dan menurut definisi, setiap penjumlahan harus selalu dimulai dari satu, dan diakhiri dengan angka yang lebih besar atau sama dengan satu.

f(N) = Summation(i from 1 to N)( Summation(j = 1 to (N - (i - 1) * 2)( C ) )

(Kami menganggap itu foo()adalah O(1)dan mengambilC langkah-langkah.)

Kami memiliki masalah di sini: ketika imengambil nilai N / 2 + 1ke atas, Penjumlahan batin berakhir pada angka negatif! Itu tidak mungkin dan salah. Kita perlu membagi penjumlahan menjadi dua, menjadi titik penting saat idibutuhkan N / 2 + 1.

f(N) = Summation(i from 1 to N / 2)( Summation(j = 1 to (N - (i - 1) * 2)) * ( C ) ) + Summation(i from 1 to N / 2) * ( C )

Sejak momen penting i > N / 2, batinfor tidak akan dieksekusi, dan kami mengasumsikan kompleksitas eksekusi C konstan pada tubuhnya.

Sekarang penjumlahan dapat disederhanakan menggunakan beberapa aturan identitas:

  1. Penjumlahan (w dari 1 ke N) (C) = N * C
  2. Penjumlahan (w dari 1 ke N) (A (+/-) B) = Penjumlahan (w dari 1 ke N) (A) (+/-) Penjumlahan (w dari 1 ke N) (B)
  3. Penjumlahan (w dari 1 ke N) (w * C) = C * Penjumlahan (w dari 1 ke N) (w) (C adalah konstanta, independen dari w )
  4. Penjumlahan (w dari 1 ke N) (w) = (N * (N + 1)) / 2

Menerapkan beberapa aljabar:

f(N) = Summation(i from 1 to N / 2)( (N - (i - 1) * 2) * ( C ) ) + (N / 2)( C )

f(N) = C * Summation(i from 1 to N / 2)( (N - (i - 1) * 2)) + (N / 2)( C )

f(N) = C * (Summation(i from 1 to N / 2)( N ) - Summation(i from 1 to N / 2)( (i - 1) * 2)) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - 2 * Summation(i from 1 to N / 2)( i - 1 )) + (N / 2)( C )

=> Summation(i from 1 to N / 2)( i - 1 ) = Summation(i from 1 to N / 2 - 1)( i )

f(N) = C * (( N ^ 2 / 2 ) - 2 * Summation(i from 1 to N / 2 - 1)( i )) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - 2 * ( (N / 2 - 1) * (N / 2 - 1 + 1) / 2) ) + (N / 2)( C )

=> (N / 2 - 1) * (N / 2 - 1 + 1) / 2 = 

   (N / 2 - 1) * (N / 2) / 2 = 

   ((N ^ 2 / 4) - (N / 2)) / 2 = 

   (N ^ 2 / 8) - (N / 4)

f(N) = C * (( N ^ 2 / 2 ) - 2 * ( (N ^ 2 / 8) - (N / 4) )) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - ( (N ^ 2 / 4) - (N / 2) )) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - (N ^ 2 / 4) + (N / 2)) + (N / 2)( C )

f(N) = C * ( N ^ 2 / 4 ) + C * (N / 2) + C * (N / 2)

f(N) = C * ( N ^ 2 / 4 ) + 2 * C * (N / 2)

f(N) = C * ( N ^ 2 / 4 ) + C * N

f(N) = C * 1/4 * N ^ 2 + C * N

Dan BigOh adalah:

O(N²)
vz0
sumber
6
@arthur Itu akan menjadi O (N ^ 2) karena Anda akan memerlukan satu loop untuk membaca semua kolom dan satu untuk membaca semua baris kolom tertentu.
Abhishek Dey Das
@arthur: Tergantung. Di O(n)sinilah njumlah elemen, atau di O(x*y)mana xdan ymerupakan dimensi array. Besar-oh adalah "relatif terhadap input", jadi itu tergantung pada apa input Anda.
Mooing Duck
1
Jawaban yang bagus, tetapi saya benar-benar mandek. Bagaimana Penjumlahan (i dari 1 menjadi N / 2) (N) berubah menjadi (N ^ 2/2)?
Parsa
2
@ParsaAkbari Sebagai aturan umum, jumlah (i dari 1 ke a) (b) adalah a * b. Ini hanyalah cara lain untuk mengatakan b + b + ... (a kali) + b = a * b (menurut definisi untuk beberapa definisi perkalian bilangan bulat).
Mario Carneiro
Tidak begitu relevan, tetapi hanya untuk menghindari kebingungan, ada kesalahan kecil dalam kalimat ini: "indeks i mengambil nilai: 0, 2, 4, 6, 8, ..., 2 * N". Indeks saya benar-benar naik ke 2 * N - 2, loop akan berhenti kemudian.
Albert
201

Big O memberikan batas atas untuk kompleksitas waktu suatu algoritma. Ini biasanya digunakan bersamaan dengan pemrosesan set data (daftar) tetapi dapat digunakan di tempat lain.

Beberapa contoh bagaimana ini digunakan dalam kode C.

Katakanlah kita memiliki array n elemen

int array[n];

Jika kita ingin mengakses elemen pertama array, ini akan menjadi O (1) karena tidak peduli seberapa besar array, selalu dibutuhkan waktu konstan yang sama untuk mendapatkan item pertama.

x = array[0];

Jika kami ingin menemukan nomor dalam daftar:

for(int i = 0; i < n; i++){
    if(array[i] == numToFind){ return i; }
}

Ini akan menjadi O (n) karena paling banyak kita harus melihat seluruh daftar untuk menemukan nomor kita. Big-O masih O (n) meskipun kita mungkin menemukan nomor kita yang pertama kali coba dan jalankan melalui loop sekali karena Big-O menggambarkan batas atas untuk suatu algoritma (omega untuk batas bawah dan theta untuk batas ketat) .

Ketika kita mendapatkan loop bersarang:

for(int i = 0; i < n; i++){
    for(int j = i; j < n; j++){
        array[j] += 2;
    }
}

Ini adalah O (n ^ 2) karena untuk setiap lintasan loop luar (O (n)) kita harus melalui seluruh daftar lagi sehingga n melipatgandakan kita dengan n kuadrat.

Ini nyaris tidak menggores permukaan tetapi ketika Anda bisa menganalisis algoritma yang lebih kompleks matematika kompleks yang melibatkan bukti ikut bermain. Semoga ini membiasakan Anda dengan dasar-dasar setidaknya.

DShook
sumber
Penjelasan hebat! Jadi jika seseorang mengatakan algoritmanya memiliki kompleksitas O (n ^ 2), apakah itu berarti ia akan menggunakan loop bersarang?
Navaneeth KN
2
Tidak benar-benar, aspek yang mengarah ke n kali kuadrat akan dianggap sebagai n ^ 2
asyncwait
@NavaneethKN: Anda tidak akan selalu melihat loop bersarang, karena panggilan fungsi dapat> O(1)bekerja sendiri. Dalam C API standar misalnya, bsearchsecara inheren O(log n), strlenadalah O(n), dan qsortmerupakan O(n log n)(secara teknis tidak memiliki jaminan, dan quicksort sendiri memiliki kompleksitas kasus terburuk dari O(n²), tetapi dengan asumsi Anda libcpenulis tidak tolol, kompleksitas kasus rata-rata adalah O(n log n)dan menggunakan strategi pemilihan pivot yang mengurangi kemungkinan memukul O(n²)kasus). Dan keduanya bsearchdanqsort bisa lebih buruk jika fungsi komparator bersifat patologis.
ShadowRanger
95

Meskipun mengetahui cara mengetahui waktu O Besar untuk masalah khusus Anda bermanfaat, mengetahui beberapa kasus umum dapat membantu Anda membuat keputusan dalam algoritme Anda.

Berikut adalah beberapa kasus yang paling umum, diangkat dari http://en.wikipedia.org/wiki/Big_O_notation#Orders_of_common_functions :

O (1) - Menentukan apakah suatu bilangan genap atau ganjil; menggunakan tabel pencarian ukuran konstan atau tabel hash

O (logn) - Menemukan item dalam array yang diurutkan dengan pencarian biner

O (n) - Menemukan item dalam daftar yang tidak disortir; menambahkan dua angka n-digit

O (n 2 ) - Mengalikan dua angka n-digit dengan algoritma sederhana; menambahkan dua n × n matriks; semacam gelembung atau jenis sisipan

O (n 3 ) - Mengalikan dua n × n matriks dengan algoritma sederhana

O (c n ) - Menemukan solusi (tepat) untuk masalah salesman keliling menggunakan pemrograman dinamis; menentukan apakah dua pernyataan logis setara dengan menggunakan brute force

O (n!) - Memecahkan masalah salesman keliling melalui pencarian brute-force

Di n ) - Sering digunakan alih-alih O (n!) Untuk mendapatkan formula yang lebih sederhana untuk kompleksitas asimptotik

Giovanni Galbo
sumber
Mengapa tidak digunakan x&1==1untuk memeriksa keanehan?
Samy Bencherif
2
@ SamyBencherif: Itu akan menjadi cara khas untuk memeriksa (sebenarnya, pengujian saja x & 1sudah cukup, tidak perlu memeriksa == 1; di C, x&1==1dievaluasi sebagai x&(1==1) berkat prioritas operator , jadi sebenarnya sama dengan pengujian x&1). Saya pikir Anda salah membaca jawabannya; ada titik koma di sana, bukan koma. Itu tidak mengatakan Anda akan membutuhkan tabel pencarian untuk pengujian genap / ganjil, itu mengatakan baik pengujian genap / ganjil dan memeriksa tabel pencarian adalah O(1)operasi.
ShadowRanger
Saya tidak tahu tentang klaim penggunaan dalam kalimat terakhir, tetapi siapa yang melakukan itu mengganti kelas dengan yang lain yang tidak setara. Kelas O (n!) Berisi, tetapi benar-benar lebih besar dari O (n ^ n). Persamaan aktualnya adalah O (n!) = O (n ^ ne ^ {- n} sqrt (n)).
conditionalMethod
43

Pengingat kecil: big Onotasi digunakan untuk menunjukkan kompleksitas asimptotik (yaitu, ketika ukuran masalah tumbuh hingga tak terbatas), dan menyembunyikan konstanta.

Ini berarti bahwa antara suatu algoritma dalam O (n) dan satu dalam O (n 2 ), yang tercepat tidak selalu yang pertama (meskipun selalu ada nilai n sehingga untuk masalah ukuran> n, algoritma pertama adalah Tercepat).

Perhatikan bahwa konstanta tersembunyi sangat tergantung pada implementasinya!

Juga, dalam beberapa kasus, runtime bukan fungsi deterministik dari ukuran n input. Ambil pengurutan menggunakan pengurutan cepat misalnya: waktu yang diperlukan untuk mengurutkan array dari elemen n tidak konstan tetapi tergantung pada konfigurasi awal array.

Ada kompleksitas waktu yang berbeda:

  • Kasus terburuk (biasanya yang paling mudah diketahui, meskipun tidak selalu sangat berarti)
  • Kasus rata-rata (biasanya jauh lebih sulit untuk diketahui ...)

  • ...

Pengantar yang baik adalah Pengantar Analisis Algoritma oleh R. Sedgewick dan P. Flajolet.

Seperti yang Anda katakan,, premature optimisation is the root of all evildan (jika mungkin) pembuatan profil harus selalu digunakan ketika mengoptimalkan kode. Bahkan dapat membantu Anda menentukan kompleksitas algoritma Anda.

Tiram
sumber
3
Dalam matematika, O (.) Berarti batas atas, dan theta (.) Berarti Anda memiliki batas atas dan bawah. Apakah definisi sebenarnya berbeda dalam CS, atau hanya penyalahgunaan notasi? Menurut definisi matematika, sqrt (n) adalah O (n) dan O (n ^ 2) sehingga tidak selalu ada beberapa n yang sesudahnya fungsi O (n) lebih kecil.
Douglas Zare
28

Melihat jawabannya di sini saya pikir kita dapat menyimpulkan bahwa sebagian besar dari kita memang mendekati urutan algoritma dengan melihatnya dan menggunakan akal sehat alih-alih menghitungnya dengan, misalnya, metode master seperti yang kita pikirkan di universitas. Dengan mengatakan itu saya harus menambahkan bahwa bahkan profesor mendorong kita (kemudian) untuk benar-benar memikirkannya , bukan hanya menghitungnya.

Saya juga ingin menambahkan bagaimana hal itu dilakukan untuk fungsi rekursif :

misalkan kita memiliki fungsi seperti ( kode skema ):

(define (fac n)
    (if (= n 0)
        1
            (* n (fac (- n 1)))))

yang secara rekursif menghitung faktorial dari angka yang diberikan.

Langkah pertama adalah mencoba dan menentukan karakteristik kinerja untuk tubuh fungsi saja dalam hal ini, tidak ada yang istimewa yang dilakukan dalam tubuh, hanya perkalian (atau kembalinya nilai 1).

Jadi kinerja untuk tubuh adalah: O (1) (konstan).

Selanjutnya coba dan tentukan ini untuk jumlah panggilan rekursif . Dalam hal ini kami memiliki panggilan rekursif n-1.

Jadi kinerja untuk panggilan rekursif adalah: O (n-1) (urutan n, karena kami membuang bagian-bagian yang tidak penting).

Kemudian satukan keduanya dan Anda kemudian memiliki kinerja untuk seluruh fungsi rekursif:

1 * (n-1) = O (n)


Peter , untuk menjawab masalah Anda yang diangkat; metode yang saya jelaskan di sini sebenarnya menangani ini dengan cukup baik. Tetapi perlu diingat bahwa ini masih merupakan perkiraan dan bukan jawaban yang benar secara matematis sepenuhnya. Metode yang dijelaskan di sini juga merupakan salah satu metode yang kami ajarkan di universitas, dan jika saya ingat dengan benar digunakan untuk algoritma yang jauh lebih maju daripada faktorial yang saya gunakan dalam contoh ini.
Tentu saja itu semua tergantung pada seberapa baik Anda dapat memperkirakan waktu berjalan dari fungsi dan jumlah panggilan rekursif, tetapi itu juga berlaku untuk metode lainnya.

sven
sumber
Sven, saya tidak yakin bahwa cara Anda menilai kompleksitas fungsi rekursif akan bekerja untuk yang lebih kompleks, seperti melakukan pencarian / penjumlahan dari atas ke bawah / sesuatu di pohon biner. Tentu, Anda bisa memberi alasan tentang contoh sederhana dan memberikan jawabannya. Tetapi saya pikir Anda harus benar-benar melakukan beberapa matematika untuk yang rekursif?
Peteter
3
+1 untuk rekursi ... Yang ini juga indah: "... bahkan profesor mendorong kita untuk berpikir ..." :)
TT_
Ya ini sangat bagus. Saya cenderung berpikir seperti ini, semakin tinggi istilah dalam O (..), semakin banyak pekerjaan yang Anda / mesin lakukan. Memikirkannya saat berhubungan dengan sesuatu mungkin merupakan perkiraan, tetapi begitu juga batas-batas ini. Mereka hanya memberi tahu Anda bagaimana pekerjaan yang harus dilakukan meningkat ketika jumlah input meningkat.
Abhinav Gauniyal
26

Jika biaya Anda adalah polinomial, simpan saja istilah urutan tertinggi, tanpa pengali. Misalnya:

O ((n / 2 + 1) * (n / 2)) = O (n 2 /4 + n / 2) = O (n 2 /4) = O (n 2 )

Ini tidak bekerja untuk seri tak terbatas, ingatlah. Tidak ada resep tunggal untuk kasus umum, meskipun untuk beberapa kasus umum, ketidaksetaraan berikut berlaku:

O (log N ) <O ( N ) <O ( N log N ) <O ( N 2 ) <O ( N k ) <O (e n ) <O ( n !)

Marcelo Cantos
sumber
8
pasti O (N) <O (NlogN)
jk.
22

Saya memikirkannya dalam hal informasi. Setiap masalah terdiri dari mempelajari sejumlah bit.

Alat dasar Anda adalah konsep poin keputusan dan entropinya. Entropi poin keputusan adalah informasi rata-rata yang akan diberikannya kepada Anda. Misalnya, jika suatu program berisi titik keputusan dengan dua cabang, entropinya adalah jumlah dari probabilitas setiap cabang dikali log 2 dari probabilitas terbalik dari cabang itu. Itulah cara Anda belajar dengan menjalankan keputusan itu.

Misalnya, ifpernyataan yang memiliki dua cabang, keduanya memiliki kemungkinan yang sama, memiliki entropi 1/2 * log (2/1) + 1/2 * log (2/1) = 1/2 * 1 + 1/2 * 1 = 1. Jadi entropinya adalah 1 bit.

Misalkan Anda mencari tabel item N, seperti N = 1024. Itu adalah masalah 10-bit karena log (1024) = 10 bit. Jadi, jika Anda dapat mencarinya dengan pernyataan IF yang memiliki hasil yang sama-sama memungkinkan, harus diambil 10 keputusan.

Itu yang Anda dapatkan dengan pencarian biner.

Misalkan Anda melakukan pencarian linier. Anda melihat elemen pertama dan bertanya apakah itu yang Anda inginkan. Probabilitasnya 1/1024, dan 1023/1024 tidak. Entropi dari keputusan itu adalah 1/1024 * log (1024/1) + 1023/1024 * log (1024/1023) = 1/1024 * 10 + 1023/1024 * sekitar 0 = sekitar 0,01 bit. Anda telah belajar sangat sedikit! Keputusan kedua tidak jauh lebih baik. Itu sebabnya pencarian linier sangat lambat. Bahkan itu eksponensial dalam jumlah bit yang perlu Anda pelajari.

Misalkan Anda melakukan pengindeksan. Misalkan tabel sudah disortir menjadi banyak tempat sampah, dan Anda menggunakan beberapa bit di kunci untuk mengindeks langsung ke entri tabel. Jika ada 1024 nampan, entropinya adalah 1/1024 * log (1024) + 1/1024 * log (1024) + ... untuk semua kemungkinan hasil 1024. Ini adalah 1/1024 * 10 kali 1024 hasil, atau 10 bit entropi untuk operasi pengindeksan satu itu. Itu sebabnya pencarian pengindeksan cepat.

Sekarang pikirkan tentang penyortiran. Anda memiliki N item, dan Anda memiliki daftar. Untuk setiap item, Anda harus mencari ke mana item itu masuk dalam daftar, dan kemudian menambahkannya ke daftar. Jadi pengurutan membutuhkan sekitar N kali jumlah langkah pencarian yang mendasarinya.

Jadi macam berdasarkan keputusan biner memiliki hasil yang kira-kira sama kemungkinannya mengambil langkah O (N log N). Algoritma pengurutan O (N) dimungkinkan jika didasarkan pada pencarian pengindeksan.

Saya telah menemukan bahwa hampir semua masalah kinerja algoritmik dapat dilihat dengan cara ini.

Mike Dunlavey
sumber
Wow. Apakah Anda memiliki referensi bermanfaat tentang ini? Saya merasa hal ini bermanfaat bagi saya untuk mendesain / refactor / debug program.
Jesvin Jose
3
@aitchnyu: Untuk apa nilainya, saya menulis buku yang membahas itu dan topik lainnya. Sudah lama tidak dicetak, tetapi salinannya dijual dengan harga pantas. Saya sudah mencoba membuat GoogleBooks untuk mengambilnya, tetapi saat ini agak sulit untuk mengetahui siapa yang mendapatkan hak cipta.
Mike Dunlavey
21

Mari kita mulai dari awal.

Pertama-tama, terima prinsip bahwa operasi sederhana tertentu pada data dapat dilakukan dalam O(1)waktu, yaitu, dalam waktu yang tidak tergantung pada ukuran input. Operasi primitif dalam C ini terdiri dari

  1. Operasi aritmatika (mis. + Atau%).
  2. Operasi logis (mis., &&).
  3. Operasi perbandingan (misalnya, <=).
  4. Operasi pengaksesan struktur (mis. Pengindeksan array seperti A [i], atau pointer yang mengikuti dengan -> operator).
  5. Tugas sederhana seperti menyalin nilai ke dalam variabel.
  6. Panggilan ke fungsi perpustakaan (misalnya, scanf, printf).

Pembenaran untuk prinsip ini membutuhkan studi rinci tentang instruksi mesin (langkah primitif) dari komputer biasa. Setiap operasi yang dijelaskan dapat dilakukan dengan sejumlah kecil instruksi mesin; seringkali hanya satu atau dua instruksi yang diperlukan. Sebagai konsekuensinya, beberapa jenis pernyataan dalam C dapat dieksekusi dalam O(1)waktu, yaitu, dalam jumlah waktu yang konstan tanpa tergantung input. Ini termasuk sederhana

  1. Pernyataan penugasan yang tidak melibatkan pemanggilan fungsi dalam ekspresi mereka.
  2. Baca pernyataan.
  3. Tulis pernyataan yang tidak memerlukan pemanggilan fungsi untuk mengevaluasi argumen.
  4. Pernyataan lompat memecah, melanjutkan, kebagian, dan kembali ekspresi, di mana ekspresi tidak mengandung panggilan fungsi.

Dalam C, banyak for-loop dibentuk dengan menginisialisasi variabel indeks ke beberapa nilai dan menambah variabel dengan 1 setiap kali di sekitar loop. For-loop berakhir ketika indeks mencapai batas tertentu. Misalnya, untuk-loop

for (i = 0; i < n-1; i++) 
{
    small = i;
    for (j = i+1; j < n; j++)
        if (A[j] < A[small])
            small = j;
    temp = A[small];
    A[small] = A[i];
    A[i] = temp;
}

menggunakan variabel indeks i. Itu bertambah 1 kali setiap loop, dan iterasi berhenti ketika saya mencapai n - 1.

Namun, untuk saat ini, fokus pada bentuk sederhana untuk-loop, di mana perbedaan antara nilai-nilai akhir dan awal, dibagi dengan jumlah di mana variabel indeks bertambah memberitahu kita berapa kali kita berkeliling loop . Hitungan itu tepat, kecuali ada cara untuk keluar dari loop melalui pernyataan lompat; itu adalah batas atas jumlah iterasi dalam hal apa pun.

Misalnya, untuk loop berulang ((n − 1) − 0)/1 = n − 1 times, karena 0 adalah nilai awal dari i, n - 1 adalah nilai tertinggi yang dicapai oleh i (yaitu, ketika saya mencapai n − 1, loop berhenti dan tidak ada iterasi yang terjadi dengan i = n− 1), dan 1 ditambahkan ke i pada setiap iterasi dari loop.

Dalam kasus paling sederhana, di mana waktu yang dihabiskan dalam tubuh loop adalah sama untuk setiap iterasi, kita dapat mengalikan batas atas-oh besar untuk tubuh dengan jumlah kali di sekitar loop . Sebenarnya, kita harus menambahkan O (1) waktu untuk menginisialisasi indeks loop dan O (1) waktu untuk perbandingan pertama dari indeks loop dengan batas , karena kita menguji sekali lagi daripada kita memutari loop. Namun, kecuali dimungkinkan untuk menjalankan loop nol kali, waktu untuk menginisialisasi loop dan menguji batas sekali adalah istilah tingkat rendah yang dapat dijatuhkan oleh aturan penjumlahan.


Sekarang perhatikan contoh ini:

(1) for (j = 0; j < n; j++)
(2)   A[i][j] = 0;

Kita tahu bahwa baris (1) membutuhkan O(1)waktu. Jelas, kita berputar di sekitar loop n kali, karena kita dapat menentukan dengan mengurangi batas bawah dari batas atas yang ditemukan pada baris (1) dan kemudian menambahkan 1. Karena tubuh, baris (2), membutuhkan O (1) waktu, kita dapat mengabaikan waktu untuk meningkatkan j dan waktu untuk membandingkan j dengan n, yang keduanya juga O (1). Jadi, waktu berjalan dari baris (1) dan (2) adalah produk dari n dan O (1) , yaitu O(n).

Demikian pula, kita dapat mengikat waktu berjalan dari loop luar yang terdiri dari garis (2) hingga (4), yaitu

(2) for (i = 0; i < n; i++)
(3)     for (j = 0; j < n; j++)
(4)         A[i][j] = 0;

Kami telah menetapkan bahwa loop baris (3) dan (4) membutuhkan waktu O (n). Dengan demikian, kita dapat mengabaikan O (1) waktu untuk meningkatkan i dan untuk menguji apakah i <n dalam setiap iterasi, menyimpulkan bahwa setiap iterasi dari loop luar membutuhkan O (n) waktu.

Inisialisasi i = 0 dari loop luar dan (n +1) tes kondisi i <n juga mengambil O (1) waktu dan dapat diabaikan. Akhirnya, kami mengamati bahwa kami berkeliling loop luar n kali, mengambil O (n) waktu untuk setiap iterasi, memberikan total O(n^2)waktu berjalan.


Contoh yang lebih praktis.

masukkan deskripsi gambar di sini

ajknzhol
sumber
Bagaimana jika pernyataan goto berisi pemanggilan fungsi? Sesuatu seperti step3: if (M.step == 3) {M = step3 (selesai, M); } step4: if (M.step == 4) {M = step4 (M); } if (M.step == 5) {M = step5 (M); goto step3; } if (M.step == 6) {M = step6 (M); goto step4; } kembalikan cut_matrix (A, M); bagaimana kompleksitas dihitung? apakah itu penambahan atau perkalian? mengingat step4 adalah n ^ 3 dan step5 adalah n ^ 2.
Taha Tariq
14

Jika Anda ingin memperkirakan urutan kode Anda secara empiris alih-alih dengan menganalisis kode, Anda dapat menempel pada serangkaian peningkatan nilai n dan waktu kode Anda. Plot timing Anda pada skala log. Jika kodenya O (x ^ n), nilainya harus jatuh pada garis kemiringan n.

Ini memiliki beberapa keunggulan dibandingkan hanya mempelajari kode. Untuk satu hal, Anda dapat melihat apakah Anda berada dalam kisaran di mana run time mendekati urutan asimptotiknya. Juga, Anda mungkin menemukan bahwa beberapa kode yang Anda pikir adalah pesanan O (x) benar-benar pesanan O (x ^ 2), misalnya, karena waktu yang dihabiskan untuk panggilan perpustakaan.

John D. Cook
sumber
Untuk memperbarui jawaban ini: en.wikipedia.org/wiki/Analysis_of_algorithms , tautan ini memiliki rumus yang Anda butuhkan. Banyak algoritma mengikuti aturan daya, jika Anda melakukannya, dengan 2 titik waktu dan 2 runtime pada mesin, kita dapat menghitung kemiringan pada plot log-log. Yang merupakan = log (t2 / t1) / log (n2 / n1), ini memberi saya eksponen untuk algoritma di, O (N ^ a). Ini dapat dibandingkan dengan perhitungan manual menggunakan kode.
Christopher John
1
Hai, jawaban yang bagus. Saya bertanya-tanya apakah Anda mengetahui ada perpustakaan atau metodologi (saya bekerja dengan python / R misalnya) untuk menggeneralisasi metode empiris ini, yang berarti seperti memasang berbagai fungsi kompleksitas untuk meningkatkan dataset ukuran, dan mencari tahu mana yang relevan. Terima kasih
agenis
10

Pada dasarnya hal yang muncul hingga 90% dari waktu hanyalah menganalisis loop. Apakah Anda memiliki loop bersarang tunggal, ganda, tiga? Anda memiliki O (n), O (n ^ 2), O (n ^ 3) waktu berjalan.

Sangat jarang (kecuali jika Anda menulis platform dengan pustaka basis yang luas (seperti misalnya .NET BCL, atau C ++ 's STL), Anda akan menemukan apa pun yang lebih sulit daripada hanya melihat loop Anda (untuk pernyataan, sementara, goto, dll ...)

Adam
sumber
1
Tergantung pada loop.
kelalaka
8

Notasi O besar berguna karena mudah digunakan dan menyembunyikan komplikasi dan detail yang tidak perlu (untuk beberapa definisi yang tidak perlu). Salah satu cara yang bagus untuk mengetahui kompleksitas algoritma divide and conquer adalah metode tree. Katakanlah Anda memiliki versi quicksort dengan prosedur median, jadi Anda membagi array menjadi sub-array yang seimbang sempurna setiap saat.

Sekarang bangun pohon yang sesuai dengan semua array yang bekerja dengan Anda. Pada root Anda memiliki larik asli, root memiliki dua anak yang merupakan subarrays. Ulangi ini sampai Anda memiliki array elemen tunggal di bagian bawah.

Karena kita dapat menemukan median dalam waktu O (n) dan membagi array menjadi dua bagian dalam waktu O (n), pekerjaan yang dilakukan pada setiap node adalah O (k) di mana k adalah ukuran array. Setiap level dari pohon berisi (paling banyak) seluruh array sehingga pekerjaan per level adalah O (n) (ukuran dari subarrays ditambahkan hingga n, dan karena kami memiliki O (k) per level kami dapat menambahkan ini) . Hanya ada level log (n) di pohon karena setiap kali kita membagi dua input.

Oleh karena itu kita dapat membatasi jumlah pekerjaan dengan O (n * log (n)).

Namun, Big O menyembunyikan beberapa detail yang terkadang tidak bisa kita abaikan. Pertimbangkan menghitung urutan Fibonacci dengan

a=0;
b=1;
for (i = 0; i <n; i++) {
    tmp = b;
    b = a + b;
    a = tmp;
}

dan mari kita asumsikan a dan b adalah BigIntegers di Java atau sesuatu yang dapat menangani angka besar secara sewenang-wenang. Kebanyakan orang akan mengatakan ini adalah algoritma O (n) tanpa gentar. Alasannya adalah bahwa Anda memiliki n iterasi dalam for loop dan O (1) bekerja di samping loop.

Tetapi bilangan Fibonacci besar, bilangan Fibonacci ke-n adalah eksponensial dalam n jadi hanya menyimpannya akan mengambil urutan n byte. Performa tambahan dengan bilangan bulat besar akan membutuhkan O (n) jumlah pekerjaan. Jadi jumlah total pekerjaan yang dilakukan dalam prosedur ini adalah

1 + 2 + 3 + ... + n = n (n-1) / 2 = O (n ^ 2)

Jadi algoritma ini berjalan dalam waktu kuadrat!


sumber
1
Anda seharusnya tidak peduli tentang bagaimana angka-angka disimpan, itu tidak mengubah bahwa algoritma tumbuh pada tingkat atas O (n).
mikek3332002
8

Kurang berguna secara umum, saya pikir, tetapi demi kelengkapan ada juga Big Omega Ω , yang mendefinisikan batas bawah pada kompleksitas algoritma, dan Big Theta Θ , yang mendefinisikan batas atas dan bawah.

Martin
sumber
7

Hancurkan algoritma menjadi beberapa bagian yang Anda ketahui notasi O besar, dan gabungkan melalui operator O besar. Itulah satu-satunya cara yang saya tahu.

Untuk informasi lebih lanjut, periksa halaman Wikipedia tentang masalah ini.

Lasse V. Karlsen
sumber
7

Keakraban dengan algoritma / struktur data yang saya gunakan dan / atau analisis sekilas iterasi bersarang. Kesulitannya adalah ketika Anda memanggil fungsi pustaka, mungkin beberapa kali - Anda sering tidak yakin apakah Anda memanggil fungsi tersebut secara tidak perlu pada waktu atau implementasi apa yang mereka gunakan. Mungkin fungsi perpustakaan harus memiliki ukuran kompleksitas / efisiensi, apakah itu Big O atau metrik lain, yang tersedia dalam dokumentasi atau bahkan IntelliSense .

Matt Mitchell
sumber
6

Adapun "bagaimana Anda menghitung" Big O, ini adalah bagian dari teori kompleksitas Komputasi . Untuk beberapa (banyak) kasus khusus Anda mungkin dapat datang dengan beberapa heuristik sederhana (seperti mengalikan jumlah loop untuk loop bersarang), esp. ketika semua yang Anda inginkan adalah estimasi batas atas, dan Anda tidak keberatan jika terlalu pesimis - yang saya kira mungkin adalah pertanyaan Anda.

Jika Anda benar-benar ingin menjawab pertanyaan Anda untuk algoritma apa pun, yang terbaik yang dapat Anda lakukan adalah menerapkan teori tersebut. Selain analisis "kasus terburuk" yang sederhana, saya merasa analisis Amortisasi sangat berguna dalam praktiknya.

Suma
sumber
6

Untuk kasus 1, loop dalam dieksekusi n-ikali, jadi jumlah total eksekusi adalah jumlah untuk ipergi dari 0ke n-1(karena lebih rendah dari, tidak lebih rendah dari atau sama) dari n-i. Anda akhirnya n*(n + 1) / 2, jadi O(n²/2) = O(n²).

Untuk loop ke-2, iberada di antara 0dan ntermasuk untuk loop luar; maka loop dalam dieksekusi ketika jbenar-benar lebih besar dari n, yang kemudian mustahil.

Emmanuel
sumber
5

Selain menggunakan metode master (atau salah satu spesialisasinya), saya menguji algoritme saya secara eksperimental. Ini tidak dapat membuktikan bahwa setiap kelas kompleksitas tertentu tercapai, tetapi dapat memberikan jaminan bahwa analisis matematika sesuai. Untuk membantu dengan jaminan ini, saya menggunakan alat cakupan kode bersama dengan eksperimen saya, untuk memastikan bahwa saya menggunakan semua kasing.

Sebagai contoh yang sangat sederhana katakan Anda ingin melakukan pemeriksaan kewarasan pada kecepatan semacam daftar .NET framework. Anda bisa menulis sesuatu seperti berikut ini, kemudian menganalisis hasilnya di Excel untuk memastikan mereka tidak melebihi kurva n * log (n).

Dalam contoh ini saya mengukur jumlah perbandingan, tetapi juga bijaksana untuk memeriksa waktu aktual yang diperlukan untuk setiap ukuran sampel. Namun kemudian Anda harus lebih berhati-hati bahwa Anda hanya mengukur algoritma dan tidak termasuk artefak dari infrastruktur pengujian Anda.

int nCmp = 0;
System.Random rnd = new System.Random();

// measure the time required to sort a list of n integers
void DoTest(int n)
{
   List<int> lst = new List<int>(n);
   for( int i=0; i<n; i++ )
      lst[i] = rnd.Next(0,1000);

   // as we sort, keep track of the number of comparisons performed!
   nCmp = 0;
   lst.Sort( delegate( int a, int b ) { nCmp++; return (a<b)?-1:((a>b)?1:0)); }

   System.Console.Writeline( "{0},{1}", n, nCmp );
}


// Perform measurement for a variety of sample sizes.
// It would be prudent to check multiple random samples of each size, but this is OK for a quick sanity check
for( int n = 0; n<1000; n++ )
   DoTest(n);
Eric
sumber
4

Jangan lupa untuk memungkinkan kompleksitas ruang yang juga dapat menjadi penyebab kekhawatiran jika seseorang memiliki sumber daya memori yang terbatas. Jadi misalnya Anda mungkin mendengar seseorang menginginkan algoritma ruang konstan yang pada dasarnya merupakan cara untuk mengatakan bahwa jumlah ruang yang diambil oleh algoritma tidak tergantung pada faktor apa pun di dalam kode.

Kadang-kadang kompleksitas dapat datang dari berapa kali sesuatu dipanggil, seberapa sering loop dieksekusi, seberapa sering memori dialokasikan, dan sebagainya adalah bagian lain untuk menjawab pertanyaan ini.

Terakhir, O besar dapat digunakan untuk kasus terburuk, kasus terbaik, dan kasus amortisasi di mana umumnya itu adalah kasus terburuk yang digunakan untuk menggambarkan seberapa buruk suatu algoritma.

JB King
sumber
4

Yang sering diabaikan adalah perilaku yang diharapkan dari algoritma Anda. Itu tidak mengubah Big-O dari algoritme Anda , tetapi itu berhubungan dengan pernyataan "optimasi prematur ..."

Perilaku yang diharapkan dari algoritma Anda adalah - sangat tercengang - seberapa cepat Anda dapat mengharapkan algoritma Anda bekerja pada data yang paling mungkin Anda lihat.

Misalnya, jika Anda mencari nilai dalam daftar, itu O (n), tetapi jika Anda tahu bahwa sebagian besar daftar yang Anda lihat memiliki nilai Anda di depan, perilaku khas algoritma Anda lebih cepat.

Untuk benar-benar memahaminya, Anda harus dapat menggambarkan distribusi probabilitas "ruang input" Anda (jika Anda perlu mengurutkan daftar, seberapa sering daftar itu akan disortir? Seberapa sering itu terbalik total? Bagaimana sering itu sebagian besar diurutkan?) Tidak selalu layak bahwa Anda tahu itu, tetapi terkadang Anda melakukannya.

Baltimark
sumber
4

pertanyaan bagus!

Penafian: jawaban ini berisi pernyataan salah lihat komentar di bawah.

Jika Anda menggunakan Big O, Anda berbicara tentang kasus terburuk (lebih lanjut tentang apa artinya nanti). Selain itu, ada modal theta untuk kasus rata-rata dan omega besar untuk kasus terbaik.

Lihatlah situs ini untuk mendapatkan definisi resmi tentang Big O: https://xlinux.nist.gov/dads/HTML/bigOnotation.html

f (n) = O (g (n)) berarti ada konstanta positif c dan k, sehingga 0 ≤ f (n) ≤ cg (n) untuk semua n ≥ k. Nilai-nilai c dan k harus diperbaiki untuk fungsi f dan tidak harus bergantung pada n.


Oke, jadi sekarang apa yang kita maksud dengan kompleksitas "kasus terbaik" dan "kasus terburuk"?

Ini mungkin digambarkan paling jelas melalui contoh. Sebagai contoh jika kita menggunakan pencarian linear untuk menemukan angka dalam array yang diurutkan maka kasus terburuk adalah ketika kita memutuskan untuk mencari elemen terakhir dari array karena ini akan mengambil langkah sebanyak yang ada item dalam array. Kasus terbaik adalah ketika kita mencari elemen pertama karena kita akan selesai setelah pemeriksaan pertama.

Inti dari semua kompleksitas kata sifat ini adalah bahwa kami sedang mencari cara untuk membuat grafik jumlah waktu yang dihabiskan oleh program hipotetis dalam hal ukuran variabel tertentu. Namun untuk banyak algoritma, Anda dapat berargumen bahwa tidak ada waktu untuk ukuran input tertentu. Perhatikan bahwa ini bertentangan dengan persyaratan mendasar dari suatu fungsi, input apa pun harus memiliki tidak lebih dari satu output. Jadi kami datang dengan beberapa fungsi untuk menggambarkan kompleksitas suatu algoritma. Sekarang, walaupun mencari sebuah array ukuran n mungkin membutuhkan jumlah waktu yang bervariasi tergantung pada apa yang Anda cari dalam array dan bergantung secara proporsional ke n, kami dapat membuat deskripsi informatif dari algoritma menggunakan kasus terbaik, kasus rata-rata , dan kelas kasus terburuk.

Maaf ini ditulis dengan buruk dan tidak memiliki banyak informasi teknis. Tapi mudah-mudahan ini akan membuat kelas kompleksitas waktu lebih mudah untuk dipikirkan. Setelah Anda merasa nyaman dengan ini, itu menjadi masalah sederhana penguraian melalui program Anda dan mencari hal-hal seperti untuk-loop yang tergantung pada ukuran array dan penalaran berdasarkan pada struktur data Anda input apa yang akan menghasilkan kasus-kasus sepele dan input apa yang akan dihasilkan dalam kasus terburuk.

Samy Bencherif
sumber
1
Ini salah. Big O berarti "batas atas" bukan kasus terburuk.
Samy Bencherif
1
Adalah kesalahpahaman umum bahwa big-O mengacu pada kasus terburuk. Bagaimana O dan Ω berhubungan dengan kasus terburuk dan terbaik?
Bernhard Barker
1
Ini menyesatkan. Big-O berarti batas atas untuk fungsi f (n). Omega berarti batas bawah untuk suatu fungsi f (n). Sama sekali tidak terkait dengan kasus terbaik atau kasus terburuk.
Tasneem Haider
1
Anda dapat menggunakan Big-O sebagai batas atas untuk kasus terbaik atau terburuk, tetapi selain itu, ya tidak ada hubungannya.
Samy Bencherif
2

Saya tidak tahu bagaimana menyelesaikan ini secara terprogram, tetapi hal pertama yang dilakukan orang adalah bahwa kami mencicipi algoritma untuk pola tertentu dalam jumlah operasi yang dilakukan, katakanlah 4n ^ 2 + 2n + 1 kami memiliki 2 aturan:

  1. Jika kita memiliki jumlah persyaratan, istilah dengan tingkat pertumbuhan terbesar disimpan, dengan persyaratan lainnya dihilangkan.
  2. Jika kita memiliki produk dari beberapa faktor, faktor konstan dihilangkan.

Jika kita menyederhanakan f (x), di mana f (x) adalah rumus untuk jumlah operasi yang dilakukan, (4n ^ 2 + 2n + 1 dijelaskan di atas), kita memperoleh nilai O-besar [O (n ^ 2) dalam hal ini kasus]. Tetapi ini harus memperhitungkan interpolasi Lagrange dalam program, yang mungkin sulit untuk diterapkan. Dan bagaimana jika nilai big-O nyata adalah O (2 ^ n), dan kita mungkin memiliki sesuatu seperti O (x ^ n), jadi algoritma ini mungkin tidak akan diprogram. Tetapi jika seseorang membuktikan saya salah, berikan saya kodenya. . . .

Gavriel Feria
sumber
2

Untuk kode A, loop luar akan dieksekusi untuk n+1kali, waktu '1' berarti proses yang memeriksa apakah saya masih memenuhi persyaratan. Dan loop dalam berjalan nkali, n-2kali .... Jadi 0+2+..+(n-2)+n= (0+n)(n+1)/2= O(n²),.

Untuk kode B, meskipun loop dalam tidak akan masuk dan menjalankan foo (), loop dalam akan dieksekusi untuk n kali tergantung pada waktu eksekusi loop luar, yaitu O (n)

orang awam
sumber
1

Saya ingin menjelaskan Big-O dalam aspek yang sedikit berbeda.

Big-O hanya untuk membandingkan kerumitan program yang berarti seberapa cepat mereka tumbuh ketika input meningkat dan bukan waktu yang tepat yang dihabiskan untuk melakukan tindakan.

IMHO dalam rumus-O besar Anda lebih baik tidak menggunakan persamaan yang lebih kompleks (Anda mungkin hanya berpegang pada yang ada dalam grafik berikut.) Namun Anda masih dapat menggunakan rumus lain yang lebih tepat (seperti 3 ^ n, n ^ 3, .. .) tetapi lebih dari itu terkadang bisa menyesatkan! Jadi lebih baik untuk membuatnya sesederhana mungkin.

masukkan deskripsi gambar di sini

Saya ingin menekankan sekali lagi bahwa di sini kami tidak ingin mendapatkan formula yang tepat untuk algoritma kami. Kami hanya ingin menunjukkan bagaimana itu tumbuh ketika input tumbuh dan bandingkan dengan algoritma lain dalam arti itu. Kalau tidak, Anda lebih baik menggunakan metode yang berbeda seperti menandai bangku.

HmT
sumber