Bubble Sort
procedure bubbleSort(array):
n = length(array)
for i = 0 to n - 1:
swapped = False
for j = 0 to n - i - 2:
if array[j] > array[j + 1] then
// Swap elements
temp = array[j]
array[j] = array[j + 1]
array[j + 1] = temp
swapped = True
end if
end for
// Optimisation: stop if no swaps
if not swapped then
break
end if
end for
end procedure
Pass 1:
• Compare 4, 9: No swap → [4, 9, 1, 6, 7]
• Compare 9, 1: Swap → [4, 1, 9, 6, 7]
• Compare 9, 6: Swap → [4, 1, 6, 9, 7]
• Compare 9, 7: Swap → [4, 1, 6, 7, 9]
• 9 is now in final position
Pass 2:
• Compare 4, 1: Swap → [1, 4, 6, 7, 9]
• Compare 4, 6: No swap
• Compare 6, 7: No swap
• 7 is now in final position. A swap was made, so another pass is needed
Pass 3:
• Compare 1, 4: No swap
• Compare 4, 6: No swap
• No swaps were made in this pass, so the array is sorted and the algorithm stops
The array was already in order after pass 2, but the algorithm cannot know that until it completes a pass without swapping.
• Best Case: O(n) - array already sorted (with optimisation)
• Worst Case: O(n²) - array in reverse order
• Average Case: O(n²)
Why inefficient for large datasets:
• Quadratic growth makes it very slow for large n
• Many unnecessary comparisons even when nearly sorted
When useful:
• Small datasets
• Nearly sorted data (with optimisation)
• Educational purposes (simple to understand)
Insertion Sort
procedure insertionSort(array):
n = length(array)
for i = 1 to n - 1:
key = array[i]
j = i - 1
// Move elements greater than key one position ahead
while j >= 0 AND array[j] > key:
array[j + 1] = array[j]
j = j - 1
end while
array[j + 1] = key
end for
end procedure
i = 1 (insert 9):
• Sorted: [4], Unsorted: [9, 1, 6, 7]
• 9 > 4, insert after → [4, 9, 1, 6, 7]
i = 2 (insert 1):
• Sorted: [4, 9], Unsorted: [1, 6, 7]
• 1 < 9, shift 9 right
• 1 < 4, shift 4 right
• Insert 1 at start → [1, 4, 9, 6, 7]
i = 3 (insert 6):
• Sorted: [1, 4, 9], Unsorted: [6, 7]
• 6 < 9, shift 9 right
• 6 > 4, insert after 4 → [1, 4, 6, 9, 7]
i = 4 (insert 7):
• Insert after 6 → [1, 4, 6, 7, 9] ✓
• Best Case: O(n) - array already sorted
• Worst Case: O(n²) - array in reverse order
• Average Case: O(n²)
Advantage over Bubble Sort:
• Better for nearly sorted data
• Fewer swaps required
• Simple implementation
• Efficient for small datasets
Merge Sort
function mergeSort(array):
if length(array) <= 1 then
return array
end if
// DIVIDE
mid = length(array) DIV 2
left = mergeSort(array[0..mid-1])
right = mergeSort(array[mid..end])
// CONQUER (merge)
return merge(left, right)
end function
function merge(left, right):
result = []
while left is not empty AND right is not empty:
if left[0] <= right[0] then
append left[0] to result
remove left[0] from left
else
append right[0] to result
remove right[0] from right
end if
end while
// Add remaining elements
append remaining left to result
append remaining right to result
return result
end function
| Phase | Working |
|---|---|
| Divide | [7,4,2,6] → [7,4] + [2,6] → [7]+[4] and [2]+[6] |
| Merge sublists | [7]+[4] → [4,7]; [2]+[6] → [2,6] |
| Final merge | [4,7] + [2,6] → [2,4,6,7] |
• Best Case: O(n log n)
• Worst Case: O(n log n)
• Average Case: O(n log n)
• Space: O(n) - requires additional memory
Why preferred for large datasets:
• Guaranteed O(n log n) performance
• Stable sort (maintains relative order)
• Predictable regardless of input
Trade-off: Requires extra memory for merging
Quick Sort
function quickSort(array, low, high):
if low < high then
// Partition and get pivot position
pivotIndex = partition(array, low, high)
// Recursively sort sub-arrays
quickSort(array, low, pivotIndex - 1)
quickSort(array, pivotIndex + 1, high)
end if
end function
function partition(array, low, high):
pivot = array[high] // Choose last element as pivot
i = low - 1
for j = low to high - 1:
if array[j] <= pivot then
i = i + 1
swap array[i] and array[j]
end if
end for
swap array[i + 1] and array[high]
return i + 1
end function
The pivot is the last item of the part being sorted. After each partition the pivot (in bold) is in its final position, with smaller items to its left and larger items to its right.
| Step | Part being sorted | Pivot | Whole array after the partition |
|---|---|---|---|
| 1 | [9, 4, 7, 8, 6, 3, 1, 5, 2] | 2 | [1, 2, 7, 8, 6, 3, 9, 5, 4] |
| 2 | [7, 8, 6, 3, 9, 5, 4] | 4 | [1, 2, 3, 4, 6, 7, 9, 5, 8] |
| 3 | [6, 7, 9, 5, 8] | 8 | [1, 2, 3, 4, 6, 7, 5, 8, 9] |
| 4 | [6, 7, 5] | 5 | [1, 2, 3, 4, 5, 7, 6, 8, 9] |
| 5 | [7, 6] | 6 | [1, 2, 3, 4, 5, 6, 7, 8, 9] |
A part with one item, such as [1], [3], [7] or [9], is already sorted, so the recursion stops there.
Final sorted array: [1, 2, 3, 4, 5, 6, 7, 8, 9]
• Best Case: O(n log n) - balanced partitions
• Worst Case: O(n²) - unbalanced partitions (bad pivot choice)
• Average Case: O(n log n)
• Space: O(log n) - in-place sorting
Pivot choice matters:
• First/last element: Risk of O(n²) for sorted data
• Random element: Usually good
• Median-of-three: Better balance