Searching and sorting: linear, binary, selection, insertion, merge sort
Two search and three sorting algorithms, the state of the array after each pass, and a Big-O comparison. The exam asks 'what does the array look like after pass 2'.
Searching and sorting are the first stop of algorithm analysis. This week you trace two search and three sorting algorithms step by step. Typical exam questions: “what does the array look like after the second pass when selection sort is applied to this array”, “which indexes does binary search look at”, and “what is its Big-O, and why”. Instead of memorizing the code, know the one-sentence idea of each algorithm; the code follows from that idea.
Golden rules:
- Binary search works only on a sorted array. It halves the range at each step.
- Selection sort: find the smallest of the rest and put it at the front.
- Insertion sort: shift the next element into its place in the sorted part on the left.
- Merge sort: split in two, sort the two halves, merge.
- Big-O: linear , binary , selection and insertion , merge .
Searching
Linear search
Walk through the array one by one from the start, and return the index when you see the searched value. The array does not have to be sorted.
static int linearSearch(int[] a, int key) {
for (int i = 0; i < a.length; i++)
if (a[i] == key) return i;
return -1;
}
int[] data = {42, 7, 19, 3, 25, 7};
Three searches, also counting the comparisons:
1 comps=2
4 comps=5
-1 comps=6
(When 7 is searched, the first 7 is found, index 1; 5 comparisons for 25; 99 is not there, the whole array was looked at.)
Binary search
If the array is sorted, look at the middle element. If the searched value is larger, you can eliminate the left half entirely; if smaller, the right half. Last week we wrote it recursively; the loop version:
static int binarySearch(int[] a, int key) {
int low = 0, high = a.length - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (a[mid] == key) return mid;
if (a[mid] < key) low = mid + 1; // right half
else high = mid - 1; // left half
}
return -1;
}
int[] sorted = {2, 5, 9, 14, 20, 27, 33, 41, 48, 56, 63};
binarySearch(sorted, 41);
binarySearch(sorted, 4);
low=0 high=10 mid=5 a[mid]=27
low=6 high=10 mid=8 a[mid]=48
low=6 high=7 mid=6 a[mid]=33
low=7 high=7 mid=7 a[mid]=41
index 7 comps=4
low=0 high=10 mid=5 a[mid]=27
low=0 high=4 mid=2 a[mid]=9
low=0 high=1 mid=0 a[mid]=2
low=1 high=1 mid=1 a[mid]=5
index -1 comps=4
When 4 is searched, at the last step low = 1, high = 1, and since a[1] = 5 > 4, high = 0; low > high and the loop ends: −1.
PITFALL ·
low < high. If you write the condition with<, when the range shrinks to a single element that element is never checked. On the array{2, 5, 9}this faulty version found 5 but returned −1 for 9 and 2.
Java’s built-in version: Arrays.binarySearch(sorted, 27) → 5. It also expects the array to be sorted; if it is not sorted, the result is meaningless.
Sorting
We will trace the three algorithms on the same array: {29, 10, 14, 37, 13}.
Selection sort
The idea: find the smallest element in the unsorted part of the array and swap it with the start of that part. In each pass, one more element from the front settles into its final place.
static void selectionSort(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
int min = i;
for (int j = i + 1; j < a.length; j++)
if (a[j] < a[min]) min = j;
int tmp = a[i]; a[i] = a[min]; a[min] = tmp; // swap
}
}
The array after each pass:
pass 1: [10, 29, 14, 37, 13]
pass 2: [10, 13, 14, 37, 29]
pass 3: [10, 13, 14, 37, 29]
pass 4: [10, 13, 14, 29, 37]
comps=10
pass 1: [29 10 14 37 13] smallest 10 (index 1) ↔ index 0 → [10 | 29 14 37 13]
pass 2: [10 | 29 14 37 13] smallest 13 (index 4) ↔ index 1 → [10 13 | 14 37 29]
pass 3: smallest 14 is already at index 2 → [10 13 14 | 37 29]
pass 4: smallest 29 (index 4) ↔ index 3 → [10 13 14 29 | 37]
The number of comparisons is the same for every input: . For n = 5 it is 10. That is why selection sort is in every case.
Insertion sort
The idea: like sorting cards in your hand. The part on the left is always sorted. Take the next element (key), shift the ones larger than it one place to the right, and put the key in the gap that opens.
static void insertionSort(int[] a) {
for (int i = 1; i < a.length; i++) {
int key = a[i];
int j = i - 1;
while (j >= 0 && a[j] > key) {
a[j + 1] = a[j]; // shift right
j--;
}
a[j + 1] = key;
}
}
pass 1: [10, 29, 14, 37, 13]
pass 2: [10, 14, 29, 37, 13]
pass 3: [10, 14, 29, 37, 13]
pass 4: [10, 13, 14, 29, 37]
comps=8
Compare the passes of the two algorithms: in selection sort the elements at the front are final (after pass 2, 10 and 13 never change again). In insertion sort the front part is sorted but not final: in pass 3, [10, 14, 29, 37] is sorted but 13 slipped in between later.
Insertion sort’s comparison count depends on the input:
| Input | Insertion | Selection |
|---|---|---|
{1, 2, 3, 4, 5} (sorted) | 4 | 10 |
{5, 4, 3, 2, 1} (reverse) | 10 | 10 |
{29, 10, 14, 37, 13} | 8 | 10 |
On sorted input, insertion sort makes one comparison per element and stops: the best case is . On reverse-ordered input each element slides all the way to the front: the worst case is .
Merge sort
The idea: divide and conquer. Split the array in two, sort the two halves recursively, then merge the two sorted halves into a single sorted array (merge). An array with one element is already sorted: the base case.
static void mergeSort(int[] a, int low, int high) {
if (low >= high) return; // 0 or 1 elements
int mid = (low + high) / 2;
mergeSort(a, low, mid); // left half
mergeSort(a, mid + 1, high); // right half
merge(a, low, mid, high); // merge
}
static void merge(int[] a, int low, int mid, int high) {
int[] tmp = new int[high - low + 1];
int i = low, j = mid + 1, k = 0;
while (i <= mid && j <= high)
tmp[k++] = (a[i] <= a[j]) ? a[i++] : a[j++]; // take the smaller one
while (i <= mid) tmp[k++] = a[i++]; // the leftovers
while (j <= high) tmp[k++] = a[j++];
System.arraycopy(tmp, 0, a, low, tmp.length);
}
For {38, 27, 43, 3, 9, 82, 10}, the state of that range after each merge:
merge [0..1]: [27, 38]
merge [2..3]: [3, 43]
merge [0..3]: [3, 27, 38, 43]
merge [4..5]: [9, 82]
merge [4..6]: [9, 10, 82]
merge [0..6]: [3, 9, 10, 27, 38, 43, 82]
[3, 9, 10, 27, 38, 43, 82] comps=14
[38 27 43 3 9 82 10]
[38 27 43 3] [9 82 10]
[38 27] [43 3] [9 82] [10]
[38] [27] [43] [3] [9] [82]
└─┬─┘ └─┬─┘ └─┬─┘
[27 38] [3 43] [9 82]
└────┬────┘ └──┬──┘
[3 27 38 43] [9 10 82]
└──────────┬──────────┘
[3 9 10 27 38 43 82]
The splitting takes levels, and at each level a total of n elements are merged: , independent of the input. The cost: extra memory for the temporary array.
Big-O comparison
| Algorithm | Best | Average | Worst | Extra memory | Note |
|---|---|---|---|---|---|
| Linear search | none | Works on an unsorted array too | |||
| Binary search | none | Sorted array required | |||
| Selection sort | none | At most n − 1 swaps | |||
| Insertion sort | none | Fast on nearly sorted data, stable | |||
| Merge sort | Preferred for big data |
The practical counterpart of the theory: comparisons counted on reverse-ordered arrays.
| n | Selection sort | Merge sort |
|---|---|---|
| 1000 | 499500 | 4932 |
| 2000 | 1999000 | 10864 |
| 4000 | 7998000 | 23728 |
When n doubles, selection sort’s work quadruples (), while merge sort’s grows a little more than double ().
TIP · in real life. You do not write your own sort:
Arrays.sort(array)andCollections.sort(list)are ready and . In the exam, however, you are asked to trace and write the algorithm by hand; the built-in method is not accepted.
Common mistakes
| Wrong idea | The truth |
|---|---|
| Binary search works on every array | A sorted array is required |
while (low < high) is enough | It must be <=, otherwise the last remaining element is skipped |
| Selection sort is fast on a sorted array | The comparison count is always n(n−1)/2 |
| The passes of insertion sort and selection sort are the same | Selection finalizes from the front; insertion keeps the front part sorted but elements can slip in |
| Merge sort merges the right half first | The left half finishes completely, then the right |
| Merge sort needs no extra memory | A temporary array: O(n) |
| The difference between O(n log n) and O(n²) is small | At n = 4000, 23728 versus 7998000 |
| n passes are needed | n − 1 passes are enough, the last element is in place by itself |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
Show the array after each pass of selection sort (smallest to the front).
int[] a = {29, 10, 14, 37, 13};Show answer
In each pass the smallest of the unsorted part is found and swapped with the start of that part. Pass 1: the smallest, 10, swaps with 29. Pass 2: among the remaining [29,14,37,13] the smallest, 13, swaps with 29. Pass 3: the remaining is [14,37,29], and the smallest, 14, is already in place (it swaps with itself, the array is the same). Pass 4: [37,29] → 29 to the front. For n = 5 there are 4 passes.
Show the array after each pass of insertion sort.
int[] a = {29, 10, 14, 37, 13};Show answer
In each pass the next element is shifted into its correct place in the sorted part on the left. Pass 1: 10 goes in front of 29. Pass 2: 14 goes in front of 29 (larger than 10). Pass 3: 37 is already the largest, nothing changes. Pass 4: 13 shifts three elements to the right and goes in after 10.
Binary search for 41 in {2, 5, 9, 14, 20, 27, 33, 41, 48, 56, 63} with mid = (low + high) / 2. List the mid values checked.
Show answer
(0,10) → mid 5, a[5] = 27 < 41, low = 6. (6,10) → mid 8, a[8] = 48 > 41, high = 7. (6,7) → mid 6, a[6] = 33 < 41, low = 7. (7,7) → mid 7, a[7] = 41: found. 4 comparisons; for an 11-element array at most 4 are needed.
How many comparisons does linear search make to find 25 in {42, 7, 19, 3, 25, 7}, and to search for 99?
Show answer
Linear search looks one by one from the start: 42, 7, 19, 3, 25 → it finds it at the 5th comparison (index 4). For a value that is not there it looks at the whole array: 6 comparisons, returns −1. If 7 were searched, it would find the first 7 (index 1) and stop: 2 comparisons.
What is the Big-O (worst case) of each algorithm? linear search, binary search, selection sort, insertion sort, merge sort
Show answer
Linear may look at every element: . Binary halves the range at each step: . Selection and insertion have two nested loops: . Merge sort divides into levels, and at each level n elements are merged: .
Insertion sort makes 4 comparisons on {1, 2, 3, 4, 5} but 10 on {5, 4, 3, 2, 1}. Selection sort makes 10 on both. Why?
Show answer
On sorted input, insertion sort makes a single comparison per element and stops: n − 1 = 4. On reverse-ordered input, each element slides all the way to the front: 1 + 2 + 3 + 4 = 10. To find the smallest, selection sort always looks at the whole remaining part: n(n − 1)/2 = 10, regardless of the input.
Merge sort sorts {38, 27, 43, 3, 9, 82, 10} with mid = (low + high) / 2, left half first. What is the result of the third merge?
Show answer
The order of merges: [0..1] → [27, 38], [2..3] → [3, 43], then these two [0..3] → [3, 27, 38, 43]. It does not move to the right half until the left half is completely done. Then [4..5] → [9, 82], [4..6] → [9, 10, 82], and last [0..6] → [3, 9, 10, 27, 38, 43, 82].
Find the bug: the search below misses some elements of {2, 5, 9}.
static int search(int[] a, int key) {
int low = 0, high = a.length - 1;
while (low < high) {
int mid = (low + high) / 2;
if (a[mid] == key) return mid;
if (a[mid] < key) low = mid + 1;
else high = mid - 1;
}
return -1;
}Show answer
When the range shrinks to a single element (low == high), the loop ends without checking that element. In the test run 5 was found (1) but −1 was returned for 9 and 2. The correct form is while (low <= high).
Which statement is TRUE?
- Binary search works on any array
- Selection sort does fewer comparisons on an already sorted array
- Merge sort needs an extra array proportional to n
- Insertion sort is O(n log n) in the worst case
Show answer
The merge step merges the two halves into a temporary array: extra memory . Binary search works only on a sorted array. Selection sort's comparison count is independent of the input. Insertion sort's worst case is .
For n = 1000, 2000 and 4000 (reverse-ordered input), selection sort made 499500, 1999000 and 7998000 comparisons; merge sort made 4932, 10864 and 23728. What happens to each when n doubles?
Show answer
: when n doubles, , four times (499500 → 1999000 → 7998000). : , a little more than double (4932 → 10864 → 23728). As n grows, the gap becomes a chasm; that is why merge sort (and Arrays.sort) is used for big data.
How many comparisons does binary search need at most for a sorted array of 1,000,000 elements?
Show answer
Each comparison halves the range. , so at most 20 steps. Linear search on the same array makes 1,000,000 comparisons in the worst case.
Write a method sortByLength(String[] words) that sorts the array by word length (shortest first) using insertion sort. Words with equal length must keep their original order. {"kiwi", "fig", "banana", "apple", "date"} becomes [fig, kiwi, date, apple, banana].
Show answer
Insertion sort is stable: it does not shift equal elements, so their order is preserved. For this the comparison must be >, not >=:
public static void sortByLength(String[] words) {
for (int i = 1; i < words.length; i++) {
String key = words[i];
int j = i - 1;
while (j >= 0 && words[j].length() > key.length()) {
words[j + 1] = words[j]; // shift right
j--;
}
words[j + 1] = key;
}
}kiwi and date both have 4 letters; kiwi came first, so it is first in the result too: [fig, kiwi, date, apple, banana].Verification: All traces, comparison counts and outputs were run with JDK 26 (java verify/SEN1002/W13.java, W13b.java). The growth table is the comparisons counted in the same file for n = 1000, 2000, 4000.