Year 8 · Search and Sort · Part C: Telling it what to do

Searching and sorting

In one line

Searching finds an item. Sorting puts items in order. Some algorithms are hugely faster than others on big lists, and the difference grows as the list grows.

1

Carry out a linear search and a binary search by hand, and say when each one can be used.

2

Work out the most checks a binary search can need, using halving (log₂).

3

Run bubble sort, insertion sort and merge sort by hand, pass by pass.

4

Compare algorithms by counting comparisons, and explain n, n² and n log n in plain words.

Linear searchCheck every item in turn.
Binary searchSorted list only. Check the middle, throw half away.
Bubble sortSwap neighbours until a pass makes no swaps.
Insertion sortSlide each item into place in the sorted part.
Merge sortSplit into singles, then merge back in order.
ComparisonOne "is this bigger than that?" check. We count these to measure speed.

Why search and sort?

Computers search all day: your contacts, a shop's stock, a game's leaderboard, Google's index. Most of that data is sorted first, because sorted data is much faster to search.

Not sorted. Is 50 here? 8 checks. You can only say "no" after looking at every single item. Sorted. Is 50 here? 3 checks. 50 would sit between 42 and 51, and it isn't there. Done.
Same 8 numbers. Red circles = checks made on the messy list. Green circles = checks made on the sorted list, in order 1, 2, 3.
  1. An algorithm is a recipe. Exact, ordered steps that always finish. Searching and sorting are the classic examples.

  2. We measure speed by counting. Not seconds: computers differ. We count comparisons: each time the algorithm asks "is this the one?" or "is this bigger than that?".

  3. We care about big lists. On 8 items every method is instant. On 8 million, a bad choice can take days instead of a blink.

WhereSearch or sort?Why it matters
Phone contactsSorted A–Z, then searchedTyping "Ma" jumps straight to the M names.
Game leaderboardSorted by scoreTop 10 is just the first 10 items.
School register (SIMS)Sorted by surnameFinding one pupil among 1,200 is quick.
Search engineHuge sorted indexBillions of pages, answers in under a second.
★Remember

On these diagrams the colours always mean the same algorithm:

Linear searchBinary searchBubble sortInsertion sortMerge sort

Linear search: one at a time

Linear search starts at the first item and checks each one in turn. It stops when it finds the item, or when it runs out of items.

Found 7 at index 3 after 4 checks. The items after it are never looked at.
Each orange arrow is one check. "≠" means "not the one, move on".
  1. Start at the first item (index 0).

  2. Is it the target? Yes → stop and report where it is.

  3. No → move to the next item and repeat.

  4. If you pass the last item without finding it, report "not found".

CaseWhen it happensChecks for a list of n itemsFor n = 1,000
BestThe target is the very first item11
WorstThe target is last, or not there at alln1,000
AverageThe target is somewhere in the listabout n ÷ 2about 500
def linear_search(items, target):
    for i in range(len(items)):
        if items[i] == target:
            return i      # found: give back its index
    return -1             # checked them all: not there

scores = [12, 45, 33, 7, 50, 28]
print(linear_search(scores, 7))
3
?Why?

Why bother with linear search at all? It works on any list, sorted or not. It is easy to write. On a short list it is perfectly fast. If you only search a messy list once, sorting it first would cost more than just looking.

Binary search: halve it every time

Binary search needs a sorted list. Like a dictionary: open it in the middle, decide which half your word is in, throw the other half away. Repeat.

Searching for 66. Blue = the middle item being checked. Faded = thrown away. 16 → 8 → 4 → 2 → found: 4 checks.
  1. Look at the middle item. (Middle index = (low + high) ÷ 2, rounded down.)

  2. Is it the target? Stop, found.

  3. Is the target bigger? Throw away the middle and everything left of it. Smaller? Throw away the middle and everything right of it.

  4. Repeat with what's left. If nothing is left, the target is not in the list.

!Watch out

On an unsorted list binary search gives wrong answers. It might throw away the half that holds your item. Sorted first, always.

How many checks, at most?

Each check halves what's left. So the question is: how many times can you halve n before you get down to 1? That number has a name: log₂ n ("log base 2 of n").

By hand, for n = 1,000. Press Next line.

  1. Halve, rounding down each time: 1,000 → 500 → 250 → 125 → 62 → 31 → 15 → 7 → 3 → 1.

  2. Count the arrows: 9 halvings. So ⌊log₂ 1,000⌋ = 9.

  3. Check it the other way, by doubling: 2⁹ = 512 is not more than 1,000, but 2¹⁰ = 1,024 is. So 9 is the biggest power that fits.

  4. The last item left still needs one more check: 9 + 1 = 10 checks.

  5. Compare: linear search could need 1,000 checks on the same list. Binary search needs at most 10.

