Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 11 – Graphs: modelling, traversal and connectivity

Implicit graphs, state-space search and 0-1 BFS

Find the fewest knight moves between two squares by searching a graph that is never stored, starting from the brute force and the signal that points to BFS.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free intro, Pro: 7 of 30 minutes free
Free: The problem: the fewest knight moves between two squares. With a pass: the other 12 sections and 3 interview answers, about 23 minutes.

What you will learn

  • Model puzzles as graphs of states
  • Search state spaces with BFS and bidirectional BFS
  • Use 0-1 BFS when every edge costs 0 or 1

Before you start

What you’ll learn · Before you start

What you will learn

  • Model puzzles as graphs of states
  • Search state spaces with BFS and bidirectional BFS
  • Use 0-1 BFS when every edge costs 0 or 1

Before you start

On this page

Many search problems have a graph that nobody writes down. In implicit graphs the vertices are states, such as a knight’s square or a puzzle’s tray, and a rule generates the edges from each state only when they are needed. State-space search runs an ordinary graph search over those states. Breadth-first search from the start finds the fewest moves when every move costs the same, and it reaches each state once. 0-1 BFS finds the cheapest route when moves cost either 0 or 1, with a double-ended queue in place of a heap. In every case, the number of states decides the cost. Below is a problem of this kind, the fewest knight moves, with its brute force and the signal to notice.

The problem: the fewest knight moves between two squares

The problem

A knight starts on square (r1, c1) of a square board with n rows and n columns, both counted from 0. Each move jumps two squares in one direction and one square at right angles, and the knight may never leave the board. Return the fewest moves that take it to (r2, c2), or −1 when no sequence of moves gets there. Limits: 1 ≤ n ≤ 1,000.

  1. n = 8, from (0, 0) to (7, 7): 6 moves, corner to corner.
  2. n = 8, from (0, 0) to (1, 1): 4 moves. Two would do on an unbounded board, but both such routes leave this one.
  3. n = 3, from (0, 0) to (1, 1): −1, because no move ever lands on the centre of a 3 × 3 board.
A 5 by 5 board whose squares show the fewest knight moves from the top-left corner, from 0 to 4.Fewest moves from the top left0, start323234123214323232323234

Every square of a 5 × 5 board is a state; the number is its fewest moves from the corner

Text description of the diagram

The board has 5 rows and 5 columns, numbered from 0 at the top left, where the knight starts. Each square shows the fewest knight moves needed to reach it from there.

Row 0: 0 (the start), 3, 2, 3, 2. Row 1: 3, 4, 1, 2, 3. Row 2: 2, 1, 4, 3, 2. Row 3: 3, 2, 3, 2, 3. Row 4: 2, 3, 2, 3, 4.

Only two squares are one move away: row 1, column 2 and row 2, column 1. The square diagonally next to the start, row 1, column 1, needs 4 moves, as many as the far corner, because the moves that would reach it in two would leave the board.

The brute force: try every sequence of moves

Try every sequence of one move, then of two, and so on, until one ends on the target. That is right, but it never remembers a square, so it reaches the same squares again and again by different sequences:

Every sequence of moves, shortest first, with the count Python · brute/brute.py
# The brute force: try every sequence of knight moves, shortest sequences first (iterative deepening), and count the
# sequences it tries. It keeps no record of squares already reached, so it explores the same squares again and again.

MOVES = [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)]


def fewest_by_trying(n, start, target):
    """Fewest moves, or -1; also the number of move sequences tried."""
    tried = 0

    def reaches(square, moves_left):
        nonlocal tried
        tried += 1
        if square == target:
            return True
        if moves_left == 0:
            return False
        r, c = square
        return any(reaches((r + dr, c + dc), moves_left - 1)
                   for dr, dc in MOVES if 0 <= r + dr < n and 0 <= c + dc < n)

    for limit in range(n * n):              # a shortest route never repeats a square: at most n*n - 1 moves
        if reaches(start, limit):
            return limit, tried
    return -1, tried


import math

for n, start, target in [(8, (0, 0), (7, 7)), (8, (0, 0), (1, 1)), (3, (0, 0), (1, 1))]:
    moves, tried = fewest_by_trying(n, start, target)
    print(f"n = {n}, {start} -> {target}: answer {moves} after {tried:,} move sequences")
counts = []
for k in (3, 6, 9, 11):
    moves, tried = fewest_by_trying(16, (0, 0), (k, k))
    counts.append(tried)
    print(f"n = 16, (0, 0) -> {(k, k)}: {moves} moves after {tried:,} move sequences")
rate = (counts[-1] / counts[-2]) ** 0.5         # the last two targets are 2 moves apart
print(f"each extra move multiplies the sequences by about {rate:.1f}")
print(f"n = 1,000, corner to corner, 666 moves: about 10^{round(666 * math.log10(rate))} sequences")

Output

n = 8, (0, 0) -> (7, 7): answer 6 after 3,427 move sequences
n = 8, (0, 0) -> (1, 1): answer 4 after 133 move sequences
n = 3, (0, 0) -> (1, 1): answer -1 after 1,013 move sequences
n = 16, (0, 0) -> (3, 3): 2 moves after 8 move sequences
n = 16, (0, 0) -> (6, 6): 4 moves after 113 move sequences
n = 16, (0, 0) -> (9, 9): 6 moves after 3,870 move sequences
n = 16, (0, 0) -> (11, 11): 8 moves after 174,991 move sequences
each extra move multiplies the sequences by about 6.7
n = 1,000, corner to corner, 666 moves: about 10^551 sequences

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute.py

Each extra move multiplies the work by about 6.7, so 8 moves already cost 174,991 sequences, and the 666 moves from corner to corner of a 1,000 × 1,000 board would need about 10⁵⁵¹. An answer of −1 is worse: the search must try every sequence of up to n² − 1 moves before it can give up.

What to notice

The future of the knight depends only on its square, never on the route that brought it there. So each square is a state: a vertex of a graph with at most n² = 10⁶ vertices and 8 edges each, made on the fly. Implicit graphs of this size are small for a search that reaches every state at most once. Every move costs one and the question asks for the fewest moves, which is exactly what breadth-first search answers. This is state-space search in its simplest form, with a square as the whole state. Bidirectional BFS runs one search from the start and one from the target and stops where they meet. If some moves cost nothing and others cost one, 0-1 BFS keeps the same linear cost.

BFS over the squares, step by step Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Why the first visit is the fewest moves: the layer invariant
  • The code in five languages, with run records
  • How many states, before any code
  • Bidirectional BFS, measured
  • 0-1 BFS: when a move can cost nothing
  • Edge cases and four common bugs, reproduced
  • Variants: blocked squares, many knights, route counts and costs of 2
  • Exercise: fewest knight moves
    • Fewest knight moves between two squares
  • Check yourself
  • A practice set with answers
  • Interview questions
    • In a state-space search, what must a state hold, and what goes wrong if it leaves something out?
    • Your BFS for the fewest knight moves passes every test on boards up to 1,000 × 1,000. A new version must answer on boards of 100,000 × 100,000 within a second. What breaks first, and how do you still give the exact answer?
    • A robot arm has 4 joints, each set to one of 36 angles. A move turns one joint by one notch either way, and 5 % of all settings make the arm hit its frame. How many states are there, what does BFS between two settings cost, and how much does searching from both ends save?
    • A route planner for warehouse robots returns routes through the fewest cells, but operations wants the fewest turns, and turning around counts as two. Model the states and the costs, choose the search, and estimate its work and memory on a 2,000 × 2,000 grid.
  • Key takeaways

7 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 24 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.