Saya harus setuju bahwa ini cukup aneh saat pertama kali Anda melihat algoritma O (log n) ... dari mana asal logaritma itu? Namun, ternyata ada beberapa cara berbeda yang membuat istilah log muncul di notasi O besar. Berikut ini beberapa di antaranya:
Membagi berulang kali dengan konstanta
Ambil nomor apa saja n; katakanlah, 16. Berapa kali kamu bisa membagi n dengan dua sebelum kamu mendapatkan angka yang kurang dari atau sama dengan satu? Untuk 16, kami punya itu
16 / 2 = 8
8 / 2 = 4
4 / 2 = 2
2 / 2 = 1
Perhatikan bahwa ini akhirnya membutuhkan empat langkah untuk diselesaikan. Menariknya, kita juga punya log 2 16 = 4. Hmmm ... bagaimana dengan 128?
128 / 2 = 64
64 / 2 = 32
32 / 2 = 16
16 / 2 = 8
8 / 2 = 4
4 / 2 = 2
2 / 2 = 1
Ini membutuhkan tujuh langkah, dan log 2 128 = 7. Apakah ini kebetulan? Nggak! Ada alasan bagus untuk ini. Misalkan kita membagi bilangan n dengan 2 i kali. Kemudian kita mendapatkan nomor n / 2 i . Jika kita ingin menyelesaikan nilai i di mana nilai ini paling banyak 1, kita dapatkan
n / 2 saya ≤ 1
n ≤ 2 i
log 2 n ≤ i
Dengan kata lain, jika kita memilih bilangan bulat i sedemikian rupa sehingga i ≥ log 2 n, maka setelah membagi n menjadi setengah kali i kita akan mendapatkan nilai paling banyak 1. I terkecil yang dijamin adalah log 2 n, jadi jika kita memiliki algoritma yang membagi dengan 2 hingga jumlahnya menjadi cukup kecil, maka kita dapat mengatakan bahwa itu berakhir dalam langkah O (log n).
Detail penting adalah bahwa tidak masalah konstanta apa yang Anda bagi dengan n (asalkan lebih besar dari satu); jika Anda membagi dengan konstanta k, dibutuhkan log k n langkah untuk mencapai 1. Jadi, setiap algoritma yang berulang kali membagi ukuran input dengan beberapa pecahan akan membutuhkan iterasi O (log n) untuk berhenti. Iterasi tersebut mungkin membutuhkan banyak waktu dan waktu proses bersih tidak perlu O (log n), tetapi jumlah langkah akan menjadi logaritmik.
Jadi, dari mana ini muncul? Salah satu contoh klasik adalah pencarian biner , algoritma cepat untuk mencari nilai array yang diurutkan. Algoritme bekerja seperti ini:
- Jika larik kosong, kembalikan elemen tersebut tidak ada dalam larik.
- Jika tidak:
- Lihatlah elemen tengah dari array.
- Jika itu sama dengan elemen yang kita cari, kembalikan kesuksesan.
- Jika lebih besar dari elemen yang kita cari:
- Buang paruh kedua larik.
- Ulang
- Jika kurang dari elemen yang kita cari:
- Buang paruh pertama larik.
- Ulang
Misalnya, untuk mencari 5 dalam larik
1 3 5 7 9 11 13
Pertama-tama kita akan melihat elemen tengah:
1 3 5 7 9 11 13
^
Karena 7> 5, dan karena array sudah diurutkan, kita tahu pasti bahwa angka 5 tidak boleh berada di bagian belakang array, jadi kita bisa membuangnya. Daun ini
1 3 5
Jadi sekarang kita melihat elemen tengah di sini:
1 3 5
^
Karena 3 <5, kita tahu bahwa 5 tidak bisa muncul di paruh pertama larik, jadi kita bisa membuang larik separuh pertama untuk keluar.
5
Sekali lagi kita melihat bagian tengah dari larik ini:
5
^
Karena ini persis dengan angka yang kita cari, kita dapat melaporkan bahwa 5 memang ada dalam larik.
Jadi seberapa efisien ini? Nah, pada setiap iterasi kami membuang setidaknya setengah dari elemen array yang tersisa. Algoritme berhenti segera setelah array kosong atau kita menemukan nilai yang kita inginkan. Dalam kasus terburuk, elemen tidak ada, jadi kami terus membagi dua ukuran array hingga kami kehabisan elemen. Berapa lama waktu yang dibutuhkan? Nah, karena kita terus memotong array menjadi dua lagi dan lagi, kita akan menyelesaikan paling banyak iterasi O (log n), karena kita tidak dapat memotong array menjadi setengah lebih dari O (log n) kali sebelum kita menjalankannya. keluar dari elemen array.
Algoritme yang mengikuti teknik umum bagi-dan-taklukkan (memotong masalah menjadi beberapa bagian, menyelesaikan bagian-bagian itu, kemudian menyatukan masalah kembali) cenderung memiliki istilah logaritmik di dalamnya karena alasan yang sama - Anda tidak dapat terus memotong beberapa objek setengah lebih dari O (log n) kali. Anda mungkin ingin melihat merge sort sebagai contoh yang bagus untuk ini.
Memproses nilai satu digit pada satu waktu
Berapa digit dalam bilangan basis-10 n? Nah, jika ada angka k dalam bilangan tersebut, maka kita akan mendapatkan bahwa digit terbesar adalah kelipatan 10 k . Angka k-digit terbesar adalah 999 ... 9, k kali, dan ini sama dengan 10 k + 1 - 1. Akibatnya, jika kita mengetahui bahwa n memiliki k digit di dalamnya, maka kita tahu bahwa nilai n adalah paling banyak 10 k + 1 - 1. Jika kita ingin menyelesaikan k dalam suku-suku dari n, kita dapatkan
n ≤ 10 k + 1 - 1
n + 1 ≤ 10 k + 1
log 10 (n + 1) ≤ k + 1
(log 10 (n + 1)) - 1 ≤ k
Dari sini kita mendapatkan bahwa k kira-kira adalah logaritma basis 10 dari n. Dengan kata lain, banyaknya digit di n adalah O (log n).
Misalnya, mari kita pikirkan kerumitan menambahkan dua angka besar yang terlalu besar untuk dimasukkan ke dalam kata mesin. Misalkan kita memiliki bilangan-bilangan tersebut yang direpresentasikan dalam basis 10, dan kita akan memanggil bilangan tersebut m dan n. Salah satu cara untuk menambahkannya adalah melalui metode sekolah dasar - tulis angka satu digit pada satu waktu, lalu kerjakan dari kanan ke kiri. Misalnya, untuk menjumlahkan 1337 dan 2065, kita akan mulai dengan menuliskan angka sebagai
1 3 3 7
+ 2 0 6 5
==============
Kami menambahkan digit terakhir dan membawa 1:
1
1 3 3 7
+ 2 0 6 5
==============
2
Kemudian kita menambahkan digit kedua-ke-terakhir ("kedua dari belakang") dan membawa 1:
1 1
1 3 3 7
+ 2 0 6 5
==============
0 2
Selanjutnya, kami menambahkan digit ketiga-ke-terakhir ("antepenultimate"):
1 1
1 3 3 7
+ 2 0 6 5
==============
4 0 2
Terakhir, kami menambahkan digit keempat hingga terakhir ("preantepenultimate" ... Saya suka bahasa Inggris):
1 1
1 3 3 7
+ 2 0 6 5
==============
3 4 0 2
Sekarang, berapa banyak pekerjaan yang kita lakukan? Kami melakukan total O (1) pekerjaan per digit (yaitu, jumlah pekerjaan yang konstan), dan ada total digit O (max {log n, log m}) yang perlu diproses. Ini memberikan total kompleksitas O (max {log n, log m}), karena kita perlu mengunjungi setiap digit dalam dua angka.
Banyak algoritme mendapatkan istilah O (log n) di dalamnya dari bekerja satu digit pada satu waktu di beberapa basis. Contoh klasiknya adalah radix sort , yang mengurutkan bilangan bulat satu digit dalam satu waktu. Ada banyak ragam jenis radix, tetapi biasanya dijalankan dalam waktu O (n log U), di mana U adalah bilangan bulat terbesar yang sedang diurutkan. Alasan untuk ini adalah bahwa setiap lintasan pengurutan membutuhkan waktu O (n), dan ada total iterasi O (log U) yang diperlukan untuk memproses setiap digit O (log U) dari bilangan terbesar yang diurutkan. Banyak algoritme tingkat lanjut, seperti algoritme jalur terpendek Gabow atau versi penskalaan algoritme aliran maks Ford-Fulkerson , memiliki istilah log dalam kompleksitasnya karena algoritme tersebut bekerja satu digit pada satu waktu.
Mengenai pertanyaan kedua Anda tentang bagaimana Anda memecahkan masalah itu, Anda mungkin ingin melihat pertanyaan terkait ini yang mengeksplorasi aplikasi yang lebih maju. Mengingat struktur umum masalah yang dijelaskan di sini, Anda sekarang dapat memiliki pemahaman yang lebih baik tentang bagaimana memikirkan masalah saat Anda mengetahui ada istilah log dalam hasil, jadi saya akan menyarankan agar Anda tidak melihat jawabannya sampai Anda memberikannya. beberapa pemikiran.
Semoga ini membantu!
O(log n)
dapat dilihat sebagai: Jika Anda menggandakan ukuran masalahn
, algoritme Anda hanya membutuhkan jumlah langkah yang konstan lagi.