Doubling2¹2²2³2⁴2⁵2⁶2⁷2⁸2⁹2¹⁰
Items2481632641282565121,024
Worst checks234567891011
def binary_search(items, target):
    low = 0
    high = len(items) - 1
    while low <= high:
        mid = (low + high) // 2
        if items[mid] == target:
            return mid
        elif items[mid] < target:
            low = mid + 1     # throw away the left half
        else:
            high = mid - 1    # throw away the right half
    return -1                 # nothing left: not there
?Why?

Why so fast? Doubling the list only adds one check, because one check halves it straight back. 2¹⁰ = 1,024 and 2²⁰ ≈ 1 million, so a million items need only about 20 checks.

Linear vs binary: the race

Plot "most checks needed" against "items in the list". Linear search climbs in a straight line. Binary search barely leaves the floor.

Items (n)Linear (worst)Binary (worst)How many times faster
101042.5 ×
1,0001,00010100 ×
1,000,0001,000,0002050,000 ×
8 billion (everyone on Earth)8,000,000,00033about 240 million ×
Linear searchBinary search
Needs a sorted list?NoYes
Speed on big listsSlow: up to n checksVery fast: up to ⌊log₂ n⌋ + 1
Easy to code?VeryFairly: the low/high/mid logic is easy to get wrong by one
Works on a linked list or a stream?YesNo: it must jump straight to the middle
★Remember

Binary search is faster only if the list is already sorted. If you'd have to sort it first just to search once, linear search may win overall.

Bubble sort: swap the neighbours

Bubble sort walks along the list comparing neighbours. If a pair is in the wrong order, it swaps them. One trip along the list is called a pass.

Pass 1 on [5, 1, 4, 2, 8]. The red pair is being compared. The big number 5 "bubbles" to the right until it meets a bigger one. Green = in its final place.
  1. Compare items 0 and 1. Wrong order? Swap them.

  2. Move one place right: compare items 1 and 2. Then 2 and 3, and so on to the end. That's one pass.

  3. After pass 1, the biggest item is at the end, in its final place. After pass 2, the second biggest is too. So each pass can stop one place earlier.

  4. Early stop: if a whole pass makes no swaps, the list is sorted. Stop.

e.g.Example

[5, 1, 4, 2, 8] → pass 1 → [1, 4, 2, 5, 8] → pass 2 → [1, 2, 4, 5, 8] → pass 3: no swaps, done.

How many comparisons, at most?

The worst case is a list in reverse order: every pass swaps every pair. With n items, pass 1 compares n − 1 pairs, pass 2 compares n − 2, … down to 1.

4 rows × 5 dots = 20. The filled dots are exactly half: 20 ÷ 2 = 10.
The pairing trick as a picture, for n = 5. Filled dots = comparisons. A second, upside-down copy (hollow) turns the triangle into a rectangle.

By hand, for n = 5. Press Next line.

  1. Passes compare 4, then 3, then 2, then 1 pairs. Total: 4 + 3 + 2 + 1.

  2. Pair them up from the outside in: (4 + 1) and (3 + 2).

  3. Every pair adds to 5, which is n. There are 4 numbers, so (n − 1) ÷ 2 = 2 pairs.

  4. 2 pairs × 5 = 10. That's n × (n − 1) ÷ 2 = 5 × 4 ÷ 2 = 10.

  5. Same trick for n = 100: 100 × 99 ÷ 2 = 4,950 comparisons. For n = 1,000: 499,500. Ten times the items, about a hundred times the work.

def bubble_sort(items):
    n = len(items)
    swapped = True
    while swapped:
        swapped = False
        for i in range(n - 1):
            if items[i] > items[i + 1]:
                # swap the neighbours
                items[i], items[i + 1] = items[i + 1], items[i]
                swapped = True
        n = n - 1   # the last one is now in place
    return items
!Watch out

Easy to code, but very slow on big lists. The best case (already sorted) is fast: one pass, n − 1 comparisons, no swaps, stop. A version without the early stop and without shrinking always does (n − 1) × (n − 1) comparisons.

Insertion sort: like a hand of cards

Insertion sort keeps a sorted part on the left. It picks up the next item from the unsorted part and slides it left into its correct place.

sorted part unsorted part boundary drop in shiftshift Before: pick up 2. 5 and 4 are bigger, so each shifts right. After: 2 drops in after 1. The sorted part grew by one.
The dashed line is the boundary between the parts. Everything left of it is sorted (part colour). Each round moves the boundary one place right.
  1. The first item on its own counts as sorted.

  2. Pick up the first unsorted item. This leaves a gap.

  3. Compare it with the item to the left of the gap. Bigger than yours? Shift it right into the gap. Repeat.

  4. Smaller (or the front of the list)? Drop your item into the gap. The sorted part has grown by one.

