gl/ripetizioni

Graphs and trees: BFS and DFS for the algorithms exam

Graphs (adjacency lists and matrix), BFS and DFS traversals traced on 6 nodes, O(V+E) cost, binary trees and typical mistakes in the Algorithms exam.

by Gaetano Livornese

Read the original in Italian

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:

NodeNeighbours
AB, C
BA, D
CA, D, E
DB, C
EC, F
FE

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 :

  1. Dequeue : enqueue . Queue: .
  2. Dequeue : is marked, enqueue . Queue: .
  3. Dequeue : and already marked, enqueue . Queue: .
  4. Dequeue : neighbours are already marked. Queue: .
  5. Dequeue : enqueue . Queue: .
  6. 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.

Frequently asked questions

What is the difference between breadth-first (BFS) and depth-first (DFS) traversal?

BFS uses a queue and visits nodes level by level, by distance from the source; DFS uses a stack (or recursion) and goes as deep as it can before backtracking.

What is the complexity of BFS and DFS?

With adjacency lists both cost O(V+E), because every node is visited once and every adjacency list is scanned once. With an adjacency matrix the cost rises to O(V²).

Why do you have to mark visited nodes in a traversal?

If the graph has cycles, without this check you keep returning to the same nodes forever. Marking guarantees that each node enters the queue, or is explored, only once.

Which traversal finds the path with the fewest edges between two nodes?

BFS: since it visits level by level, the level at which a node is discovered is its minimum distance in number of edges from the source, in an unweighted graph.

Keep reading
  • Algorithm complexity and Big-O notation

    O, Ω and Θ notation, recurrences and the hierarchy of complexities for the Algorithms and Data Structures exam: essential theory and three worked exercises.

  • From flowchart to Python code

    Flowchart blocks, selection and iteration, pseudocode and translation into Python, types and casting: three worked examples from diagram to code.

  • Boolean algebra and Karnaugh maps: the method

    Logic gates, De Morgan's theorems, SOP/POS canonical forms and 2-3-4 variable Karnaugh maps: the step-by-step method for Logic Networks, with exercises.

Want to put what you read into practice?

Let's talk for 30 minutes, free: we look together at where you are stuck and I tell you honestly how I can help.

Or message me on WhatsApp.