Untuk memahami perbedaan antara waktu polinomial dan waktu pseudopolinom, kita perlu memulai dengan memformalkan apa yang dimaksud dengan "waktu polinomial".
Intuisi umum untuk waktu polinomial adalah "waktu O (n k ) untuk beberapa k". Misalnya, pemilihan sortir berjalan dalam waktu O (n 2 ), yang merupakan waktu polinomial, sedangkan penyelesaian gaya brute TSP membutuhkan waktu O (n · n!), Yang bukan waktu polinomial.
Semua runtime ini mengacu pada beberapa variabel n yang melacak ukuran input. Sebagai contoh, pada selection sort, n mengacu pada jumlah elemen dalam array, sedangkan pada TSP n mengacu pada jumlah node pada grafik. Untuk menstandarkan definisi dari apa sebenarnya arti "n" dalam konteks ini, definisi formal dari kompleksitas waktu mendefinisikan "ukuran" dari sebuah masalah sebagai berikut:
Ukuran masukan untuk suatu masalah adalah jumlah bit yang diperlukan untuk menuliskan masukan tersebut.
Misalnya, jika masukan untuk algoritma pengurutan adalah larik bilangan bulat 32-bit, maka ukuran masukannya adalah 32n, di mana n adalah jumlah entri dalam larik. Dalam grafik dengan n node dan m edge, input dapat ditentukan sebagai daftar semua node diikuti dengan daftar semua edge, yang membutuhkan bit Ω (n + m).
Dengan definisi ini, definisi formal waktu polinomial adalah sebagai berikut:
Algoritme berjalan dalam waktu polinomial jika runtime-nya adalah O (x k ) untuk beberapa k konstan, di mana x menunjukkan jumlah bit input yang diberikan ke algoritme.
Ketika bekerja dengan algoritma yang memproses grafik, daftar, pohon, dll., Definisi ini kurang lebih sesuai dengan definisi konvensional. Misalnya, Anda memiliki algoritme pengurutan yang mengurutkan array bilangan bulat 32-bit. Jika Anda menggunakan sesuatu seperti seleksi sort untuk melakukan ini, runtime, sebagai fungsi dari jumlah elemen input dalam larik, akan menjadi O (n 2 ). Tetapi bagaimana n, jumlah elemen dalam larik input, sesuai dengan jumlah bit input? Seperti disebutkan sebelumnya, jumlah bit input adalah x = 32n. Oleh karena itu, jika kita menyatakan runtime algoritma dalam bentuk x daripada n, kita mendapatkan bahwa runtime adalah O (x 2 ), dan algoritma berjalan dalam waktu polinomial.
Demikian pula, misalkan Anda melakukan penelusuran pertama kedalaman pada grafik, yang membutuhkan waktu O (m + n), di mana m adalah jumlah tepi pada grafik dan n adalah jumlah node. Bagaimana ini berhubungan dengan jumlah bit input yang diberikan? Nah, jika kita mengasumsikan bahwa input ditentukan sebagai daftar kedekatan (daftar semua node dan edge), maka seperti yang disebutkan sebelumnya, jumlah bit input adalah x = Ω (m + n). Oleh karena itu, runtime akan menjadi O (x), sehingga algoritme berjalan dalam waktu polinomial.
Namun, semuanya rusak ketika kita mulai berbicara tentang algoritme yang beroperasi pada angka. Mari kita pertimbangkan masalah pengujian apakah suatu bilangan prima atau tidak. Diberikan angka n, Anda dapat menguji apakah n adalah bilangan prima menggunakan algoritma berikut:
function isPrime(n):
for i from 2 to n - 1:
if (n mod i) = 0, return false
return true
Jadi berapa kompleksitas waktu dari kode ini? Nah, loop dalam itu berjalan O (n) kali dan setiap kali melakukan sejumlah pekerjaan untuk menghitung n mod i (sebagai batas atas yang sangat konservatif, ini pasti dapat dilakukan dalam waktu O (n 3 )). Oleh karena itu, keseluruhan algoritma ini berjalan dalam waktu O (n 4 ) dan mungkin jauh lebih cepat.
Pada tahun 2004, tiga ilmuwan komputer menerbitkan sebuah makalah berjudul PRIMES is in P yang memberikan algoritme waktu polinomial untuk menguji apakah suatu bilangan adalah bilangan prima. Itu dianggap sebagai hasil yang penting. Jadi apa masalahnya? Bukankah kita sudah memiliki algoritma waktu polinomial untuk ini, yaitu yang di atas?
Sayangnya, kami tidak melakukannya. Ingat, definisi formal dari kompleksitas waktu berbicara tentang kompleksitas algoritma sebagai fungsi dari jumlah bit input. Algoritma kita berjalan dalam waktu O (n 4 ), tetapi apakah itu sebagai fungsi dari jumlah bit masukan? Nah, menuliskan bilangan n membutuhkan O (log n) bit. Oleh karena itu, jika kita membiarkan x menjadi jumlah bit yang diperlukan untuk menulis masukan n, runtime dari algoritma ini sebenarnya adalah O (2 4x ), yang bukan merupakan polinomial dalam x.
Inilah inti dari perbedaan antara waktu polinomial dan waktu pseudopolinomial. Di satu sisi, algoritme kami adalah O (n 4 ), yang terlihat seperti polinomial, tetapi di sisi lain, menurut definisi formal waktu polinomial, ini bukan waktu polinomial.
Untuk mendapatkan intuisi mengapa algoritme tersebut bukan algoritme waktu polinomial, pikirkan hal berikut. Misalkan saya ingin algoritme harus melakukan banyak pekerjaan. Jika saya menuliskan masukan seperti ini:
10001010101011
maka akan membutuhkan waktu yang paling buruk, katakanlah T
, untuk menyelesaikannya. Jika sekarang saya menambahkan satu bit di akhir nomor, seperti ini:
100010101010111
Waktu proses sekarang (dalam kasus terburuk) menjadi 2T. Saya dapat menggandakan jumlah pekerjaan yang dilakukan algoritme hanya dengan menambahkan satu bit lagi!
Algoritme berjalan dalam waktu pseudopolynomial jika runtime adalah beberapa polinomial dalam nilai numerik input , bukan dalam jumlah bit yang diperlukan untuk mewakilinya. Algoritme pengujian utama kami adalah algoritme waktu pseudopolinomial, karena berjalan dalam waktu O (n 4 ), tetapi ini bukan algoritme waktu polinomial karena sebagai fungsi dari jumlah bit x yang diperlukan untuk menuliskan input, runtime adalah O (2 4x ). Alasan mengapa kertas "PRIMES ada di P" begitu signifikan adalah karena runtime-nya (kira-kira) O (log 12 n), yang sebagai fungsi dari jumlah bit adalah O (x 12 ).
Jadi mengapa ini penting? Nah, kami memiliki banyak algoritma waktu pseudopolynomial untuk memfaktorkan bilangan bulat. Namun, algoritme ini, secara teknis, adalah algoritme waktu eksponensial. Ini sangat berguna untuk kriptografi: jika Anda ingin menggunakan enkripsi RSA, Anda harus percaya bahwa kami tidak dapat memfaktorkan angka dengan mudah. Dengan meningkatkan jumlah bit dalam angka menjadi nilai yang sangat besar (katakanlah, 1024 bit), Anda dapat membuat jumlah waktu yang harus diambil oleh algoritme pemfaktoran waktu semu menjadi begitu besar sehingga akan sepenuhnya dan sama sekali tidak layak untuk memfaktorkan nomor. Sebaliknya, jika kita dapat menemukan algoritme pemfaktoran waktu polinomial , belum tentu demikian. Menambahkan lebih banyak bit dapat menyebabkan pekerjaan bertambah banyak, tetapi pertumbuhan hanya akan menjadi pertumbuhan polinomial, bukan pertumbuhan eksponensial.
Meskipun demikian, dalam banyak kasus algoritme waktu pseudopolynomial baik-baik saja karena ukuran angkanya tidak terlalu besar. Sebagai contoh, urutan penghitungan memiliki waktu proses O (n + U), di mana U adalah bilangan terbesar dalam larik. Ini adalah waktu pseudopolynomial (karena nilai numerik U membutuhkan O (log U) bit untuk menuliskannya, sehingga runtime eksponensial dalam ukuran input). Jika kita secara artifisial membatasi U sehingga U tidak terlalu besar (katakanlah, jika kita membiarkan U menjadi 2), maka runtime-nya adalah O (n), yang sebenarnya merupakan waktu polinomial. Beginilah cara kerja radix sort : dengan memproses angka satu bit pada satu waktu, waktu proses setiap putaran adalah O (n), sehingga waktu proses keseluruhan adalah O (n log U). Ini benar-benar adalah waktu polinomial, karena penulisan n bilangan yang akan diurutkan menggunakan Ω (n) bit dan nilai log U berbanding lurus dengan jumlah bit yang diperlukan untuk menuliskan nilai maksimum pada larik.
Semoga ini membantu!