Algoritma Quickselect

2024-04-24 Last updated: 2025-08-02

13 min read

Intro

Tahun lalu waktu lagi persiapan interview, tiba-tiba kepikiran, bagaimana kalau nanti pas lagi interview ditanya tentang algoritma Quickselect dan bagaimana cara membuktikan bahwa algoritma Quickselect dapat bekerja dalam kompleksitas O(N)O(N). Teringat kembali waktu masih belajar tentang rekursi di kelas Desain Analisis & Algoritma (DAA) waktu kuliah dan mencoba membuka kembali ingatan waktu itu. Butuh waktu yang cukup lama untuk mempelajari algoritma ini kembali, padahal dulu waktu belajar teringat cukup mudah.

Terus setelah memahami kembali dan takut akan lupa lagi, jadinya aku nulis post kecil-kecilan kayak gini tentang apa aja yang udah kupelajari dengan harapan semoga kalau nanti di masa depan aku lupa lagi sama topik ini, aku bisa menggunakan post ini buat dibaca. Ini juga sesuai dengan teknik Feynman dalam belajar yaitu belajar dengan mengajar. Semoga post ini dapat berguna juga bagi pembaca post ini.

Latar Belakang Permasalahan

Sebelum masuk terlalu dalam ke definisi algoritma Quickselect, di sini aku mau mencoba menjelaskan terlebih dahulu permasalahan apa yang ingin diselesaikan dengan algoritma Quickselect.

Misalkan kita diberikan NN buah bilangan bulat positif. Kita ingin mencari elemen terkecil ke-KK. Bagaimana cara paling efisien untuk menyelesaikannya?

Salah satu solusi yang cukup mudah dan intuitif adalah dengan melakukan sorting terlebih dahulu menggunakan algoritma sorting yang cepat seperti Mergesort atau Quicksort. Kemudian kita cukup mengakses elemen ke-KK pada barisan bilangan yang telah diurutkan tersebut. Algoritma ini akan memiliki kompleksitas waktu O(NlogN)O(N \log N).

Quick Sort
4
5
1
3
2
Number of elements: 5
5||8
Contoh mencari elemen terkecil ke-4 dengan menggunakan algoritma sorting

Cara lain yang dapat digunakan adalah dengan menggunakan sedikit modifikasi pada algoritma Selection Sort dimana kita mencari elemen terkecil pertama, kedua, ..., hingga ke-KK. Tetapi algoritma ini sendiri juga memiliki kompleksitas waktu O(NK)O(NK) dimana untuk nilai KK yang besar akan membuat algoritma ini memiliki worst case berupa O(N2)O(N^2).

Selection Sort
4
5
1
3
2
Number of elements: 5
5||8
Contoh mencari elemen terkecil ke-4 dengan menggunakan algoritma Selection Sort

Apakah ada algoritma yang lebih cepat selain kedua algoritma di atas untuk menyelesaikan permasalahan tersebut? Tentu saja ada! Quickselect!

Apa itu Quickselect

Apabila kalian pernah mempelajari algoritma Quicksort sebelumnya, maka algoritma Quickselect ini akan terdengar familiar. Karena pada awalnya algoritma Quickselect ini dibahas pertama kali pada paper pertama yang membahas Quicksort. Ide utama dari algoritma ini adalah menggunakan Decrease and Conquer variasi dari Divide and Conquer dimana kita membagi permasalahan menjadi subproblem yang lebih kecil sehingga jumlah operasi yang diperlukan juga menjadi lebih sedikit.

Berikut kurang lebih isi dari algoritma Quickselect ini

  1. Pilih sebuah pivot secara acak
  2. Lakukan pengecekan setiap elemen pada array, hitung ada berapa banyak elemen yang lebih kecil dari pivot, dan lebih besar dari pivot.
  3. Lakukan salah satu dari kasus berikut:
    1. Apabila jumlah elemen yang lebih kecil dari pivot >K > K. Maka kita tau bahwa elemen terkecil ke-KK lebih kecil dari pivot. Kita akan memecah ini menjadi subproblem baru dimana kita mencari elemen terkecil ke-KK pada array yang terdiri dari elemen-elemen yang << dari pivot.
    2. Apabila jumlah elemen yang lebih kecil dari pivot <K < K maka kita tau bahwa elemen terkecil ke-KK lebih besar dari pivot. Sama seperti sebelumnya kita akan memecah ini menjadi subproblem baru dimana kita akan mencari elemen terkecil ke-KK pada array yang terdiri dari elemen >> pivot.
    3. Apabila bukan kedua kasus di atas maka kita tau bahwa elemen terkecil ke-KK adalah pivot dan kita berhenti mencari elemen terkecil ke-KK.
  4. Kembali ke langkah 1, apabila elemen ke-KK belum ditemukan.

