1. Gambaran keseluruhan
Dalam tutorial ini, kita akan membincangkan maksud Notasi Big O. Kami akan melalui beberapa contoh untuk menyiasat kesannya pada masa berjalan kod anda.
2. Intuisi Notasi Big O
Kita sering mendengar prestasi algoritma yang dijelaskan menggunakan Notasi Big O.
Kajian prestasi algoritma - atau kerumitan algoritma - tergolong dalam bidang analisis algoritma. Analisis algoritma menjawab persoalan berapa banyak sumber, seperti ruang atau masa cakera, yang digunakan oleh algoritma.
Kami akan melihat masa sebagai sumber. Biasanya, semakin sedikit masa yang diperlukan algoritma untuk diselesaikan, semakin baik.
3. Algoritma Masa Tetap - O (1)
Bagaimana ukuran input algoritma ini mempengaruhi masa berjalannya? Kunci untuk memahami Big O adalah memahami kadar di mana sesuatu dapat berkembang. Kadar yang dimaksudkan di sini adalah masa yang diambil bagi setiap ukuran input.
Pertimbangkan sekeping kod ringkas ini:
int n = 1000; System.out.println("Hey - your input is: " + n);
Jelas sekali, ia tidak kira apa n adalah, di atas. Bahagian kod ini memerlukan masa yang berterusan untuk dijalankan. Ia tidak bergantung pada ukuran n.
Begitu juga:
int n = 1000; System.out.println("Hey - your input is: " + n); System.out.println("Hmm.. I'm doing more stuff with: " + n); System.out.println("And more: " + n);
Contoh di atas juga adalah masa yang tetap. Walaupun diperlukan 3 kali lebih lama untuk dijalankan, ia tidak bergantung pada ukuran input, n. Kami menunjukkan algoritma masa tetap seperti berikut: O (1) . Perhatikan bahawa O (2) , O (3) atau bahkan O (1000) bermaksud perkara yang sama.
Kami tidak peduli dengan tepat berapa lama masa berlari, hanya memerlukan masa yang berterusan.
4. Algoritma Masa Logaritma - O (log n)
Algoritma masa tetap adalah (tanpa simptomatik) paling cepat. Masa logaritma adalah yang terpantas seterusnya. Malangnya, mereka agak sukar untuk dibayangkan.
Salah satu contoh umum algoritma masa logaritma ialah algoritma carian binari. Untuk melihat bagaimana melaksanakan carian binari di Java, klik di sini.
Apa yang penting di sini ialah masa berjalan bertambah sepadan dengan logaritma input (dalam kes ini, log ke pangkalan 2):
for (int i = 1; i < n; i = i * 2){ System.out.println("Hey - I'm busy looking at: " + i); }
Sekiranya n adalah 8, outputnya adalah seperti berikut:
Hey - I'm busy looking at: 1 Hey - I'm busy looking at: 2 Hey - I'm busy looking at: 4
Algoritma ringkas kami menjalankan log (8) = 3 kali.
5. Algoritma Masa Linear - O (n)
Selepas algoritma masa logaritma, kami mendapat kelas terpantas seterusnya: algoritma masa linear.
Sekiranya kita mengatakan sesuatu tumbuh secara linear, kita bermaksud bahawa ia tumbuh secara berkadar terus dengan ukuran inputnya.
Fikirkan mudah untuk gelung:
for (int i = 0; i < n; i++) { System.out.println("Hey - I'm busy looking at: " + i); }
Berapa kali ini dilakukan untuk gelung? n kali, sudah tentu! Kami tidak tahu berapa lama masa ini untuk dijalankan - dan kami tidak bimbang tentang perkara itu.
Yang kita tahu ialah algoritma sederhana yang ditunjukkan di atas akan berkembang secara linear dengan ukuran inputnya.
Kami lebih suka masa larian 0.1n daripada (1000n + 1000) , tetapi kedua-duanya masih algoritma linear; mereka berdua tumbuh secara langsung sesuai dengan ukuran input mereka.
Sekali lagi, jika algoritma diubah menjadi berikut:
for (int i = 0; i < n; i++) { System.out.println("Hey - I'm busy looking at: " + i); System.out.println("Hmm.. Let's have another look at: " + i); System.out.println("And another: " + i); }
Waktu berjalan masih linear dalam ukuran inputnya, n . Kami menunjukkan algoritma linear seperti berikut: O (n) .
Seperti algoritma masa tetap, kami tidak mementingkan spesifik masa berjalan. O (2n + 1) sama dengan O (n) , kerana Big O Notation menyangkut dirinya dengan pertumbuhan untuk ukuran input.
6. Algoritma Masa N Log N - O (n log n)
n log n adalah kelas algoritma seterusnya. Masa berjalan meningkat berkadar dengan n log n input:
for (int i = 1; i <= n; i++){ for(int j = 1; j < n; j = j * 2) { System.out.println("Hey - I'm busy looking at: " + i + " and " + j); } }
Sebagai contoh, jika n adalah 8, maka algoritma ini akan dijalankan 8 * log (8) = 8 * 3 = 24 kali. Sama ada kita mempunyai ketaksamaan yang ketat atau tidak dalam loop for tidak relevan untuk Notasi Big O.
7. Algoritma Masa Polinomial - O (np)
Seterusnya kami mempunyai algoritma masa polinomial. Algoritma ini lebih perlahan daripada algoritma n log n .
Istilah polinomial adalah istilah umum yang mengandungi fungsi kuadratik (n2) , kubik (n3) , kuartik (n4) , dll. Apa yang penting untuk diketahui ialah O (n2) lebih cepat daripada O (n3) yang lebih cepat daripada O (n4) , dll.
Mari kita lihat contoh ringkas algoritma masa kuadratik:
for (int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { System.out.println("Hey - I'm busy looking at: " + i + " and " + j); } }
Algoritma ini akan dijalankan 82 = 64 kali. Perhatikan, jika kita meletakkan sarang lain untuk gelung, ini akan menjadi algoritma O (n3) .
8. Algoritma Masa Eksponen - O ( k n)
Sekarang kita memasuki wilayah berbahaya; algoritma ini berkembang sebanding dengan beberapa faktor yang diperkembangkan oleh ukuran input.
Contohnya, algoritma O (2n) berganda dengan setiap input tambahan. Jadi, jika n = 2 , algoritma ini akan dijalankan empat kali; jika n = 3 , mereka akan berjalan lapan kali (seperti kebalikan dari algoritma masa logaritma).
O(3n) algorithms triple with every additional input, O(kn) algorithms will get k times bigger with every additional input.
Let's have a look at a simple example of an O(2n) time algorithm:
for (int i = 1; i <= Math.pow(2, n); i++){ System.out.println("Hey - I'm busy looking at: " + i); }
This algorithm will run 28 = 256 times.
9. Factorial Time Algorithms – O(n!)
In most cases, this is pretty much as bad as it'll get. This class of algorithms has a run time proportional to the factorial of the input size.
A classic example of this is solving the traveling salesman problem using a brute-force approach to solve it.
An explanation of the solution to the traveling salesman problem is beyond the scope of this article.
Instead, let's look at a simple O(n!) algorithm, as in the previous sections:
for (int i = 1; i <= factorial(n); i++){ System.out.println("Hey - I'm busy looking at: " + i); }
where factorial(n) simply calculates n!. If n is 8, this algorithm will run 8! = 40320 times.
10. Asymptotic Functions
Big O is what is known as an asymptotic function. All this means, is that it concerns itself with the performance of an algorithm at the limit – i.e. – when lots of input is thrown at it.
Big O doesn't care about how well your algorithm does with inputs of small size. It's concerned with large inputs (think sorting a list of one million numbers vs. sorting a list of 5 numbers).
Another thing to note is that there are other asymptotic functions. Big Θ (theta) and Big Ω (omega) also both describes algorithms at the limit (remember, the limit this just means for huge inputs).
To understand the differences between these 3 important functions, we first need to know that each of Big O, Big Θ, and Big Ω describes a set (i.e., a collection of elements).
Here, the members of our sets are algorithms themselves:
- Big O describes the set of all algorithms that run no worse than a certain speed (it's an upper bound)
- Conversely, Big Ω describes the set of all algorithms that run no better than a certain speed (it's a lower bound)
- Akhirnya, Big Θ menerangkan sekumpulan semua algoritma yang berjalan pada kelajuan tertentu (seperti persamaan)
Definisi yang kami masukkan di atas tidak tepat secara matematik, tetapi ia akan membantu pemahaman kita.
Biasanya, anda akan mendengar perkara yang dijelaskan menggunakan Big O , tetapi tidak ada salahnya mengetahui mengenai Big Θ dan Big Ω.
11. Kesimpulannya
Dalam artikel ini, kami membincangkan notasi Big O, dan bagaimana memahami kerumitan algoritma dapat mempengaruhi masa berjalan kod anda.
Visualisasi kelas kerumitan yang berbeza boleh didapati di sini.
Seperti biasa, coretan kod untuk tutorial ini boleh didapati di GitHub.