Ada banyak pertanyaan tentang bagaimana menganalisis waktu berjalan algoritma (lihat, misalnya, analisis runtime dan analisis algoritma ). Banyak yang serupa, misalnya yang meminta analisis biaya loop bersarang atau algoritma Divide & Conquer, tetapi sebagian besar jawaban tampaknya dibuat khusus.
Di sisi lain, jawaban untuk pertanyaan umum lainnya menjelaskan gambaran yang lebih besar (khususnya mengenai analisis asimptotik) dengan beberapa contoh, tetapi tidak bagaimana cara membuat tangan Anda kotor.
Apakah ada metode umum terstruktur untuk menganalisis biaya algoritma? Biaya mungkin waktu berjalan (kompleksitas waktu), atau ukuran biaya lainnya, seperti jumlah perbandingan yang dilakukan, kompleksitas ruang, atau sesuatu yang lain.
Ini seharusnya menjadi referensi pertanyaan yang dapat digunakan untuk mengarahkan pemula; karenanya cakupannya lebih luas dari biasanya. Harap berhati-hati untuk memberikan jawaban umum, yang disajikan secara didaktis yang diilustrasikan oleh setidaknya satu contoh tetapi tetap mencakup banyak situasi. Terima kasih!
Jawaban:
Menerjemahkan Kode ke Matematika
Diberikan (kurang lebih) semantik operasional formal Anda dapat menerjemahkan kode algoritma (pseudo-) secara harfiah ke dalam ekspresi matematika yang memberi Anda hasilnya, asalkan Anda dapat memanipulasi ekspresi menjadi bentuk yang bermanfaat. Ini bekerja dengan baik untuk ukuran biaya tambahan seperti jumlah perbandingan, swap, pernyataan, akses memori, siklus beberapa kebutuhan mesin abstrak, dan sebagainya.
Contoh: Perbandingan di Bubblesort
Pertimbangkan algoritma ini yang mengurutkan array yang diberikan
A
:Katakanlah kita ingin melakukan analisis algoritma penyortiran yang biasa, yaitu menghitung jumlah perbandingan elemen (baris 5). Kami segera mencatat bahwa jumlah ini tidak bergantung pada konten arrayn
A
, hanya pada panjangnya . Jadi kita bisa menerjemahkan (nested) -lompatan secara harfiah ke dalam jumlah (nested); variabel loop menjadi variabel penjumlahan dan rentang membawa. Kita mendapatkan:for
di mana adalah biaya untuk setiap eksekusi baris 5 (yang kami hitung).1
Contoh: Swap dalam Bubblesort
Saya akan menyatakan dengan subprogram yang terdiri dari baris ke dan oleh biaya untuk menjalankan subprogram ini (satu kali).Pi,j Ci,j
i
j
Sekarang katakanlah kita ingin menghitung swap , yaitu seberapa sering dieksekusi. Ini adalah "blok dasar", yaitu subprogram yang selalu dijalankan secara atom dan memiliki biaya konstan (di sini, ). Mengontrak blok semacam itu adalah salah satu penyederhanaan yang berguna yang sering kita terapkan tanpa memikirkan atau membicarakannya.P6,8 1
Dengan terjemahan yang sama seperti di atas, kita sampai pada rumus berikut:
Perhatikan bahwa saya menggunakan sebagai ganti sebagai parameter; kita akan segera melihat alasannya. Saya tidak menambahkan dan sebagai parameter karena biaya tidak tergantung pada mereka di sini (dalam model biaya seragam , yaitu); secara umum, mereka mungkin saja.A n i j C5,9
Jelas, biaya tergantung pada konten (nilai-nilai dan , khususnya) jadi kami harus memperhitungkannya. Sekarang kita menghadapi tantangan: bagaimana kita "membuka" ? Nah, kita bisa membuat ketergantungan pada konten eksplisit:P5,9 A C5,9 A
A[j]
A[j+1]
Untuk setiap array input yang diberikan, biaya-biaya ini didefinisikan dengan baik, tetapi kami ingin pernyataan yang lebih umum; kita perlu membuat asumsi yang lebih kuat. Mari kita selidiki tiga kasus khas.
Kasus terburuk
Hanya dari melihat jumlah dan mencatat bahwa , kita dapat menemukan batas atas sepele untuk biaya:C5,9(A(i,j))∈{0,1}
Tetapi dapatkah ini terjadi , yaitu adakah untuk batas atas ini tercapai? Ternyata, ya: jika kita memasukkan array yang diurutkan terbalik dari elemen berbeda berpasangan, setiap iterasi harus melakukan swap¹. Oleh karena itu, kami telah mendapatkan jumlah swap terburuk yang tepat untuk Bubblesort.A
Kasus terbaik
Sebaliknya, ada batas bawah sepele:
Ini juga dapat terjadi: pada array yang sudah diurutkan, Bubblesort tidak menjalankan satu swap.
Kasus rata-rata
Kasus terburuk dan terbaik membuka cukup celah. Tapi apa adalah khas jumlah swap? Untuk menjawab pertanyaan ini, kita perlu mendefinisikan apa arti "khas". Secara teori, kami tidak memiliki alasan untuk lebih memilih satu input daripada input lainnya dan oleh karena itu kami biasanya mengasumsikan distribusi yang seragam atas semua input yang mungkin, yaitu setiap input memiliki kemungkinan yang sama besar. Kami membatasi diri untuk array dengan elemen berbeda berpasangan dan dengan demikian mengasumsikan model permutasi acak .
Kemudian, kami dapat menulis ulang biaya kami seperti ini²:
Sekarang kita harus melampaui manipulasi jumlah yang sederhana. Dengan melihat algoritma, kami mencatat bahwa setiap swap menghapus tepat satu inversi dalam (kami hanya pernah bertukar tetangga )³. Artinya, jumlah swap dilakukan pada adalah persis jumlah inversi dari . Dengan demikian, kita dapat mengganti dua jumlah dalam dan mendapatkanA A inv(A) A
Beruntung bagi kami, jumlah rata-rata inversi telah ditentukan
yang merupakan hasil akhir kami. Perhatikan bahwa ini persis setengah dari biaya terburuk.
i = n-1
loop luar yang tidak pernah melakukan apa pun tidak dieksekusi.Metode Umum
Kita telah melihat dalam contoh bahwa kita harus menerjemahkan struktur kontrol ke dalam matematika; Saya akan menyajikan ansambel khas aturan terjemahan. Kita juga telah melihat bahwa biaya dari setiap subprogram yang diberikan mungkin tergantung pada keadaan saat ini , yaitu (kira-kira) nilai variabel saat ini. Karena algoritma (biasanya) memodifikasi keadaan, metode umum sedikit rumit untuk diketahui. Jika Anda mulai merasa bingung, saya sarankan Anda kembali ke contoh atau membuat sendiri.
Kami menyatakan dengan keadaan saat ini (bayangkan sebagai seperangkat tugas variabel). Ketika kami menjalankan program yang dimulai dengan status , kami berakhir dengan status (disediakan berakhir).ψ ψ ψ/P
P
P
Pernyataan individu
Diberi hanya satu pernyataanCS(ψ)
S;
, Anda menetapkan biaya . Ini biasanya akan menjadi fungsi konstan.Ekspresi
Jika Anda memiliki ekspresi
E
formulirE1 ∘ E2
(misalnya, ekspresi aritmatika di mana∘
mungkin penambahan atau penggandaan, Anda menambahkan biaya secara rekursif:Catat itu
jadi Anda mungkin harus fleksibel dengan aturan ini.
Urutan
Diberikan program
P
sebagai urutan programQ;R
, Anda menambahkan biayaPersyaratan
Diberikan program
P
formulirif A then Q else R end
, biaya tergantung pada negara:Secara umum, mengevaluasi
A
mungkin sangat mengubah keadaan, sehingga pembaruan untuk biaya masing-masing cabang.For-Loops
Diberikan program
P
formulirfor x = [x1, ..., xk] do Q end
, tetapkan biayadi mana adalah status sebelum memproses untuk nilai , yaitu setelah iterasi dengan diatur ke , ..., .ψi
Q
xi
x
x1
xi-1
Perhatikan konstanta tambahan untuk pemeliharaan loop; variabel loop harus dibuat ( ) dan menetapkan nilainya ( )). Ini relevan sejakcinit_for cstep_for
xi
mungkin mahal danfor
-Lingkaran dengan tubuh kosong (misalnya setelah menyederhanakan dalam kasus terbaik dengan biaya tertentu) tidak memiliki biaya nol jika melakukan iterasi.Sementara-Loops
Diberikan program
P
formulirwhile A do Q end
, tetapkan biayaDengan memeriksa algoritme, perulangan ini sering kali dapat direpresentasikan dengan baik sebagai jumlah yang mirip dengan perulangan for-loop.
Contoh: Pertimbangkan algoritma singkat ini:
Dengan menerapkan aturan, kita dapatkan
dengan beberapa biaya konstan untuk masing-masing pernyataan. Kami berasumsi secara implisit bahwa ini tidak tergantung pada negara (nilai-nilai dan ); ini mungkin atau mungkin tidak benar dalam "kenyataan": pikirkan tentang luapan!c…
i
x
Sekarang kita harus menyelesaikan pengulangan ini untuk . Kami mencatat bahwa baik jumlah iterasi bukan biaya loop body bergantung pada nilai , sehingga kami dapat menjatuhkannya. Kita dibiarkan dengan pengulangan ini:C1,4
i
Ini diselesaikan dengan sarana dasar untuk
memperkenalkan kembali kondisi penuh secara simbolis; jika , maka .ψ={…,x:=5,…} ψ(x)=5
Panggilan Prosedur
Diberikan program
P
formulirM(x)
untuk beberapa parameter dix
manaM
prosedur dengan parameter (bernama)p
, menetapkan biayaCatat lagi konstanta tambahan (yang mungkin sebenarnya bergantung pada !). Panggilan prosedur mahal karena bagaimana mereka diterapkan pada mesin nyata, dan kadang-kadang bahkan mendominasi runtime (misalnya mengevaluasi pengulangan angka Fibonacci secara naif).ccall ψ
Saya membahas beberapa masalah semantik yang mungkin Anda miliki dengan keadaan di sini. Anda akan ingin membedakan negara global dan lokal untuk panggilan prosedur. Mari kita asumsikan kita hanya melewati negara global di sini dan
M
mendapat negara lokal baru, diinisialisasi dengan menetapkan nilaip
tox
. Lebih jauh,x
mungkin ungkapan yang kita (biasanya) asumsikan dievaluasi sebelum melewatinya.Contoh: Pertimbangkan prosedurnya
Sesuai aturan, kami mendapatkan:
Perhatikan bahwa kami mengabaikan negara global, karena
fac
jelas tidak mengaksesnya. Perulangan khusus ini mudah dipecahkanKami telah membahas fitur bahasa yang akan Anda temui dalam kode pseudo yang khas. Waspadai biaya tersembunyi ketika menganalisis kode pseudo tingkat tinggi; jika ragu, buka. Notasi tersebut mungkin terlihat rumit dan tentu saja merupakan masalah selera; konsep-konsep yang tercantum tidak dapat diabaikan. Namun, dengan beberapa pengalaman Anda akan dapat melihat langsung bagian mana dari negara yang relevan untuk ukuran biaya mana, misalnya "ukuran masalah" atau "jumlah simpul". Sisanya dapat dijatuhkan - ini menyederhanakan banyak hal!
Jika Anda berpikir sekarang bahwa ini terlalu rumit, disarankan: itu adalah ! Turunkan biaya tepat dari algoritma dalam model apa pun yang begitu dekat dengan mesin nyata sehingga memungkinkan prediksi runtime (bahkan yang relatif) adalah upaya yang sulit. Dan itu bahkan tidak mempertimbangkan caching dan efek buruk lainnya pada mesin nyata.
Oleh karena itu, analisis algoritme sering disederhanakan sampai titik yang dapat ditelusur secara matematis. Misalnya, jika Anda tidak memerlukan biaya yang pasti, Anda bisa melebih-lebihkan atau meremehkan pada titik mana pun (untuk batas atas, batas bawah): kurangi set konstanta, singkirkan persyaratan, sederhanakan penjumlahan, dan sebagainya.
Catatan tentang biaya asimptotik
Apa yang biasanya Anda temukan dalam literatur dan di web adalah "Analisis Besar-Oh". Istilah yang tepat adalah analisis asimptotik yang berarti bahwa alih-alih mendapatkan biaya yang tepat seperti yang kami lakukan dalam contoh, Anda hanya memberikan biaya hingga faktor konstan dan dalam batas (secara kasar, "untuk besar ").n
Ini (sering) adil karena pernyataan abstrak memiliki beberapa (umumnya tidak diketahui) biaya dalam kenyataan, tergantung pada mesin, sistem operasi dan faktor lainnya, dan runtime pendek dapat didominasi oleh sistem operasi yang mengatur proses di tempat pertama dan yang lainnya. Jadi, Anda mendapatkan sedikit gangguan.
Inilah bagaimana analisis asimptotik berhubungan dengan pendekatan ini.
Identifikasi operasi dominan (yang menyebabkan biaya), yaitu operasi yang paling sering terjadi (hingga faktor konstan). Dalam contoh Bubblesort, satu pilihan yang mungkin adalah perbandingan di baris 5.
Sebagai alternatif, ikat semua konstanta untuk operasi elementer dengan resp maksimum (dari atas). minimum mereka (dari bawah) dan melakukan analisis biasa.
Pastikan Anda memahami arti simbol Landau . Ingatlah bahwa batasan seperti itu ada untuk ketiga kasus ; menggunakan tidak berarti analisis kasus terburuk.O
Bacaan lebih lanjut
Ada banyak tantangan dan trik dalam analisis algoritma. Berikut ini beberapa bacaan yang disarankan.
Bagaimana cara menggambarkan algoritma, membuktikan dan menganalisisnya?
Bagaimana kita dapat mengasumsikan bahwa operasi dasar pada angka membutuhkan waktu yang konstan?
Apa yang merupakan satu unit waktu dalam analisis runtime?
Ada banyak pertanyaan yang ditandai dengan analisis-algoritma yang menggunakan teknik yang mirip dengan ini.
sumber
Hitungan Pernyataan Eksekusi
Ada metode lain, yang diperjuangkan oleh Donald E. Knuth dalam seri The Art of Computer Programming . Berbeda dengan menerjemahkan seluruh algoritma ke dalam satu rumus , ia bekerja secara independen dari semantik kode pada sisi "menyatukan semuanya" dan memungkinkan untuk naik ke level yang lebih rendah hanya jika diperlukan, mulai dari tampilan "mata elang". Setiap pernyataan dapat dianalisis secara terpisah dari yang lainnya, yang mengarah pada perhitungan yang lebih jelas. Namun, teknik ini cocok untuk kode yang agak rinci, bukan kode pseudo tingkat yang lebih tinggi.
Metode
Ini pada prinsipnya cukup sederhana:
Hitung total biaya
Anda dapat memasukkan taksiran dan / atau jumlah simbolis pada titik mana saja, melemahkan resp. generalisasi hasilnya sesuai.
Ketahuilah bahwa langkah 3 bisa sangat rumit. Biasanya ada di sana Anda harus bekerja dengan perkiraan (asimptotik) seperti " " untuk mendapatkan hasil.e77∈O(nlogn)
Contoh: Pencarian mendalam-pertama
Pertimbangkan algoritma grafik-traversal berikut:
Kami berasumsi bahwa grafik (tidak terarah) diberikan oleh daftar adjacency pada node . Biarkan menjadi jumlah tepi.{0,…,n−1} m
Hanya dengan melihat algoritma, kita melihat bahwa beberapa pernyataan dieksekusi sama seringnya dengan yang lain. Kami memperkenalkan beberapa placeholder , dan untuk jumlah eksekusi :A B C ei
Secara khusus, karena setiap panggilan rekursif di jalur 8 menyebabkan panggilan di saluran 3 (dan satu disebabkan oleh panggilan asli dari ). Selanjutnya, karena kondisi harus diperiksa sekali per iterasi tetapi kemudian sekali lagi untuk meninggalkannya.e8=e3−1 e6=e5+e7
foo
dfs
while
Jelas bahwa . Sekarang, selama pembuktian kebenaran kami akan menunjukkan bahwa dijalankan tepat satu kali per node; yaitu, . Tapi kemudian, kita mengulangi setiap daftar adjacency tepat sekali dan setiap tepi menyiratkan dua entri secara total (satu untuk setiap node insiden); kita mendapatkan iterasi secara total. Menggunakan ini, kami memperoleh tabel berikut:A=1 B=n C=2m
foo
Ini membawa kita ke total biaya persis
Dengan membuat instance nilai yang sesuai untuk kita dapat memperoleh lebih banyak biaya konkret. Misalnya, jika kita ingin menghitung akses memori (per kata), kami akan gunakanCi
dan dapatkan
Bacaan lebih lanjut
Lihat di bagian bawah jawaban saya yang lain .
sumber
Analisis algoritma, seperti pembuktian teorema, sebagian besar adalah seni (misalnya ada program sederhana (seperti masalah Collatz ) yang tidak kita ketahui cara menganalisisnya). Kita dapat mengonversi masalah kompleksitas algoritme menjadi masalah matematis, seperti yang dijawab secara komprehensif oleh Raphael , tetapi kemudian untuk menyatakan batasan biaya algoritma dalam hal fungsi yang diketahui, kita harus:
sumber