Oke-oke, jika kalian bingung saat membaca algoritma di atas, atau bahkan takut dengan panjangnya teks di atas dan langsung skip ke bagian ini, tidak apa-apa. Karena di bagian ini aku akan coba menjelaskan algoritma Quickselect ini secara bertahap.

Langkah pertama, pilih sebuah pivot secara acak.

Pada langkah ini, kita akan memilih pivot secara acak, tetapi demi kemudahan penjelasan, kita asumsikan pivot yang kita pilih merupakan elemen terakhir pada array.

Quickselect - 1
7
1
4
8
2
6
3
5
Memilih pivot secara acak

Langkah kedua, lakukan pengecekan setiap elemen pada array.

Setelah memilih pivot, sekarang kita akan melakukan pengecekan setiap elemen pada array terhadap pivot. Tujuannya adalah untuk menghitung berapa banyak elemen yang lebih kecil dan lebih besar dari pivot. Ini berguna, untuk mereduksi pencarian kita menjadi subproblem yang lebih kecil. Perhatikan animasi di bawah ini.

Quickselect - 2
7
1
4
8
2
6
3
5
Komparasi elemen dengan pivot

Apabila kita lihat dari animasi di atas, maka elemen yang lebih kecil dari pivot akan ditandai dengan latar belakang warna hijau dan elemen yang lebih besar dari pivot akan ditandai dengan latar belakang warna merah. Dari hasil ini kita dapat melakukan langkah selanjutnya yaitu mereduksi pencarian.

Langkah ketiga, reduksi pencarian menjadi subproblem yang lebih kecil

Kasus 1: Jumlah elemen yang lebih kecil dari pivot > K

Untuk kasus pertama, misalkan kita ingin mencari elemen terkecil ke-33. Perhatikan animasi di bawah.

Quickselect - 3.1
7
1
4
8
2
6
3
5
Kasus dimana jumlah elemen yang lebih kecil dari pivot > 3

Pada kasus ini, kita tau bahwa jumlah elemen yang lebih kecil dari pivot >3> 3. Maka kita tau bahwa elemen terkecil ke-33 pasti berada pada array yang lebih kecil dari pivot. Dengan cara ini kita dapat memecahnya menjadi subproblem baru dimana kita mencari elemen terkecil ke-33 pada array tersebut. Atau dapat dituliskan sebagai quickselect([1,4,2,3],3)quickselect([1, 4, 2, 3], 3).

Quickselect - 3.1
1
4
2
3
Hasil array setelah reduksi

Kasus 2: Jumlah elemen yang lebih kecil dari pivot < K

Untuk kasus ini, kita gunakan kembali array pada kasus pertama, tetapi kali ini kita ingin mencari elemen terkecil ke-66. Perhatikan animasi di bawah.

Quickselect - 3.2
7
1
4
8
2
6
3
5
Kasus dimana jumlah elemen yang lebih kecil dari pivot < 6

Pada kasus ini, kita tau bahwa jumlah elemen yang lebih kecil dari pivot <6< 6. Maka kita tau bahwa elemen terkecil ke-66 pasti lebih besar dari pivot. Sama seperti sebelumnya kita akan memecah ini menjadi subproblem baru dimana kita akan mencari elemen terkecil ke-66 pada array tersebut. Atau dapat dituliskan sebagai quickselect([7,8,6],1)quickselect([7, 8, 6], 1).

Ingat bahwa pada kasus ini, kita harus mengurangi nilai KK dengan jumlah elemen yang lebih kecil sama dengan dari pivot. Sehingga nilai K yang kita cari menjadi 65=16 - 5 = 1 (4 elemen di kiri + 1 pivot).

Quickselect - 3.2
7
8
6
Hasil array setelah reduksi

Kasus 3: Jumlah elemen yang lebih kecil sama dengan dari pivot = K

Kasus ini, merupakan salah satu kasus termudah, karena artinya elemen terkecil ke-KK adalah pivot itu sendiri.

Quickselect - 3.3
7
1
4
8
2
6
3
5
Elemen terkecil ke-k merupakan pivot

Langkah keempat, kembali ke langkah 1

Setelah menjalankan langkah ketiga, kita telah mereduksi permasalahan menjadi subproblem yang lebih kecil sehingga kita bisa menggunakan konsep rekursi dan kembali ke langkah pertama.

Kurang lebih beginilah proses algoritma Quickselect bekerja secara keseluruhan.

Kalian bisa mencoba memainkan animasi di bawah dan mengatur elemen ke-KK yang ingin dicari.

Algoritma Quickselect
7
1
4
8
2
6
3
5
Find the k-th element: 3
1||||||8
Algoritma Quickselect dengan elemen terakhir sebagai pivot