▶

Insertion sort, one move at a time

In your hand
The list

e.g.Example

[5 | 1, 4, 2, 8] → [1, 5 | 4, 2, 8] → [1, 4, 5 | 2, 8] → [1, 2, 4, 5 | 8] → [1, 2, 4, 5, 8].

def insertion_sort(items):
    for i in range(1, len(items)):
        key = items[i]        # pick up the next card
        j = i - 1
        while j >= 0 and items[j] > key:
            items[j + 1] = items[j]   # shift right
            j = j - 1
        items[j + 1] = key    # drop it into the gap
    return items
?Why?

Why is it fast on nearly sorted lists? Each item only slides as far as it needs to. If it's already in place, that's one comparison and no shifts. Try "Nearly sorted list" above and watch the counter. Worst case (reverse order) is the same as bubble sort: n × (n − 1) ÷ 2.

Merge sort: split, then merge

Merge sort splits the list in half again and again until every piece has 1 item. A list of 1 is already sorted. Then it merges pieces back together, in order, two at a time.

Grey rows split. Green rows merge. 8 items: 3 levels of splitting (8 → 4 → 2 → 1) and 3 levels of merging. 3 = log₂ 8.
  1. Split: [6, 3, 8, 1] [5, 2, 7, 4]

  2. Split: [6, 3] [8, 1] [5, 2] [7, 4]

  3. Split into singles. Each single is sorted.

  4. Merge: [3, 6] [1, 8] [2, 5] [4, 7]

  5. Merge: [1, 3, 6, 8] [2, 4, 5, 7]

  6. Merge: [1, 2, 3, 4, 5, 6, 7, 8]

★Remember

To merge two sorted lists, only ever compare the front item of each. Take the smaller one. Repeat. When one list is empty, copy the rest of the other straight across.

▶

Merge sort, one comparison at a time

The pieces

def merge(left, right):
    result = []
    i = 0
    j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:      # compare the fronts
            result.append(left[i])
            i = i + 1
        else:
            result.append(right[j])
            j = j + 1
    return result + left[i:] + right[j:]   # copy what's left

def merge_sort(items):
    if len(items) <= 1:
        return items                       # 1 item: sorted
    mid = len(items) // 2
    return merge(merge_sort(items[:mid]), merge_sort(items[mid:]))
?Why?

Why is it fast? There are only about log₂ n levels (8 items → 3 levels, 1,000 → 10 levels). Each level merges all n items once, using at most about n comparisons. So the total is about n × log₂ n. For 1,000 items that's about 10,000, not 500,000.

!Watch out

Merge sort needs extra memory: the merged lists are new copies. Bubble and insertion sort work "in place", swapping inside the original list. Merge sort's function calls itself (that's called recursion). You don't need to write recursion at GCSE, only explain the split and merge steps.

Comparing the three sorts

There's rarely one best algorithm. There's only the best one for this data: how big it is, how messy it is, and how much memory you have.

SortComparisons: worstComparisons: bestExtra memoryGood for
Bubblen(n − 1) ÷ 2
reverse order
n − 1
already sorted, early stop
None (in place)Teaching. Tiny lists. Checking a list is sorted.
Insertionn(n − 1) ÷ 2
reverse order
n − 1
already sorted
None (in place)Small or nearly sorted lists. Adding new items to a sorted list.
Mergeabout n log₂ nabout half of thatA copy of the list (about n)Big lists. Steady speed whatever the order.
▶

The race: comparisons for a list of n

Searching for one item (worst case)
Sorting the whole list (worst case)
e.g.Real life

Python's own sorted() uses Timsort. It is a mix: insertion sort on small runs, then merging. Real programs combine the strengths of both.

★Exams

AQA 8525 names linear search, binary search, bubble sort and merge sort. OCR J277 adds insertion sort to those. Both ask you to compare them in words; neither needs you to write formulas like n log n.

Big-O: how the work grows

Computer scientists describe speed by asking one question: if the list gets bigger, how fast does the work grow? The answer is written with a capital O, for "order of". (This is A-level, but the idea is simple.)

