Insertion sort meniru cara manusia menyusun kartu di tangan: ambil satu kartu, geser kartu-kartu yang lebih besar ke kanan, lalu sisipkan kartu baru ke celah yang terbentuk. Algoritma ini menjadi O(n) pada data hampir terurut dan stabil sehingga sering dipakai sebagai sub-rutin pada hybrid sort seperti introsort (yang dipakai std::sort). Pada Chapter ini kita akan membedah mekanisme insertion sort, mengimplementasikannya, dan menjelaskan mengapa algoritma ini menjadi pilihan untuk sub-array kecil. Pada akhir chapter kita akan paham mengapa insertion sort masih relevan meskipun secara asimptotik O(n²).
Aturan: Sisipkan ke Posisi Benar #
Pada setiap iterasi i dari 1 hingga n-1:
- Simpan
key = v[i]. - Bandingkan
keydenganv[i-1], v[i-2], ...secara mundur. - Geser elemen yang lebih besar dari
keysatu posisi ke kanan. - Sisipkan
keyke posisi yang tepat (sebelum elemen yang lebih besar).
Setelah iterasi i selesai, v[0..i] terurut.
Mekanisme Kerja #
Tiap iterasi menempatkan satu elemen baru ke posisi yang benar dalam bagian already-sorted v[0..i].
Kode: Insertion Sort dengan Instrumentasi #
#include <iostream>
#include <vector>
#include <utility>
void insertion_sort(std::vector<int>& v, long long& cmp, long long& shift) {
cmp = 0;
shift = 0;
for (std::size_t i = 1; i < v.size(); ++i) {
int key = v[i];
int j = static_cast<int>(i) - 1;
while (j >= 0) {
++cmp;
if (v[j] > key) {
v[j + 1] = v[j];
++shift;
--j;
} else break;
}
v[j + 1] = key;
}
}
void print(const std::vector<int>& v) {
for (int x : v) std::cout << ' ' << x;
std::cout << '\n';
}
int main() {
std::vector<int> a = {5, 4, 3, 2, 1};
long long cmp, shift;
insertion_sort(a, cmp, shift);
std::cout << "Worst case [5,4,3,2,1]:\n hasil = "; print(a);
std::cout << " cmp = " << cmp << ", shift = " << shift << '\n';
std::vector<int> b = {1, 2, 3, 4, 5};
insertion_sort(b, cmp, shift);
std::cout << "\nBest case [1,2,3,4,5]:\n hasil = "; print(b);
std::cout << " cmp = " << cmp << ", shift = " << shift << '\n';
std::vector<int> c = {1, 2, 3, 5, 4, 6, 7};
insertion_sort(c, cmp, shift);
std::cout << "\nHampir terurut [1,2,3,5,4,6,7]:\n hasil = "; print(c);
std::cout << " cmp = " << cmp << ", shift = " << shift << '\n';
return 0;
}Output yang Dihasilkan:
Worst case [5,4,3,2,1]:
hasil = 1 2 3 4 5
cmp = 10, shift = 10
Best case [1,2,3,4,5]:
hasil = 1 2 3 4 5
cmp = 4, shift = 0
Hampir terurut [1,2,3,5,4,6,7]:
hasil = 1 2 3 4 5 6 7
cmp = 7, shift = 1Bedah Kode Baris-per-Baris #
int key = v[i];: simpan elemen yang akan disisipkan ke variabel sementarakey. Langkah ini penting karenav[i]akan ditimpa saat pergeseran.int j = static_cast<int>(i) - 1;: indeks pembanding, dimulai dari elemen sebelumi.while (j >= 0): loop mundur hingga awal array atau hingga ditemukan posisi yang benar.++cmp;: catat setiap pembandingan untuk instrumentasi.if (v[j] > key): bandingkan dengan>(strict greater). Stabilitas terjaga karena elemen yang equal (v[j] == key) tidak digeser —keytetap berada setelahv[j]sesuai urutan input.v[j + 1] = v[j]; ++shift; --j;: geser elemen yang lebih besar satu posisi ke kanan, catat pergeseran, lanjut ke elemen sebelumnya.v[j + 1] = key;: tempatkankeypada posisi yang benar — celah yang ditinggalkan setelah pergeseran.- Output worst case
[5,4,3,2,1]:cmp = 10 = n(n-1)/2 = 5·4/2,shift = 10. Sesuai formula worst caseΘ(n²). - Output best case
[1,2,3,4,5]:cmp = 4 = n-1,shift = 0. Setiap iterasi hanya satu pembandingan (langsungbreak). KompleksitasΘ(n). - Output hampir terurut:
cmp = 7(1+1+1+2+1+1 = 7 untuk n=7),shift = 1. Insertion sort adaptif terhadap data hampir terurut — inilah alasan ia menjadi sub-rutin introsort.
Kompleksitas #
| Kasus | Kompleksitas | Penjelasan |
|---|---|---|
| Best case | O(n) | Data sudah terurut — 1 pembandingan per iterasi |
| Average case | O(n²) | Separuh pasangan perlu digeser |
| Worst case | O(n²) | Data terurut descending — semua elemen digeser |
| Space | O(1) | Hanya key dan j |
| Swaps | O(n²) | Sama dengan jumlah pembandingan |
Best case O(n) tercapai bila array sudah terurut — hanya n-1 pembandingan dengan 0 pergeseran. Inilah yang membuat insertion sort unik dibanding bubble dan selection sort.
Stabilitas #
Insertion sort stabil. Pembandingan memakai v[j] > key (strict greater), sehingga elemen yang equal dengan key tidak digeser. Akibatnya key (yang berasal dari indeks lebih besar) selalu ditempatkan setelah elemen equal yang sudah ada — urutan relatif terjaga.
In-Place #
Insertion sort in-place dengan O(1) memori tambahan. Hanya variabel key, i, dan j yang dialokasikan.
Mengapa Insertion Sort Dipakai pada std::sort #
std::sort di libstdc++ dan libc++ mengimplementasikan introsort — kombinasi:
- Quick sort untuk partisi awal.
- Heap sort bila kedalaman rekursi melebihi
2·log₂ n(menghindari worst case quick sort). - Insertion sort bila ukuran sub-array ≤ 16 (cutoff).
Alasan insertion sort dipilih untuk sub-array kecil:
- Konstanta kecil — tidak ada overhead rekursi atau pemanggilan fungsi.
- Adaptif — pada data hampir terurut (sering terjadi setelah quick sort selesai), kompleksitas mendekati
O(n). - Stabil relatif terhadap posisi elemen equal di sub-array.
- Branch predictor modern bekerja sangat baik pada loop sederhana insertion sort.
Kode: Insertion Sort pada Sub-array STL #
#include <iostream>
#include <vector>
#include <algorithm>
void insertion_sort_range(std::vector<int>& v, int lo, int hi) {
for (int i = lo + 1; i <= hi; ++i) {
int key = v[i];
int j = i - 1;
while (j >= lo && v[j] > key) {
v[j + 1] = v[j];
--j;
}
v[j + 1] = key;
}
}
int main() {
std::vector<int> v = {9, 3, 7, 1, 8, 2, 6, 5, 4, 0};
int n = static_cast<int>(v.size());
// Sort 6 elemen pertama saja dengan insertion sort
insertion_sort_range(v, 0, 5);
std::cout << "Sort v[0..5] dengan insertion sort:\n";
for (int x : v) std::cout << ' ' << x;
std::cout << '\n';
// Sekarang sort sisanya dengan std::sort
std::sort(v.begin() + 6, v.end());
std::cout << "Sort v[6..9] dengan std::sort:\n";
for (int x : v) std::cout << ' ' << x;
std::cout << '\n';
return 0;
}Output yang Dihasilkan:
Sort v[0..5] dengan insertion sort:
1 2 3 7 8 9 6 5 4 0
Sort v[6..9] dengan std::sort:
1 2 3 7 8 9 0 4 5 6insertion_sort_range(v, lo, hi): varian yang menerima batas[lo, hi]— berguna sebagai sub-rutin hybrid sort.j >= lo: batas bawah indeks — mencegah akses di luar rentang.- Output pertama: sub-array
v[0..5]terurut menjadi1 2 3 7 8 9, sedangkanv[6..9]tetap6 5 4 0. std::sort(v.begin() + 6, v.end()): STL dapat mengurutkan sub-array dengan iterator range.
Binary Insertion Sort #
Pembandingan linear dapat diganti dengan binary search untuk menemukan posisi penyisipan dalam O(log i). Sayangnya, pergeseran elemen tetap O(i) sehingga total tetap O(n²). Binary insertion sort berguna bila biaya pembandingan tinggi (misalnya string panjang) tetapi tidak mengubah asimptotik.
int find_pos(const std::vector<int>& v, int lo, int hi, int key) {
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (v[mid] <= key) lo = mid + 1;
else hi = mid;
}
return lo;
}v[mid] <= key: gunakan<=(bukan<) agar tetap stabil — posisi penyisipan adalah setelah elemen equal terakhir.- Kompleksitas binary search:
O(log i)per iterasi vsO(i)linear. - Total kompleksitas: pembandingan
O(n log n)tetapi pergeseran tetapO(n²).
Kapan Insertion Sort Layak Dipakai #
nkecil (< 16–32) — konstanta kecil, sering lebih cepat daripada quick sort.- Data hampir terurut — best case
O(n). - Sebagai sub-rutin hybrid sort (introsort, timsort).
- Online sorting — elemen datang satu per satu dan harus segera disisipkan.
Ringkasan #
- Insertion sort menyisipkan elemen ke posisi yang benar dengan menggeser elemen yang lebih besar ke kanan.
- Best case
O(n)(data hampir terurut), average dan worstO(n²). - Stabil dan in-place (
O(1)memori tambahan). - Menjadi sub-rutin pada
std::sort(introsort) untuk sub-array kecil — bukti relevansi algoritmaO(n²)di dunia modern. - Variasi binary insertion sort mempercepat pembandingan menjadi
O(log n)per iterasi tetapi pergeseran tetapO(n).
Pada chapter berikutnya kita akan naik kelas ke algoritma divide and conquer: merge sort dengan kompleksitas O(n log n) yang dijamin pada semua kasus.