1. Gambaran keseluruhan
Dalam tutorial ini, kita akan meneroka algoritma QuickSort secara terperinci, dengan fokus pada pelaksanaan Java.
Kami juga akan membincangkan kelebihan dan kekurangannya dan kemudian menganalisis kerumitan waktunya.
2. Algoritma QuickSort
Quicksort adalah algoritma penyortiran, yang memanfaatkan prinsip pembahagi dan penaklukan. Ia mempunyai kerumitan O (n log n) rata-rata dan ini adalah salah satu algoritma penyusun yang paling banyak digunakan, terutamanya untuk jumlah data yang besar.
Penting untuk diingat bahawa Quicksort bukan algoritma yang stabil. Algoritma penyusun stabil adalah algoritma di mana elemen dengan nilai yang sama muncul dalam urutan yang sama dalam output yang disusun seperti yang muncul dalam senarai input.
Senarai input dibahagikan kepada dua sub-senarai oleh elemen yang dipanggil pivot; satu sub-senarai dengan unsur-unsur yang kurang daripada pangsi dan satu lagi dengan unsur yang lebih besar daripada pangsi. Proses ini berulang untuk setiap sub-senarai.
Akhirnya, semua sub-senarai yang disusun bergabung untuk membentuk hasil akhir.
2.1. Langkah Algoritma
- Kami memilih elemen dari senarai, yang dipanggil pangsi. Kami akan menggunakannya untuk membahagikan senarai menjadi dua sub-senarai.
- Kami menyusun semula semua elemen di sekitar pangsi - elemen dengan nilai yang lebih kecil diletakkan di hadapannya, dan semua elemen lebih besar daripada pangsi selepasnya. Selepas langkah ini, pivot berada pada kedudukan terakhir. Ini adalah langkah partition yang penting.
- Kami menerapkan langkah-langkah di atas secara berulang ke kedua sub-senarai di kiri dan kanan pangsi.
Seperti yang kita lihat, quicksort secara semula jadi adalah algoritma rekursif, seperti setiap pendekatan membagi dan menaklukkan.
Mari kita ambil contoh mudah untuk memahami algoritma ini dengan lebih baik.
Arr[] = {5, 9, 4, 6, 5, 3}
- Anggaplah kita memilih 5 sebagai teras penting untuk kesederhanaan
- Kami akan meletakkan semua elemen kurang dari 5 pada kedudukan pertama array: {3, 4, 5, 6, 5, 9}
- Kami kemudian akan mengulanginya untuk sub-array kiri {3,4}, menjadikan 3 sebagai pangsi
- Tidak ada unsur kurang dari 3
- Kami menggunakan quicksort pada sub-array di sebelah kanan pangsi, iaitu {4}
- Sub-array ini hanya terdiri daripada satu elemen yang disusun
- Kami meneruskan bahagian kanan susunan asal, {6, 5, 9} sehingga kami mendapat susunan terakhir yang diperintahkan
2.2. Memilih Pivot Optimum
Perkara penting dalam QuickSort adalah memilih pangsi terbaik. Elemen tengah tentu saja adalah yang terbaik, kerana ia akan membahagikan senarai menjadi dua sub-senarai yang sama.
Tetapi mencari elemen tengah dari senarai yang tidak tersusun adalah sukar dan memakan masa, sebab itulah kita mengambil elemen asas, elemen terakhir, median atau elemen rawak lain.
3. Pelaksanaan di Jawa
Kaedah pertama adalah quickSort () yang mengambil parameter array yang akan disusun, indeks pertama dan terakhir. Pertama, kami memeriksa indeks dan meneruskan hanya jika masih ada unsur yang akan disusun.
Kami mendapat indeks pivot yang disusun dan menggunakannya untuk memanggil kaedah partition () secara rekursif dengan parameter yang sama dengan kaedah quickSort () , tetapi dengan indeks yang berbeza:
public void quickSort(int arr[], int begin, int end) { if (begin < end) { int partitionIndex = partition(arr, begin, end); quickSort(arr, begin, partitionIndex-1); quickSort(arr, partitionIndex+1, end); } }
Mari kita teruskan dengan kaedah partition () . Untuk kesederhanaan, fungsi ini mengambil elemen terakhir sebagai pangsi. Kemudian, periksa setiap elemen dan tukar sebelum pangsi jika nilainya lebih kecil.
Pada akhir pembahagian, semua elemen kurang daripada pangsi di sebelah kiri dan semua elemen lebih besar daripada pangsi di sebelah kanannya. Pivot berada pada kedudukan terakhir yang disusun dan fungsi mengembalikan kedudukan ini:
private int partition(int arr[], int begin, int end) { int pivot = arr[end]; int i = (begin-1); for (int j = begin; j < end; j++) { if (arr[j] <= pivot) { i++; int swapTemp = arr[i]; arr[i] = arr[j]; arr[j] = swapTemp; } } int swapTemp = arr[i+1]; arr[i+1] = arr[end]; arr[end] = swapTemp; return i+1; }
4. Analisis Algoritma
4.1. Kerumitan Masa
In the best case, the algorithm will divide the list into two equal size sub-lists. So, the first iteration of the full n-sized list needs O(n). Sorting the remaining two sub-lists with n/2 elements takes 2*O(n/2) each. As a result, the QuickSort algorithm has the complexity of O(n log n).
In the worst case, the algorithm will select only one element in each iteration, so O(n) + O(n-1) + … + O(1), which is equal to O(n2).
On the average QuickSort has O(n log n) complexity, making it suitable for big data volumes.
4.2. QuickSort vs MergeSort
Let's discuss in which cases we should choose QuickSort over MergeSort.
Although both Quicksort and Mergesort have an average time complexity of O(n log n), Quicksort is the preferred algorithm, as it has an O(log(n)) space complexity. Mergesort, on the other hand, requires O(n) extra storage, which makes it quite expensive for arrays.
Quicksort requires to access different indices for its operations, but this access is not directly possible in linked lists, as there are no continuous blocks; therefore to access an element we have to iterate through each node from the beginning of the linked list. Also, Mergesort is implemented without extra space for LinkedLists.
In such case, overhead increases for Quicksort and Mergesort is generally preferred.
5. Conclusion
Quicksort adalah algoritma penyusun elegan yang sangat berguna dalam kebanyakan kes.
Ini biasanya algoritma "di tempat", dengan kerumitan waktu rata-rata O (n log n).
Satu lagi perkara menarik untuk disebutkan ialah kaedah Java's Arrays.sort () menggunakan Quicksort untuk menyusun susunan primitif. Pelaksanaannya menggunakan dua pivot dan berkinerja jauh lebih baik daripada penyelesaian mudah kami, sebab itulah kod pengeluaran biasanya lebih baik menggunakan kaedah perpustakaan.
Seperti biasa, kod untuk pelaksanaan algoritma ini terdapat di repositori GitHub kami.