NameIn plain wordsExampleDouble n, and the work…
O(log n)Halves the problem each stepBinary searchgoes up by just 1
O(n)Looks at every item onceLinear searchdoubles
O(n log n)About log n levels, each touching every itemMerge sorta bit more than doubles
O(n²)Compares (nearly) every item with every otherBubble, insertion (worst)goes up 4 times
Why n² hurts. Double the items (3 → 6): the row of n doubles (3 → 6) but the square of n² grows four times (9 → 36).
nlog₂ nnn log₂ nn²
10about 310about 33100
100about 7100about 66410,000
1,000about 101,000about 9,9661,000,000
1,000,000about 201,000,000about 20 million1,000,000,000,000
  1. Say a computer does 1 million comparisons a second.

  2. Sorting 1 million items with an n² sort: 10¹² ÷ 10⁶ = 1,000,000 seconds. That's about 11.6 days.

  3. With an n log n sort: 20,000,000 ÷ 1,000,000 = 20 seconds.

  4. Same computer. Same data. The algorithm made the difference between a coffee break and a fortnight.

?Why?

Why ignore the "÷ 2" in n(n − 1) ÷ 2? Big-O only cares about the shape of the growth. Halving a fortnight is still a week. Changing the shape (n² → n log n) turns it into seconds.

Sorting with no computer: networks and scales

A sorting network is a fixed set of "compare and swap" stations. Numbers travel along lanes. At each station two lanes meet: the smaller number goes up, the bigger goes down. At the end they're sorted, every time.

▶

Run a 6-lane sorting network

  1. 12 stations in 5 stages. Stations in the same stage never share a lane, so they can all happen at the same time. That's parallel processing, like the many cores in a graphics card.

  2. It never changes its plan. The same 12 comparisons happen whatever the numbers are. Bubble sort's comparisons depend on the data; a network's don't.

  3. The weighing version. Give a group 8 identical pots with different amounts of sand, and one balance scale. The only allowed question is "which of these two is heavier?" That is exactly one comparison. Sort the pots, and count how many weighings it took.

e.g.Classroom

CS Unplugged draws this network in chalk on the playground. Six pupils each hold a number card, walk along the lanes, and meet at the circles. Smaller goes left (or up), bigger goes right. Time a run, then try the same with bubble sort as a line of pupils.

Common mix-ups

The mistakes nearly everyone makes the first time. Read the left side, then the right.

People think

Binary search works on any list.

Actually

Only on a sorted list. Otherwise it may throw away the half your item is in.

People think

Binary search is always the better choice.

Actually

Not if the list is unsorted and you search once. Sorting first costs more than one linear search.

People think

"Binary search" has something to do with binary numbers.

Actually

"Binary" here means two: it splits the list into two halves each time.

People think

Bubble sort is done after one pass.

Actually

One pass only fixes the biggest item. You keep going until a pass makes no swaps.

People think

Merge sort compares every item in one list with every item in the other when merging.

Actually

It only compares the two front items. That's why merging is quick.

People think

The middle of a list of 10 (indexes 0–9) is index 5.

Actually

(0 + 9) ÷ 2 = 4.5, rounded down = index 4. Exam boards accept rounding up too, if you're consistent.

People think

A faster computer fixes a slow algorithm.

Actually

A 10 × faster computer turns 11 days into 1 day. A better algorithm turns 11 days into 20 seconds.

Check yourself

Quick questions first. Then endless practice: a wrong answer shows you the working, step by step.

[ {"q":"Binary search only works if the list is…","o":["Short","Sorted","Made of numbers"],"a":1,"w":"Throwing away half only makes sense if the list is in order. Sorted words work too."}, {"q":"What is the most checks binary search can need on 1,000 sorted items?","o":["500","10","1,000"],"a":1,"w":"1,000 halves 9 times to reach 1 (2⁹ = 512), plus one last check: 9 + 1 = 10."}, {"q":"A linear search on 50 unsorted items, looking for something that is **not** there, makes…","o":["1 check","25 checks","50 checks"],"a":2,"w":"It can only say 'not found' after checking every item: the worst case, n = 50."}, {"q":"Bubble sort compares…","o":["Neighbours","The first and last items","Random pairs"],"a":0,"w":"Items next to each other, swapping them if they're in the wrong order."}, {"q":"During bubble sort, a whole pass makes no swaps. This means…","o":["Do one more pass to be safe","The list is sorted: stop","Start again from the beginning"],"a":1,"w":"No pair was out of order, so the whole list is in order. This is the early stop."}, {"q":"Which sort splits the list into single items first?","o":["Insertion sort","Merge sort","Bubble sort"],"a":1,"w":"Split until every piece has 1 item, then merge back in order."}, {"q":"Insertion sort is especially quick when the list is…","o":["In reverse order","Nearly sorted","Very long"],"a":1,"w":"Each item only slides as far as it needs. Nearly sorted means hardly any shifting."}, {"q":"Merge sort is fast on big lists. What's its main cost?","o":["It needs extra memory for the merged copies","It only works on numbers","It needs the list to be sorted first"],"a":0,"w":"Merging builds new lists, so it uses about another n spaces of memory."} ]