Sebagai catatan, seharusnya algoritma ini menggunakan pivot secara acak, tetapi demi kemudahan penjelasan, kita asumsikan pivot yang kita pilih merupakan elemen terakhir pada array.

Analasis kompleksitas

Bagaimana dengan kompleksitas algoritma ini? Kalau kita lihat secara sekilas jumlah operasi yang kita lakukan akan sebanyak jumlah elemen pada array yaitu array|array| ditambah dengan jumlah elemen pada array setelah dipecah menjadi subproblem. Secara simpel, banyak iterasi yang dilakukan dapat ditulis sebagai berikut quickselect(arr)=len(arr)+quickselect(newarr)quickselect(arr) = len(arr) + quickselect(newarr). Dimana newarr adalah array baru yang tercipta setelah langkah ketiga.

Untuk perhitungan kasus terbaik dan kasus terburuk pada algoritma ini, kita misalkan pada awalnya terdapat NN buah elemen. Setelah memilih sebuah pivot secara acak, maka kita akan membagi array menjadi 2 bagian dengan panjang antara NkN - k dan kk dimana kk adalah sebuah nilai konstanta yang bergantung pada performa pivot yang kita pilih. Sehingga fungsi tersebut akan menjadi quickselect(arr)=len(arr)+quickselect(arrk)quickselect(arr) = len(arr) + quickselect(arr_{k}) atau quickselect(arr)=len(arr)+quickselect(arrNk)quickselect(arr) = len(arr) + quickselect(arr_{N-k}). Dimana arrkarr_k adalah array yang terdiri dari kk elemen dan arrNkarr_{N-k} adalah array yang terdiri dari NkN-k elemen.

Kemungkinan Hasil Reduksi
7
1
4
8
2
6
3
5
Silahkan tekan tab untuk melihat kemungkinan hasil array baru setelah reduksi

Di sini kita dengan asumsi kasus terburuk, kita akan selalu milih array dengan jumlah elemen yang lebih banyak sehingga fungsi akan menjadi quickselect(arr)=len(arr)+quickselect(arrmax(Nk,k))quickselect(arr) = len(arr) + quickselect(arr_{max(N-k, k)}).

Kasus terbaik akan terjadi pada saat nilai dari Nk=kN-k = k. Dimana nilai dari kk akan sama dengan N/2N/2. Sehingga pada setiap pemecahan subproblem kita mengubah array dengan panjang NN menjadi N2\frac{N}{2}. Sehingga fungsi kita akan menjadi quickselect(arr)=len(arr)+quickselect(arraylen(arr)/2)quickselect(arr) = len(arr) + quickselect(array_{len(arr)/2}). Sehingga apabila dijabarkan, akan didapatkan total perhitungan operasi sebagai berikut N+N2+N4+N8+...+1=2NN + \frac{N}{2} + \frac{N}{4} + \frac{N}{8} + ... + 1 = 2N menggunakan jumlah deret geometri S=a(1r)S = \frac{a}{(1 - r)}, dimana a=Na = N dan r=1/2r = 1/2 didapatkan N112=N1/2=2N=O(N)\frac{N}{1-\frac{1}{2}} = \frac{N}{1/2} = 2N = O(N).

Kasus terbaik algoritma Quickselect
1
2
3
5
8
6
7
4
Find the k-th element: 5
Kasus terbaik dimana elemen terbagi secara rata

Sedangkan kasus terburuk akan terjadi apabila pemilihan pivot tidak optimal dimana kita memilih pivot sehingga nilai dari salah satu NkN - k atau kk menjadi sangat besar yaitu max(Nk,k)=N1max(N-k, k) = N - 1. Sehingga fungsi kita akan menjadi quickselect(arr)=len(arr)+quickselect(arraylen(arr1))quickselect(arr) = len(arr) + quickselect(array_{len(arr - 1)}). Sehingga apabila dijabarkan, akan didapatkan total perhitungan operasi sebagai berikut N+(N1)+(N2)+(N3)+...+1=O(N2)N + (N - 1) + (N - 2) + (N - 3) + ... + 1 = O(N^2).

Kasus terburuk algoritma Quickselect
1
2
3
4
5
6
7
8
Find the k-th element: 1
Kasus terburuk dimana hasil array baru terpecah secara tidak merata

Dapat dilihat di atas kompleksitas algoritma Quickselect ini memiliki kompleksitas O(N)O(N) dan kasus terburuk O(N2)O(N^2), bagaimana dengan kasus rata-rata? Apakah aman untuk menggunakan algoritma ini secara umum? Ya, apabila kita memilih pivot secara acak maka kompleksitas rata-rata adalah O(N)O(N). Bagaimana pembuktiannya?

Pseudocode algoritma Quickselect

