- How to analyse a problem and design a correct algorithm for it.
- How to compare algorithms using execution time, space and Big-O notation.
- How common data structures affect algorithm design.
- How standard searches, sorts and shortest-path algorithms work.
Algorithm
An algorithm is a finite, ordered set of precise instructions that solves a problem or completes a task.
An algorithm is not just “some code”. Before coding, you need to understand the situation: what data goes in, what result must come out, and what restrictions apply.
Useful questions:
- What are the inputs and outputs?
- How large could the input be? We usually call this input size nnn.
- Is the data already sorted?
- Are duplicates allowed?
- Is the data stored in an array, linked list, tree or graph?
- Are there memory limits?
- What are the awkward edge cases, such as an empty list or a missing value?
A precondition is something assumed to be true before the algorithm starts, such as “the list is sorted”. A postcondition is something that should be true when it finishes, such as “the returned index contains the target value”.
Design before coding
Good algorithm design means choosing steps and data structures that match the problem, not just writing the first solution that works on a small example.
Choosing a search approach
A school stores 50,000 candidate numbers in a sorted array. You need to check whether a candidate number exists.
- Because the data is sorted and stored in an array, you can use binary search, which repeatedly halves the search area.
- A linear search may check up to 50,000 items, so its worst-case time is O(n)O(n)O(n).
- Binary search needs only about 16 comparisons because 216=655362^{16}=65536216=65536, so its time is O(logn)O(\log n)O(logn) and it uses constant extra space.
An algorithm must be correct, but for large inputs it also needs to be efficient.
Execution time means how long the algorithm takes, usually estimated by counting key operations. Space means how much memory the algorithm needs, including extra arrays, queues, stacks or recursion.
Big-O notation
Big-O notation describes how an algorithm’s time or space grows as input size increases. It focuses on growth rate, not exact seconds.
Important classes for OCR H446:
- O(1)O(1)O(1) — constant time: does not grow with input size, such as reading the top item of a stack.
- O(logn)O(\log n)O(logn) — logarithmic time: grows very slowly, such as binary search.
- O(n)O(n)O(n) — linear time: grows in proportion to input size, such as linear search.
- O(nk)O(n^k)O(nk) — polynomial time: includes O(n2)O(n^2)O(n2) and O(n3)O(n^3)O(n3), often caused by nested loops.
- O(2n)O(2^n)O(2n) — exponential time: doubles rapidly as input size grows, such as trying every subset.
- O(nlogn)O(n \log n)O(nlogn) — common for efficient comparison sorts such as merge sort and average-case quick sort.

Methods for determining efficiency include:
- Counting operations, especially loop repetitions.
- Dry running with a trace table on small inputs.
- Benchmarking, where you time real executions, although this depends on hardware, language and test data.
- Asymptotic analysis, where you simplify the growth rate using Big-O notation.
Deriving complexity from loops
An algorithm first loops through all nnn items once, then compares every item with every other item using a nested loop.
- The first loop performs nnn visits, so that part is O(n)O(n)O(n).
- The nested loop performs n×n=n2n \times n = n^2n×n=n2 comparisons, so that part is O(n2)O(n^2)O(n2).
- Combined work is n2+nn^2+nn2+n, and Big-O keeps the dominant term, so the algorithm is O(n2)O(n^2)O(n2).
Ignoring the data set
Do not choose an algorithm from Big-O alone. Binary search is fast, but it is unsuitable if the data is unsorted or stored in a structure without efficient random access.
Data structure
A data structure is a way of organising data so that particular operations can be performed efficiently.
A stack is a last-in, first-out structure, often written LIFO. The newest item is removed first.
Typical operations:
- Push: add an item to the top.
- Pop: remove and return the top item.
- Peek: read the top item without removing it.
Stacks are useful for recursion, undo features, expression evaluation and depth-first search.
A queue is a first-in, first-out structure, often written FIFO. The oldest item is removed first.
Typical operations:
- Enqueue: add an item to the rear.
- Dequeue: remove and return the front item.
Queues are useful for printer queues, simulations and breadth-first traversal of trees or graphs.
A linked list is made from nodes. Each node stores data and a pointer or reference to the next node. The first node is reached using the head pointer.
To search a linked list, start at the head and follow links until the item is found or the end is reached. This is usually O(n)O(n)O(n).
Insertion and deletion can be efficient once you already have the correct position, because you change pointers rather than shifting many array elements.
Pointer order matters
When inserting into a linked list, set the new node’s next pointer before changing the previous node’s pointer, otherwise you can lose access to the rest of the list.
A tree is a hierarchical data structure made from nodes connected by edges. The top node is the root. A node with no children is a leaf. In a binary tree, each node has at most two children.
Two important tree traversal algorithms are:
- Depth-first post-order traversal: visit the children first, then the node itself.
- Breadth-first traversal: visit level by level, usually using a queue.

