If you are taking Algorithms and Data Structures (computer science or engineering), lectures started a few weeks ago, and this is usually the period when, after complexity, graphs and trees arrive. The winter-session exam, in January or February, is eight to ten weeks away: you have time to build the method calmly. This topic has one advantage: hand traces can be learned, and in the exam they are almost always the surest way to pick up marks. Before this post it helps to be clear on complexity with big-O notation; for the in-memory representation with nodes and references you also need pointers in C.
Representing a graph
A graph has a set of nodes (vertices) and a set of edges . We will also use and for their sizes, as in the costs. Two standard representations:
- Adjacency lists: for each node, the list of its neighbours. It takes space and is the natural choice for sparse graphs.
- Adjacency matrix: a table with 1 (or the weight) if the edge exists. It takes and lets you find out in constant time whether two nodes are connected.
In an undirected graph each edge appears twice (in two lists, in two symmetric cells): the sum of the degrees is .
The graph used throughout the article has 6 nodes and 6 edges: , , , , , . Adjacency lists, in alphabetical order:
| Node | Neighbours |
|---|---|
| A | B, C |
| B | A, D |
| C | A, D, E |
| D | B, C |
| E | C, F |
| F | E |
The degrees are 2, 2, 3, 2, 2, 1: they add up to 12, that is with .
BFS: breadth-first traversal with a queue
You start from a source, mark it and put it in the queue. While the queue is not empty: take out the first node and put its not yet marked neighbours in the queue, marking them straight away.
BFS(G, s):
mark s; queue = [s]
while queue is not empty:
u = dequeue(queue)
visit u
for each v in neighbours(u):
if v is not marked:
mark v; enqueue v
Trace from :
- Dequeue : enqueue . Queue: .
- Dequeue : is marked, enqueue . Queue: .
- Dequeue : and already marked, enqueue . Queue: .
- Dequeue : neighbours are already marked. Queue: .
- Dequeue : enqueue . Queue: .
- Dequeue . Queue empty.
BFS visiting order: . The level at which I discover a node is its minimum distance in edges: , , , . That is why BFS finds shortest paths in unweighted graphs.
DFS: depth-first traversal with recursion
You move on to the first unvisited neighbour and go back only when the neighbours run out.
DFS(u):
mark u; visit u
for each v in neighbours(u):
if v is not marked:
DFS(v)
Trace from : I start at and go to ; from the neighbour is marked, so I go to ; from , is marked, I go to ; from , and are marked, I go to ; from I go to . From there is nothing else and I backtrack all the way to .
DFS visiting order: . Compared with BFS it changes: comes before , because DFS goes deep before coming back. The stack of recursive calls does the job that the queue does in BFS. If you write DFS iteratively with an explicit stack, watch the order: if you push the neighbours in alphabetical order, the first one popped is the last, so the visiting order is reversed compared with the recursive one, unless you push them in reverse.
Complexity: O(V+E)
With adjacency lists, in both traversals each node is marked and processed once (cost ) and each list is scanned once; the lists hold elements in all for an undirected graph, for a directed one. The cost is
With the adjacency matrix, to find the neighbours of a node you have to scan a whole row of cells: for nodes the cost becomes . On sparse graphs lists win.
Binary trees: pre-order, in-order and post-order
A tree is a connected graph with no cycles; a binary tree has, for each node, at most one left child and one right child. Traversals are a DFS with three variants, depending on when you visit the root:
- pre-order: root, left subtree, right subtree;
- in-order: left, root, right;
- post-order: left, right, root.
Take the tree with root 8, whose children are 3 and 10; the children of 3 are 1 and 6; the children of 6 are 4 and 7; 10 has only a right child 14, and 14 has only a left child 13.
- Pre-order:
- In-order:
- Post-order:
- Level by level (BFS on the tree):
In this tree the rules of the binary search tree hold (smaller values on the left, larger on the right), so the in-order traversal comes out sorted. It is an exam observation, but it holds only for that type of tree.
Typical mistakes in the exam
Order of the neighbours. The visiting order depends on the order in which the adjacency lists are written. If the question does not say, state it (“neighbours in alphabetical order”): with the lists reversed, on our graph BFS would give and DFS . An answer different from the model solution can still be correct, provided you state the convention.
Already visited nodes. Forgetting the marking means going back to nodes already seen and, if there is a cycle, never terminating. Another variant: in BFS you mark the node when you dequeue it rather than when you enqueue it, and so you can put it in the queue several times.
Mixing up queue and stack. BFS with a stack is not BFS.
Pre-, in- and post-order. Write the sequence from the definition, starting at the root and descending recursively, not by intuition.
Cost. Saying for lists, or without the : in a disconnected graph or one with isolated nodes you cannot drop the term.
Three worked exercises
1. Shortest path with BFS
What is the minimum number of edges to go from to in the graph above? Give a path.
From the BFS trace: is discovered from at level 3, from at level 2, from at level 1. Path , 3 edges. There is no shorter path: to reach you must go through , the only neighbour of , and can be reached only from , which is at distance 1 from .
2. Lists or matrix?
Compare the space for the 6-node, 6-edge graph and for a graph with 1000 nodes and 3000 edges.
For our graph the matrix has cells, the lists elements (plus 6 heads). With and : the matrix has cells, the lists about elements (plus 1000 heads). The graph is very sparse, and indeed the lists cost a small fraction. For a traversal, the comparison is against .
3. Trees: rebuilding from a pair of traversals
Pre-order ; in-order . Rebuild the tree and write the post-order.
The root is the first in the pre-order: . In the in-order, are to the left of and to the right. The left subtree has pre-order : root ; in the in-order is to the left of and to the right. So has as children (with children and ) and . Post-order: .
Test yourself
For graphs and trees the page of university practice tests is useful, as it collects questions by course: check how much of Algorithms it covers, because coverage depends on the course. The most useful exercise is still redoing the traces by hand on a graph of your own, changing the source and the order of the neighbours. Studying Karnaugh maps and Boolean algebra is another Fundamentals topic where tracing by hand pays off in the same way. More resources are on the computer science page; for a 1-to-1 online session on traces, you can start with a free introductory call.