Pseudocode algoritma ini adalah sebagai berikut:

def quickselect(array, k) {
    pivot = random(array) # pilih pivot secara acak
    left = []
    right = []
    for elem in array {
        if elem < pivot {
            left.push(elem)
        } else if elem > pivot {
            right.push(elem)
        }
    }
    if len(left) > k {
        # kalau jumlah element < pivot lebih banyak dari k
        # cari pada array dengan elemen lebih kecil dari pivot
        return quickselect(left, k)
    } else if len(left) + 1 < k {
        # sebaliknya, cari pada array dengan elemen lebih besar
        return quickselect(right, k - len(left) - 1)
    } else {
        # elemen terkecil ke-k adalah pivot
        return pivot
    }
}

Sebagai catatan, pseudode algoritma di atas bukanlah yang paling efisien dari sisi space complexity karena kita menggunakan array tambahan untuk menyimpan elemen yang lebih kecil dan lebih besar dari pivot. Terdapat implementasi lain yang tidak menggunakan array tambahan, tetapi tidak dijabarkan di sini.

Pembuktian kompleksitas rata-rata

Misalkan pivot yang kita pilih merupakan 50% elemen tertengah saat diurutkan.

Ilustrasi 50% elemen tertengah saat diurutkan
50% elemen tertengah saat diurutkan

Maka kita pasti mendapatkan pembagian pada operasi pemecahan subproblem terburuk menjadi 25% dan 75%. Yaitu saat pivot yang kita pilih berada di antara elemen ke 25% atau 75% (daerah yang diarsir). Dapat kita lihat bahwa kasus terburuk adalah kita akan mengubah array dengan panjang NN menjadi 75%75\%-nya atau sebanyak 34N\frac{3}{4}N. Sehingga total perhitungan operasi akan menjadi N+34N+916N+2764N+...+1N + \frac{3}{4}N + \frac{9}{16}N + \frac{27}{64}N + ... + 1. Tetapi ini belum semuanya, karena ini masih menggunakan asumsi bahwa pivot yang kita pilih akan selalu berada di 50% elemen tertengah dimana pada kenyataan hal tersebut belum terbukti. Kita tau bahwa probabilitas untuk mendapatkan 50% elemen tertengah apabila kita memilih pivot secara acak adalah 12\frac{1}{2}. Maka expected value pivot yang kita pilih merupakan 50% elemen tertengah adalah 2 (secara intuitif apabila kita tahu probabilitas elemen adalah 1/2, maka kira-kira dibutuhkan dua kali percobaan). Maka banyak operasi akan menjadi 2N+342N+9162N+27642N+...+12N + \frac{3}{4}2N + \frac{9}{16}2N + \frac{27}{64}2N + ... + 1 yang ekuivalen dengan 2(N+34N+916N+2764N+...+1)2(N + \frac{3}{4}N + \frac{9}{16}N + \frac{27}{64}N + ... + 1). Menggunakan jumlah deret geometri dimana nilai a=Na = N dan r=34r = \frac{3}{4} didapatkan 2N134=2N14=2×4N=8N=O(N)2\frac{N}{1 - \frac{3}{4}} = 2 \frac{N}{\frac{1}{4}} = 2 × 4N = 8N = O(N). Terbukti bahwa kompleksitas rata-rata dari algoritma ini adalah O(N)O(N).

Mengapa pemilihan pivot harus acak?

Sebenarnya ini sudah terjawab sebelumnya, karena apabila kita memilih pivot secara tidak acak, akan sangat mudah untuk mencari kasus terburuk dimana kita akan mendapatkan kompleksitas O(N2)O(N^2). Sebagai contoh, untuk algoritma dimana kita memilih elemen terakhir sebagai pivot memiliki kasus buruk seperti yang dapat kita lihat pada contoh di atas pada bagian kasus terburuk.

Penutup

Dulu sebelum aku mengenal algoritma Quickselect, aku tidak pernah tau kalau ada cara yang lebih cepat untuk menyelesaikan permasalahan mencari elemen terkecil ke-KK selain menggunakan algoritma sorting. Saat pertama kali mendengar algoritma ini, aku lumayan terpukau dengan cara kerja algoritma ini karena walau terlihat cukup sederhana, algoritma ini memiliki kompleksitas yang efisien.

Terdapat juga algoritma lain yang dapat digunakan untuk mencari elemen terkecil ke-kk yaitu dengan menggunakan Median of Medians yang memiliki kompleksitas waktu O(N)O(N) pada kasus terburuk. Tetapi algoritma ini memiliki konstanta yang lebih besar dibandingkan dengan algoritma Quickselect dan sedikit lebih rumit untuk diimplementasikan.

Terima kasih sudah membaca post ini sampai akhir, semoga bisa bermanfaat! :D