Comparison Sorts Overview
| Algorithm | Best | Average | Worst | Space | Stable |
|---|---|---|---|---|---|
| Bubble Sort | O(n) | O(n²) | O(n²) | O(1) | Yes |
| Selection Sort | O(n²) | O(n²) | O(n²) | O(1) | No |
| Insertion Sort | O(n) | O(n²) | O(n²) | O(1) | Yes |
| Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes |
| Quick Sort | O(n log n) | O(n log n) | O(n²) | O(log n) | No |
| Heap Sort | O(n log n) | O(n log n) | O(n log n) | O(1) | No |
| Tim Sort | O(n) | O(n log n) | O(n log n) | O(n) | Yes |
Bubble Sort
def bubble_sort(arr):
n = len(arr)
for i in range(n):
swapped = False
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped: # early exit if already sorted
break
return arr
print(bubble_sort([64, 34, 25, 12, 22, 11, 90]))
# [11, 12, 22, 25, 34, 64, 90]
Insertion Sort
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
# Excellent for small arrays or nearly-sorted data
print(insertion_sort([5, 2, 4, 6, 1, 3]))
# [1, 2, 3, 4, 5, 6]
Merge Sort
def merge_sort(arr):
"""Divide and conquer — O(n log n) always, O(n) space"""
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
print(merge_sort([38, 27, 43, 3, 9, 82, 10]))
# [3, 9, 10, 27, 38, 43, 82]
In-place Merge Sort (Bottom-Up)
def merge_sort_bottom_up(arr):
n = len(arr)
size = 1
while size < n:
for start in range(0, n, size * 2):
mid = min(start + size, n)
end = min(start + size * 2, n)
merged = merge(arr[start:mid], arr[mid:end])
arr[start:end] = merged
size *= 2
return arr
Quick Sort
def quick_sort(arr, low=0, high=None):
"""Divide and conquer — O(n log n) average, O(n²) worst"""
if high is None:
high = len(arr) - 1
if low < high:
pivot_idx = partition(arr, low, high)
quick_sort(arr, low, pivot_idx - 1)
quick_sort(arr, pivot_idx + 1, high)
return arr
def partition(arr, low, high):
"""Lomuto partition scheme"""
pivot = arr[high]
i = low - 1
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
# Better: 3-way partition (handles duplicates well)
def quick_sort_3way(arr, lo=0, hi=None):
if hi is None:
hi = len(arr) - 1
if lo >= hi:
return arr
lt, gt = lo, hi
pivot = arr[lo]
i = lo + 1
while i <= gt:
if arr[i] < pivot:
arr[lt], arr[i] = arr[i], arr[lt]
lt += 1; i += 1
elif arr[i] > pivot:
arr[i], arr[gt] = arr[gt], arr[i]
gt -= 1
else:
i += 1
quick_sort_3way(arr, lo, lt - 1)
quick_sort_3way(arr, gt + 1, hi)
return arr
Randomized Quick Sort
import random
def randomized_quick_sort(arr, low=0, high=None):
if high is None:
high = len(arr) - 1
if low < high:
# Random pivot avoids O(n²) worst case
rand_idx = random.randint(low, high)
arr[rand_idx], arr[high] = arr[high], arr[rand_idx]
pivot_idx = partition(arr, low, high)
randomized_quick_sort(arr, low, pivot_idx - 1)
randomized_quick_sort(arr, pivot_idx + 1, high)
return arr
Heap Sort
def heap_sort(arr):
"""O(n log n) always, O(1) space"""
n = len(arr)
# Build max-heap
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
# Extract elements from heap one by one
for i in range(n - 1, 0, -1):
arr[0], arr[i] = arr[i], arr[0] # move max to end
heapify(arr, i, 0) # restore heap on remaining
return arr
def heapify(arr, n, i):
largest = i
left = 2 * i + 1
right = 2 * i + 2
if left < n and arr[left] > arr[largest]:
largest = left
if right < n and arr[right] > arr[largest]:
largest = right
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
print(heap_sort([12, 11, 13, 5, 6, 7]))
# [5, 6, 7, 11, 12, 13]
Non-Comparison Sorts
Counting Sort — O(n + k)
def counting_sort(arr, max_val=None):
"""Works for non-negative integers — O(n + k) where k = range"""
if not arr:
return arr
if max_val is None:
max_val = max(arr)
count = [0] * (max_val + 1)
for num in arr:
count[num] += 1
result = []
for num, freq in enumerate(count):
result.extend([num] * freq)
return result
print(counting_sort([4, 2, 2, 8, 3, 3, 1]))
# [1, 2, 2, 3, 3, 4, 8]
Radix Sort — O(d(n + k))
def radix_sort(arr):
"""Sort integers digit by digit — O(d*(n+10)) where d=digits"""
max_val = max(arr)
exp = 1
while max_val // exp > 0:
arr = counting_sort_by_digit(arr, exp)
exp *= 10
return arr
def counting_sort_by_digit(arr, exp):
n = len(arr)
output = [0] * n
count = [0] * 10
for num in arr:
idx = (num // exp) % 10
count[idx] += 1
for i in range(1, 10):
count[i] += count[i - 1]
for i in range(n - 1, -1, -1):
idx = (arr[i] // exp) % 10
output[count[idx] - 1] = arr[i]
count[idx] -= 1
return output
print(radix_sort([170, 45, 75, 90, 802, 24, 2, 66]))
# [2, 24, 45, 66, 75, 90, 170, 802]
Exercises
Exercise 1: Sort Colors (Dutch National Flag)
Sort array containing only 0, 1, 2 in-place in O(n) without counting.
Solution:
def sort_colors(nums):
"""3-way partition (Dutch National Flag)"""
low, mid, high = 0, 0, len(nums) - 1
while mid <= high:
if nums[mid] == 0:
nums[low], nums[mid] = nums[mid], nums[low]
low += 1; mid += 1
elif nums[mid] == 1:
mid += 1
else:
nums[mid], nums[high] = nums[high], nums[mid]
high -= 1
nums = [2, 0, 2, 1, 1, 0]
sort_colors(nums)
print(nums) # [0, 0, 1, 1, 2, 2]
Exercise 2: Kth Largest Element
Find Kth largest element in O(n) average using QuickSelect.
Solution:
import random
def find_kth_largest(nums, k):
"""QuickSelect — O(n) average, O(n²) worst"""
target = len(nums) - k # kth largest = target-th smallest
def quickselect(lo, hi):
pivot = nums[hi]
p = lo
for i in range(lo, hi):
if nums[i] <= pivot:
nums[p], nums[i] = nums[i], nums[p]
p += 1
nums[p], nums[hi] = nums[hi], nums[p]
if p == target:
return nums[p]
elif p < target:
return quickselect(p + 1, hi)
else:
return quickselect(lo, p - 1)
return quickselect(0, len(nums) - 1)
print(find_kth_largest([3, 2, 1, 5, 6, 4], 2)) # 5
print(find_kth_largest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4)) # 4