1. Bubble Sort
Performance:
- Best: ( O(n) )
- Average: ( O(n^2) )
- Worst: ( O(n^2) )
Memory: ( O(1) )
Pros:
- Simple to implement
Cons:
- Inefficient for large datasets
( n ) Derivation:
- ( n ) represents the number of elements to be sorted. The algorithm compares each pair of adjacent elements, making it quadratic in the worst case.
// JavaScript implementation of Bubble Sort
function bubbleSort(arr: number[]): number[] {
const n = arr.length;
let swapped = false;
for (let i = 0; i < n - 1; i++) {
swapped = false;
for (let j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
swapped = true;
}
}
if (!swapped) break;
}
return arr;
}
bubbleSort([5, 3, 8, 4, 2]); // Output: [2, 3, 4, 5, 8]2. Selection Sort
Performance:
- Best: ( O(n^2) )
- Average: ( O(n^2) )
- Worst: ( O(n^2) )
Memory: ( O(1) )
Pros:
- Simple and predictable performance
Cons:
- Inefficient for large datasets
( n ) Derivation:
- It iteratively finds the minimum element from the unsorted section and puts it at the beginning, making the time complexity quadratic.
// JavaScript implementation of Selection Sort
function selectionSort(arr: number[]): number[] {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
let minIndex = i;
for (let j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) minIndex = j;
}
if (minIndex !== i) [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
return arr;
}
selectionSort([5, 3, 8, 4, 2]); // Output: [2, 3, 4, 5, 8]3. Insertion Sort
Performance:
- Best: ( O(n) )
- Average: ( O(n^2) )
- Worst: ( O(n^2) )
Memory: ( O(1) )
Pros:
- Efficient for nearly sorted lists
Cons:
- Inefficient for large or reverse-sorted datasets
( n ) Derivation:
- ( n ) is the number of elements. It builds a sorted array one element at a time, making it quadratic in the worst case.
// JavaScript implementation of Insertion Sort
function insertionSort(arr: number[]): number[] {
const n = arr.length;
for (let i = 1; i < n; i++) {
let key = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
}
insertionSort([5, 3, 8, 4, 2]); // Output: [2, 3, 4, 5, 8]4. Merge Sort
Performance:
- Best: ( O(n \log n) )
- Average: ( O(n \log n) )
- Worst: ( O(n \log n) )
Memory: ( O(n) )
Pros:
- Stable and has consistent performance
Cons:
- Requires additional memory space
( n ) Derivation:
- The array is recursively divided in half, and each half is sorted. Since the algorithm divides and then merges, it’s ( O(n \log n) ).
// JavaScript implementation of Merge Sort
function mergeSort(arr: number[]): number[] {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
function merge(left: number[], right: number[]): number[] {
const sorted = [];
let i = 0;
let j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) sorted.push(left[i++]);
else sorted.push(right[j++]);
}
return sorted.concat(left.slice(i)).concat(right.slice(j));
}
}
mergeSort([5, 3, 8, 4, 2]); // Output: [2, 3, 4, 5, 8]5. Quick Sort
Performance:
- Best: ( O(n \log n) )
- Average: ( O(n \log n) )
- Worst: ( O(n^2) )
Memory: ( O(\log n) ) (average)
Pros:
- Fast and doesn’t require additional space
Cons:
- Performance depends on pivot selection; worst case is rare but possible
( n ) Derivation:
- It partitions the array and then recursively sorts the partitions. The choice of pivot affects whether the partitioning is balanced.
// JavaScript implementation of Quick Sort
function quickSort(arr: number[]): number[] {
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = [];
const right = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] < pivot) left.push(arr[i]);
else if (arr[i] > pivot) right.push(arr[i]);
}
return quickSort(left).concat(pivot, quickSort(right));
}
quickSort([5, 3, 8, 4, 2]); // Output: [2, 3, 4, 5, 8]6. Heap Sort
Performance:
- Best: ( O(n \log n) )
- Average: ( O(n \log n) )
- Worst: ( O(n \log n) )
Memory: ( O(1) )
Pros:
- Consistent performance and in-place
Cons:
- Somewhat slower compared to well-implemented Quick Sort
( n ) Derivation:
- It uses a binary heap data structure, and the time complexity arises from repeatedly removing the maximum element and then maintaining the heap property.
// JavaScript implementation of Heap Sort
function heapSort(arr: number[]): number[] {
const n = arr.length;
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) heapify(arr, n, i);
for (let i = n - 1; i > 0; i--) {
[arr[0], arr[i]] = [arr[i], arr[0]];
heapify(arr, i, 0);
}
return arr;
function heapify(arr: number[], n: number, i: number): void {
let largest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
if (left < n && arr[left] > arr[largest]) largest = left;
if (right < n && arr[right] > arr[largest]) largest = right;
if (largest !== i) {
[arr[i], arr[largest]] = [arr[largest], arr[i]];
heapify(arr, n, largest);
}
}
}
heapSort([5, 3, 8, 4, 2]); // Output: [2, 3, 4, 5, 8]7. Counting Sort
Performance:
- Best: ( O(n + k) )
- Average: ( O(n + k) )
- Worst: ( O(n + k) )
Memory: ( O(k) )
Pros:
- Efficient for small ranges of integer keys
Cons:
- Not suitable for sorting elements with non-integer keys or large ranges
( n ) Derivation:
- ( n ) is the number of elements, and ( k ) is the range of the input. It counts the occurrence of each element.
Each algorithm’s performance is affected by the number of elements ( n ) being sorted and other factors like the range of values ( k ) or the choice of pivot.
// JavaScript implementation of Counting Sort
function countingSort(arr: number[]): number[] {
const n = arr.length;
const max = Math.max(...arr);
const count = Array(max + 1).fill(0);
const output = Array(n);
for (let i = 0; i < n; i++) count[arr[i]]++;
for (let i = 1; i <= max; i++) count[i] += count[i - 1];
for (let i = n - 1; i >= 0; i--) {
output[count[arr[i]] - 1] = arr[i];
count[arr[i]]--;
}
return output;
}
countingSort([5, 3, 8, 4, 2]); // Output: [2, 3, 4, 5, 8]8. Radix Sort
Performance:
- Best: ( O(nk) )
- Average: ( O(nk) )
- Worst: ( O(nk) )
Memory: ( O(n + k) )
Pros:
- Efficient for sorting integers with a fixed number of digits
Cons:
- Not suitable for floating-point numbers or negative integers
( n ) Derivation:
- ( n ) is the number of elements, and ( k ) is the number of digits in the largest element. It sorts the elements by their individual digits.
// JavaScript implementation of Radix Sort
function radixSort(arr: number[]): number[] {
const max = Math.max(...arr);
const maxDigits = max.toString().length;
for (let i = 0; i < maxDigits; i++) {
const buckets = Array.from({ length: 10 }, () => []);
for (let j = 0; j < arr.length; j++) {
const digit = Math.floor(arr[j] / 10 ** i) % 10;
buckets[digit].push(arr[j]);
}
arr = buckets.flat();
}
return arr;
}
radixSort([5, 3, 8, 4, 2]); // Output: [2, 3, 4, 5, 8]Genrify Sorts
function bubbleSort<T>(arr: T[], compareFn: (a: T, b: T) => number): T[] {
const len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = 0; j < len - i - 1; j++) {
if (compareFn(arr[j], arr[j + 1]) > 0) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
function selectionSort<T>(arr: T[], compareFn: (a: T, b: T) => number): T[] {
const len = arr.length;
for (let i = 0; i < len; i++) {
let minIdx = i;
for (let j = i + 1; j < len; j++) {
if (compareFn(arr[j], arr[minIdx]) < 0) {
minIdx = j;
}
}
[arr[i], arr[minIdx]] = [arr[minIdx], arr[i]];
}
return arr;
}
function insertionSort<T>(arr: T[], compareFn: (a: T, b: T) => number): T[] {
const len = arr.length;
for (let i = 1; i < len; i++) {
let key = arr[i];
let j = i - 1;
while (j >= 0 && compareFn(arr[j], key) > 0) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
return arr;
}
Why always me?