edubun 한국어

Graph search: BFS and DFS

In 1736 Leonhard Euler showed that no walk crosses each of Königsberg's seven bridges exactly once, and graph theory began with that answer.

Dots and lines

A graph is a set of points, called vertices or nodes, joined by lines, called edges. The idea is plain, and it fits a huge range of things: cities joined by roads, web pages joined by links, people joined by friendships, squares of a maze joined where there is no wall. Once a problem is drawn as a graph, the same handful of algorithms can answer questions like "can I get from here to there?" and "what is the shortest way?"

BackgroundThe city of Königsberg (now Kaliningrad) had seven bridges linking two islands and the two banks of its river. People asked whether a walk could cross every bridge exactly once. In 1736 Leonhard Euler answered no. His key step was to throw away the map: only the land areas and the bridges between them mattered. Each land area became a point and each bridge a line. He then showed that such a walk needs every point, except possibly the start and the end, to have an even number of lines, and in Königsberg all four land areas had an odd number.
A B C D E F
The example graph in this unit. A is the start; F is the goal. Edges: A‑B, A‑C, B‑D, C‑D, C‑E, D‑F, E‑F.

Storing a graph in code

A common way to store a graph is an adjacency list: for each vertex, a list of its neighbours. MIT's 6.006 notes use exactly this, an array or dictionary Adj where Adj[u] lists the vertices joined to u. For the graph above it looks like this:

const graph = {
  A: ["B", "C"],
  B: ["A", "D"],
  C: ["A", "D", "E"],
  D: ["B", "C", "F"],
  E: ["C", "F"],
  F: ["D", "E"],
};

Breadth-first search: ring by ring

Breadth-first search (BFS) explores a graph in rings around the start. First the start itself, at distance 0. Then all its neighbours, at distance 1. Then every new vertex next to those, at distance 2, and so on. To do this it keeps a queue, a line where the first one in is the first one out. Take the vertex at the front of the queue, visit it, and add its unseen neighbours to the back. From A in the example, BFS visits A, then B and C, then D and E, then F. Because it moves outward one ring at a time, the first time BFS reaches a vertex it has found a path with the fewest edges. F is 3 edges from A, for example A, B, D, F.

function bfs(graph, start) {
  const seen = new Set([start]);
  const queue = [start];
  const order = [];
  while (queue.length) {
    const u = queue.shift();        // first in, first out
    order.push(u);
    for (const v of graph[u]) {
      if (!seen.has(v)) { seen.add(v); queue.push(v); }
    }
  }
  return order;   // A B C D E F
}

Depth-first search: as far as you can, then back up

Depth-first search (DFS) behaves like a person in a maze keeping one hand on the wall. It follows one path as deep as it can go. When it reaches a dead end or a place it has already seen, it backs up to the most recent choice point and tries the next option. It uses a stack instead of a queue, last in, first out, and is very natural to write with recursion, where the call stack does the remembering. From A, a recursive DFS visits A, B, D, C, E, F. DFS reaches every vertex BFS reaches, but the path it follows is not necessarily the shortest one.

function dfs(graph, u, seen = new Set(), order = []) {
  seen.add(u);
  order.push(u);
  for (const v of graph[u]) if (!seen.has(v)) dfs(graph, v, seen, order);
  return order;   // A B D C E F
}
Loading

Drag to turn. Scroll or pinch to zoom.

A maze as a graph: each open square is a vertex, joined to the open squares next to it. Run BFS, then DFS on the same maze, and compare the squares visited and the length of the path found.

Which one to use

In the lab's first maze, BFS visits 105 squares and finds a path of 22 moves, the shortest possible. DFS happens to dive toward the goal and visits only 31 squares, but its path takes 28 moves. Neither is always cheaper. Both handle each vertex and each edge a fixed number of times, so both run in O(V + E) time for V vertices and E edges. Choose BFS when you need the fewest steps: the fewest moves in a puzzle, the fewest links between two people. Choose DFS when you need to explore everything or follow choices to the end: checking whether every room of a maze connects, or trying the moves of a puzzle one line at a time and backing up when stuck. When edges have different lengths, as roads do, neither is enough on its own; methods such as Dijkstra's algorithm extend the BFS idea with distances.

Both searches must remember which vertices they have already seen. Without that, a graph with a loop (A to B to D to C and back to A) would send the search round and round forever.
graphVertices joined by edges
vertex (node)A point of a graph
edgeA line joining two vertices
adjacency listFor each vertex, the list of its neighbours
queueFirst in, first out; used by BFS
BFSBreadth-first search: ring by ring; finds fewest-edge paths
DFSDepth-first search: deep first, then back up; uses a stack
O(V + E)The running time of BFS and DFS
  1. In the example graph, how many edges are on the shortest path from A to F?
  2. How many edges does the example graph have?
  3. Which data structure does breadth-first search use?
    • A queue (first in, first out)
    • A stack (last in, first out)
    • A sorted list
    • No structure at all
  4. Which search is guaranteed to find a path with the fewest edges?
    • BFS
    • DFS
    • Both, always
    • Neither
  5. Put the vertices in the order BFS from A visits them (neighbours taken in alphabetical order).
  6. Put the vertices in the order recursive DFS from A visits them (neighbours in alphabetical order).
  7. On the lab's 15 by 9 grid with every wall cleared, how many moves is the shortest path from S (top left) to G (bottom right)?
  8. Why must BFS and DFS remember which vertices they have seen?
    • Otherwise a loop in the graph would make the search go round forever
    • To print them at the end
    • To sort them
    • It is only needed for DFS
  9. In what year did Euler solve the Königsberg bridge problem?
    • 1683
    • 1736
    • 1843
    • 1945
  10. What is the running time of BFS on a graph with V vertices and E edges?
    • O(V + E)
    • O(V × E)
    • O(log V)
    • O(2ⱽ)
  11. You want to know the fewest friend-of-a-friend links between two people in a social network. Which search?
    • BFS
    • DFS
    • Binary search
    • Merge sort
Key points
  • A graph is vertices joined by edges; adjacency lists store it.
  • BFS uses a queue, goes ring by ring, and finds fewest-edge paths.
  • DFS uses a stack (or recursion), goes deep first, then backs up.
  • Both run in O(V + E) and must remember seen vertices.
  • Euler's 1736 Königsberg bridges solution began graph theory.