OCR-style pseudocode for post-order traversal:
procedure postOrder(node)
if node != null then
postOrder(node.left)
postOrder(node.right)
output node.data
endif
endprocedure
Tracing tree traversals
For the tree with root A, children B and C, and leaves D, E, F and G:
- Post-order goes deep into the left subtree first: visit D, then E, then B.
- It then processes the right subtree: visit F, then G, then C.
- Finally it visits the root, giving D, E, B, F, G, C, A.
- Breadth-first visits level by level, giving A, B, C, D, E, F, G.
Linear search checks each item in order until it finds the target or reaches the end.
It works on unsorted data and linked lists, but its worst-case time is O(n)O(n)O(n).
function linearSearch(items[], n, target)
for i = 0 to n - 1
if items[i] == target then
return i
endif
next i
return -1
endfunction
Binary search repeatedly checks the middle item and discards half of the remaining data.
It requires sorted data and efficient access to the middle item, so it is usually used with arrays. Its time is O(logn)O(\log n)O(logn).
function binarySearch(items[], n, target)
low = 0
high = n - 1
while low <= high
mid = (low + high) DIV 2
if items[mid] == target then
return mid
elseif target < items[mid] then
high = mid - 1
else
low = mid + 1
endif
endwhile
return -1
endfunction
Searching a sorted array
Search for 33 in [3, 5, 9, 12, 18, 21, 26, 33, 40].
- Start with
low = 0, high = 8, so mid = 4. The middle value is 18, and 33 is larger, so discard indices 0 to 4.
- Now
low = 5, high = 8, so mid = 6. The value is 26, and 33 is larger, so discard indices 5 to 6.
- Now
low = 7, high = 8, so mid = 7. The value is 33, so the algorithm returns index 7.
A sorting algorithm puts data into order, such as ascending numerical order or alphabetical order.
An in-place sort uses only a small amount of extra memory. A stable sort keeps equal items in their original relative order.
- Bubble sort repeatedly compares adjacent items and swaps them if they are in the wrong order. It is simple but usually slow: O(n2)O(n^2)O(n2).
- Insertion sort builds a sorted section at the start of the list and inserts each new item into the correct place. It is good for small or nearly sorted data, but worst-case time is O(n2)O(n^2)O(n2).
- Merge sort splits the data into halves, recursively sorts them, then merges the sorted halves. It is O(nlogn)O(n \log n)O(nlogn) and stable, but needs extra memory.
- Quick sort chooses a pivot, partitions the data around it, then recursively sorts the partitions. It is often fast, with average time O(nlogn)O(n \log n)O(nlogn), but worst-case time O(n2)O(n^2)O(n2) if pivots are badly chosen.
Choosing a sorting algorithm
You need to sort different data sets.
- For 20 nearly sorted names, insertion sort is sensible because it has low overhead and only a few items may need shifting.
- For 1,000,000 random records where predictable performance matters, merge sort is a strong choice because it stays O(nlogn)O(n \log n)O(nlogn).
- If memory is tight, quick sort may be preferred over merge sort because merge sort needs an extra array, although quick sort’s worst case must be considered.
A graph is a set of nodes, called vertices, connected by edges. In a weighted graph, each edge has a cost, distance or time.
Dijkstra’s algorithm finds the shortest path from a start node to other nodes in a graph with non-negative edge weights.
Main idea:
- Give the start node distance 0 and all other nodes infinity.
- Repeatedly choose the unvisited node with the smallest tentative distance.
- Update neighbouring distances if a shorter route has been found.
- Stop when the target is visited or all reachable nodes are processed.
Applying Dijkstra’s algorithm
Edges are A–B cost 2, A–C cost 5, B–C cost 1, B–D cost 4, and C–D cost 1. Find the shortest path from A to D.
- Start at A with distance 0. Update neighbours: B becomes 2 and C becomes 5.
- Choose B because it has the smallest tentative distance. Through B, C becomes 3 and D becomes 6.
- Choose C next. Through C, D becomes 4, which is better than 6.
- Choose D. The shortest route is A → B → C → D with total cost 4.
A* also finds a shortest path, but it uses a heuristic, which is an estimate of the remaining cost to the goal.
It chooses the node with the smallest value of:
f(n)=g(n)+h(n)f(n)=g(n)+h(n)f(n)=g(n)+h(n)
where g(n)g(n)g(n) is the known cost from the start and h(n)h(n)h(n) is the estimated cost to the goal.
A* is especially useful in route finding and games because a good heuristic can direct the search towards the target instead of exploring equally in all directions.
Shortest path edge cases
Dijkstra’s algorithm assumes non-negative edge weights. A* is guaranteed to find an optimal route only if its heuristic is admissible, meaning it never overestimates the remaining cost.
When comparing algorithms, discuss both time and space, and link your answer to the task and data set.
For example:
- Use binary search only when the data is sorted and random access is efficient.
- Use linear search when the data is unsorted or stored as a linked list.
- Use insertion sort for small or nearly sorted data.
- Use merge sort when stable, predictable O(nlogn)O(n \log n)O(nlogn) sorting is needed and extra memory is available.
- Use Dijkstra for shortest paths with non-negative weights.
- Use A* when you have a useful heuristic to guide the search.
In the exam
- State assumptions clearly, especially whether the data is sorted, weighted, linked, array-based or tree-based.
- Compare algorithms using both execution time and space, not just one of them.
- For trace questions, keep the changing values visible:
low, high, mid, stack top, queue front/rear, or tentative distances.
- Use Big-O for growth rate, but explain suitability in the context of the actual task and data set.
Check yourself
- Why is binary search unsuitable for an unsorted linked list?
- What data structure is naturally used for breadth-first traversal?
- When might merge sort be preferred over quick sort?