Iterative deepening depth-first search Created 2026-10-20 Updated 2026-10-24
Repeat a depth-limited depth-first search with successively larger depth limits. With unit-cost steps, the first successful depth is the minimum number of steps, provided each depth-limited search is complete. Each pass stores only the current path and its search bookkeeping.
Iterative lengthening search 2026-10-24
Iterative lengthening search repeatedly runs cost-bounded depth-first search, starting with threshold zero and replacing each unsuccessful threshold by the smallest rejected path cost. It uses a depth-first search stack rather than the potentially large search frontier of uniform-cost search.
Suppose an optimal plan has cost and each pass terminates. At every unsuccessful threshold , some first rejected prefix of an optimal plan has path cost at most . Therefore the next threshold is no larger than . Any first returned goal state has cost at most and hence exactly . Finite branching factor and a positive lower bound on step costs ensure that only finitely many search nodes precede that cost, giving completeness as well as optimality. With arbitrary real step costs, the threshold increments can be much smaller than the smallest step cost: the difference between two path costs need not itself be a step cost.
Neighbour enumeration 2026-10-14
Neighbour enumeration reports the neighbours of a specified vertex. For a simple graph, scan its adjacency list and report each entry. With constant-time list access and work per entry, a list of length needs time in Big O notation, conventionally written for nonempty lists. Use vertex degree for an undirected graph and outdegree for a directed graph. Obtaining the list reference can take constant time; consuming it requires visiting every entry.[ref]
Digraph.java. Digraph.java, class documentation and adj: obtaining the collection versus iterating in time proportional to outdegree
In our undirected example, enumerating 's neighbours reads : three entries. Scanning row of the full adjacency matrix reads six entries, , to produce the same answer. In the directed variant, enumerating 's outgoing neighbours reads no neighbour entries, but still needs constant work to locate and recognize its empty list. For a multigraph, scanning lists enumerates incident edges with multiplicity; reporting only distinct neighbours additionally requires removing repetitions.
For a short time complexity argument, each reported entry requires at least one output operation, and one scan suffices. Across all vertices, the undirected entry count is by the handshaking lemma; the directed count is . A graph search that processes each vertex once and scans each of its entries once therefore does structural work, provided its visited-state access and FIFO queue or stack operations also have the required constant-time costs. With a full adjacency matrix, scanning every row instead inspects entries. This is how the representation enters the analysis of breadth-first search and depth-first search.
Use one logical traversal token carrying the discovered vertex set and the active LIFO stack of vertices. The current vertex either discovers its first still-unvisited outgoing graph neighbour, or returns to its preceding stack entry. The former records both the child's parent in a rooted tree and the parent's child. Unlike concurrent flooding discovery, this visits one entire branch before the next, giving an actual depth-first search.
The important directed-graph detail is that a child's parent need not be an outgoing graph neighbour. Return the token by routing it through the original directed graph; a routing hop does not count as a traversal visit. The following duplicate-suppressed flooding route requires no previously known topology and sends only to immediate outgoing graph neighbours.
final case class Token(visited: Set[NodeId], stack: List[NodeId], serial: Int)
sealed trait Msg
final case class Route(serial: Int, target: NodeId, token: Token) extends Msg
case object Finished extends MsgHere
stack.head is the current vertex. Use the same paired toNode and fromNode ports as in (i), now with the new Msg type. Set mailbox capacity to , normalize neighbours with distinct, and again leave the mailboxes open until all processes have joined. The following helpers are local functions inside each node's generated process.def span: PROC = proc {
val adj = neighbours.distinct
val routed = scala.collection.mutable.Set[Int]()
var entered = false
var done = false
def flood(message: Msg): Unit =
for (w <- adj) toNode(w) ! message
def move(target: NodeId, state: Token): Unit = {
val s = state.serial + 1
routed += s
flood(Route(s, target, state.copy(serial = s)))
}
def step(t: Token): Unit = {
assert(t.stack.head == me)
if (!entered) {
entered = true
parent = if (t.stack.tail.isEmpty) me else t.stack.tail.head
children = Nil
}
adj.find(w => !t.visited(w)) match {
case Some(w) =>
children = children :+ w
move(w, Token(t.visited + w, w :: t.stack, t.serial))
case None =>
t.stack.tail match {
case p :: rest => move(p, t.copy(stack = p :: rest))
case Nil => flood(Finished); done = true
}
}
}
if (me == 0) step(Token(Set(0), List(0), 0))
while (!done) {
fromNode(me) ? () match {
case r @ Route(s, target, t) =>
if (!routed(s)) {
routed += s
if (target == me) step(t) else flood(r)
}
case Finished => flood(Finished); done = true
}
}
}Every route number is globally unique: there is one logical token, and its serial increases at every move. The sender inserts that serial in its own
routed set before flooding; this stops a returning copy from circulating again. Intermediate vertices forward the first copy of an envelope and ignore later copies. Its target consumes exactly one copy without forwarding it. Because the directed graph is strongly connected, a simple directed path from sender to target exists; its intermediate vertices eventually relay the envelope. Thus each move eventually finishes.A routed return can pass through a vertex not yet discovered by the depth-first search. That vertex only forwards the envelope: it does not run
step, change entered, or assign a parent. This distinction is essential. For the directed cycle , returning from two to one uses , while the logical traversal stack changes from to .A depth-first search return follows a routed directed path when the child has no reverse directed edge
. Details. The discovered vertex set only grows. Each newly discovered vertex is pushed once and later popped once; an exhausted vertex can never acquire a new unvisited neighbour as this set grows. Consequently there are exactly downward moves and return moves. When the root exhausts its outgoing graph neighbours, every vertex has been visited, by the standard reachability argument for depth-first search. Each nonroot parent was recorded along the actual downward directed edge, and
children records exactly those assignments in discovery order.There are route envelopes, each forwarded at most once per outgoing directed edge, and one final
Finished flood. Each node forwards Finished before exiting; strong connectivity therefore delivers it everywhere. There are at most total messages into any mailbox, below the proposed capacity. Even a mailbox whose process has already exited cannot block later sends. All processes terminate withThis deliberately trades communication efficiency for a simple valid solution on a genuinely directed graph. A direct parent acknowledgement would instead require an additional bidirectional-edge assumption, which the question does not supply. Past exam of the computer science course of the University of Oxford 2018 Algorithms 2 d Solution 2026-10-31
In depth-first search of an undirected graph, every edge joins a vertex to one of its ancestors or descendants in the discovery tree. To see this directly, suppose is discovered before its neighbour . If is still undiscovered, the search from cannot finish before discovering : it eventually scans . Thus is discovered while the call for remains active and belongs to its descendant subtree.[ref]
Depth-first search by Jeff Erickson. Chapter 6, §6.1: active recursion intervals, tree/back edges and ancestor relations
For any edge , its ancestor endpoint therefore has a proper descendant and hence at least one child. It is an internal vertex of a rooted tree. Every edge has an internal endpoint, so the internal vertices form a vertex cover. The relevant leaves of a rooted tree here have no children in the rooted discovery tree; a root of vertex degree one can still be internal.
Past exam of the computer science course of the University of Oxford 2018 Algorithms 2 f Solution 2026-10-31
For a connected graph with at least one edge, run depth-first search and output the set of internal vertices of a rooted tree in its discovery tree . Part (d) proves that is a vertex cover. It is also connected: every ancestor of an internal vertex is internal, so the graph path in from that vertex to the root stays inside . The root is internal when .
For the approximation guarantee, choose a maximum-size proper matching in a rooted tree of , whose existence is supplied. It is used only in the analysis; the algorithm need not construct it. Since every matched edge uses two distinct vertices,By part (e), . ThereforeThe final inequality holds because every vertex cover of , connected or otherwise, must use at least one distinct endpoint for each of the disjoint edges in . Thus this is a 2-approximation algorithm in time complexity.
For completeness, a disconnected input needs a feasibility check: if two different connected components of a graph contain edges, no connected vertex cover can meet both. If exactly one component contains edges, run the construction on that component and ignore isolated vertices. If there are no edges, the empty vertex cover is optimal when an empty solution is permitted; if a nonempty connected solution is required, any single vertex is optimal for a nonempty input. These conventions cover the boundary cases omitted from the main approximation argument.
The first assertion is false. Give the root a first child that is a goal state of cost ten and another child leading to many cheaper non-goal search nodes. Depth-first search can take the first child and stop immediately. A-star search with the admissible heuristic behaves as uniform-cost search and expands the cheaper non-goal search nodes before selecting that goal. Thus an admissible heuristic does not make A-star search expand fewer nodes than every possible depth-first search ordering. The latter need not return an optimal solution.
The second assertion is true under the usual finite-branching factor assumption. Breadth-first search orders by search depth, not path cost. Every finite depth contains finitely many search nodes, so a goal at finite depth is eventually reached even if some steps cost zero. Zero-cost steps can affect cost optimality, but do not obstruct this completeness argument. An infinite branching factor would invalidate the argument irrespective of step costs.
The third assertion is true as a worst-case black-box requirement, with a finite optimum assumed. If outgoing actions are known only by expanding a state, an unexamined reachable branch can conceal a sufficiently negative-cost action followed by a route to a goal. Its total cost can beat the current solution by an arbitrarily large amount. Without additional lower bounds or structural information, finding one cheap solution cannot justify stopping; exhaustive examination can be necessary. This does not assert that every already-described irrelevant state must be visited on every input. Moreover, a reachable negative-cost cycle in a graph that can still reach a goal can make costs unbounded below, in which case no finite-cost optimal solution exists at all.
Iterative deepening depth-first search increases a bound on the number of actions, usually through . Iterative lengthening search instead bounds accumulated path cost; after an unsuccessful pass, its next bound is the least path cost among the rejected search nodes. Thus it can distinguish shallow expensive paths from deeper cheap ones.
Both repeatedly perform depth-first search and retain its small active-path storage, trading repeated work for avoiding a large search frontier. With unit step costs their bounds coincide. With unequal positive step costs, iterative deepening depth-first search finds a solution with the fewest actions, while iterative lengthening search, under the termination conditions discussed next, finds one of least total cost. Real-valued path costs can make iterative lengthening search perform many more passes than the depth-bounded method.
Start the threshold at zero and let be the least solution cost. Suppose an unsuccessful pass has threshold . Along an optimal action sequence, take the first prefix whose path cost exceeds . Its preceding prefix is explored, so that rejected prefix contributes to the next-threshold calculation. Positive step costs imply that its cost is at most . Therefore the next threshold is also at most .
By induction, no threshold used before the first solution exceeds . A returned goal state therefore has cost at most ; by the definition of it cannot cost less. The first returned solution is optimal.
The termination qualification matters. In a finitely branching problem with step costs at least , a threshold admits only depths up to , so each pass is finite and only finitely many path costs precede . These conditions establish completeness as well. Strict positivity alone on an arbitrary infinite tree is insufficient: an infinite branch with costs can remain below a finite threshold and trap depth-first search. The optimality argument above is valid whenever the required passes terminate; a finite graph with finitely many strictly positive action costs supplies a uniform positive lower bound.
The depth assertion is false. Each move occupies one new square, so the general bound is , not . On a board no knights attack, and a play has four moves. More generally, filling a single checkerboard colour gives a legal partial play of length , since knight moves always change colour.
The opponent-independent “best possible move” assertion is false. Minimax search maximizes the guaranteed utility against an opponent choosing the worst continuation. It need not maximize the outcome against a particular weaker opponent. If a winning strategy is available, its guaranteed win cannot be improved on in win/loss utility; if every root move loses against perfect play, all have the same minimax value, yet some may exploit a weaker opponent better. Thus the valid universal statement is that a minimax strategy guarantees its minimax value, not that its root choice is best for every possible opponent behavior.[ref]
CS 188 minimax notes. §3.2, opening assumption: the opponent chooses the move worst for the agent
The storage assertion is false for the usual minimax implementation. Here the maximum branching factor is and maximum depth is . A conventional depth-first search retaining pending successors uses the usual search-node bound. Generating successors lazily and applying/undoing moves in one board can reduce this to stack frames plus board storage; neither accounting should confuse the board side length with its squares.
The winning-strategy assertion is true. The game is finite, has perfect information, alternates two players, and has no draw under the normal-play convention. A terminal position is losing for the player to move. By backward induction, a nonterminal position is winning exactly when at least one legal move reaches a losing position; otherwise it is losing. This labels every position, including the initial board, so exactly one of the two players has a winning strategy.
TODO: the classifications, chosen heuristic functions and least-cost outcomes for G1–G5 cannot be established from the corrupted original Figure 1. The missing edges, costs, priority labels and lower portions of the five search trees are required inputs. The surviving portions and the two heuristic function tables do not determine those answers. No reconstructed graph or invented expansion trace is assumed.
For a readable instance, depth-first search follows the deepest pending branch and breadth-first search expands by search depth; neither generally minimizes unequal edge costs. Uniform-cost search orders by path cost , whereas A-star search orders by . A displayed A-star search score would identify the estimate by subtracting the accumulated path cost. Under nonnegative costs and the usual terminating-search assumptions, uniform-cost search returns a least-cost goal state when testing goals on removal from the search frontier; A-star search additionally needs an admissible heuristic with appropriate reopening, or a consistent heuristic for the usual closed-set implementation. These rules explain how the missing information would be used, but cannot identify a particular damaged search tree.
Depth-first search computes minimax search values with much less live storage and naturally supports alpha-beta pruning. A parent's value needs only its current best child value and the next child to visit. Once a child subtree has been evaluated, its internal search nodes can be discarded; a final value cannot usually be computed from a nonterminal frontier alone.
With branching factor and horizon , breadth-first search stores a frontier of order . A depth-first search implementation storing sibling lists uses space complexity; lazy successor generation can reduce this to frames plus game-state storage. Both unpruned traversals have time complexity for . Depth-first search also supplies a completed sibling outcome early, allowing alpha-beta pruning to reject later subtrees before generating them.
For a finite constraint satisfaction problem with variables, all complete assignments have depth when each step assigns one new variable. Thus the shallowest-solution property of breadth-first search offers no advantage: every complete solution is already at that depth.
Depth-first search with backtracking stores a path and pending choices, using auxiliary space with ordinary explicit choice bookkeeping, or when each level stores only a current value index. Breadth-first search may store an exponentially large frontier, up to partial assignments. Depth-first search can also combine naturally with immediate constraint propagation and undoing changes. The finite depth avoids the infinite-branch obstruction of unrestricted depth-first tree search. Neither traversal removes the possible exponential time complexity.
Past exam of the mathematics course of the University of Cambridge 2007 ia Paper 5 9 b ii Solution Created 2026-09-24 Updated 2026-10-14
Use recursive dependency evaluation with memoization. After an edit, invalidate all cell states. To evaluate one formula, recursively obtain its dependencies before storing its own result. This is a depth-first search of the dependency graph; a visiting mark identifies a cycle, and a done mark avoids repeated evaluation of a shared dependency. The following single class implements the design, with public nested interfaces for formula extensions:The outer loop's order need not be a topological ordering, since recursion evaluates dependencies first. After an edit, callers can invoke
public final class Spreadsheet {
public enum Kind { EMPTY, LABEL, NUMBER, FORMULA }
public interface Lookup {
double valueAt(int row, int col);
}
public interface Formula {
double evaluate(Lookup values, int row, int col);
}
public static final class AddFormula implements Formula {
private final int dr1, dc1, dr2, dc2;
public AddFormula(int dr1, int dc1, int dr2, int dc2) {
this.dr1 = dr1; this.dc1 = dc1;
this.dr2 = dr2; this.dc2 = dc2;
}
public double evaluate(Lookup values, int r, int c) {
return values.valueAt(r + dr1, c + dc1)
+ values.valueAt(r + dr2, c + dc2);
}
}
private static final class Cell {
private final Kind kind;
private final String label;
private final double number;
private final Formula formula;
private Cell(Kind k, String s, double n, Formula f) {
kind = k; label = s; number = n; formula = f;
}
}
private static final Cell EMPTY =
new Cell(Kind.EMPTY, null, 0.0, null);
private final int rows, cols;
private final Cell[][] cells;
private final double[][] cache;
private final byte[][] state;
private final Lookup view = new Lookup() {
public double valueAt(int r, int c) {
return Spreadsheet.this.valueAt(r, c);
}
};
public Spreadsheet(int rows, int cols) {
if (rows < 0 || cols < 0) throw new IllegalArgumentException();
this.rows = rows; this.cols = cols;
cells = new Cell[rows][cols];
cache = new double[rows][cols];
state = new byte[rows][cols];
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++) cells[r][c] = EMPTY;
}
private boolean inside(int r, int c) {
return r >= 0 && r < rows && c >= 0 && c < cols;
}
private void check(int r, int c) {
if (!inside(r, c)) throw new IndexOutOfBoundsException();
}
private void invalidate() {
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++) state[r][c] = 0;
}
private void put(int r, int c, Cell cell) {
check(r, c); cells[r][c] = cell; invalidate();
}
public void setEmpty(int r, int c) { put(r, c, EMPTY); }
public void setLabel(int r, int c, String text) {
if (text == null) throw new IllegalArgumentException();
put(r, c, new Cell(Kind.LABEL, text, 0.0, null));
}
public void setNumber(int r, int c, double x) {
put(r, c, new Cell(Kind.NUMBER, null, x, null));
}
public void setFormula(int r, int c, Formula f) {
if (f == null) throw new IllegalArgumentException();
put(r, c, new Cell(Kind.FORMULA, null, 0.0, f));
}
public Kind kindAt(int r, int c) {
check(r, c); return cells[r][c].kind;
}
public String labelAt(int r, int c) {
check(r, c); return cells[r][c].label;
}
public double valueAt(int r, int c) {
if (!inside(r, c)) return 0.0;
if (state[r][c] == 2) return cache[r][c];
if (state[r][c] == 1)
throw new IllegalStateException("Cyclic formula dependency");
state[r][c] = 1;
try {
Cell cell = cells[r][c];
double answer;
switch (cell.kind) {
case NUMBER: answer = cell.number; break;
case FORMULA: answer = cell.formula.evaluate(view, r, c); break;
default: answer = 0.0;
}
cache[r][c] = answer;
state[r][c] = 2;
return answer;
} catch (RuntimeException error) {
state[r][c] = 0;
throw error;
}
}
public void recalculate() {
invalidate();
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++) valueAt(r, c);
}
}recalculate; ordinary valueAt calls also calculate lazily from the invalidated state. Empty and label cells contribute zero without destroying their display content. Relative references are interpreted at the formula's location, and off-grid references contribute zero as required.For an acyclic graph, mathematical induction over a topological ordering proves that each cached value equals the evaluation of current cell contents. Each cell becomes done at most once in a pass, so the time complexity is for cells and dependency references, plus formula arithmetic. The stack depth is at most the longest dependency path. A more elaborate implementation can record reverse dependencies and invalidate only cells affected by an edit.
The PDF gives no policy for circular formulas. Repeated numerical sweeps are not a general solution: a cell defined as itself plus one has no finite value, while a cell defined as itself plus zero has no unique value. The implementation therefore reports a cycle instead of looping or presenting an arbitrary stale value. An acyclic dependency graph is evaluated once per cell; circular dependencies are detected and rejected.
Use the two-pass strongly connected component algorithm. First run a complete depth-first search of , starting a new search at each unvisited vertex as necessary. Append each vertex to a list when its recursive call finishes. Next construct the graph transpose by reversing every edge. Reset all visited flags. Process the vertices in decreasing first-pass finishing order, running depth-first search in from each still-unvisited vertex. The newly reached vertices in each such search form one strongly connected component.
To prove this, contract each strongly connected component to one vertex. The resulting condensation graph is a Directed acyclic graph, because a directed graph cycle of distinct components would make them mutually reachable and hence a single component. For a component , write for its largest first-pass finishing time. If there is an edge between distinct components, then . Indeed, if the first search reaches before , it can reach , which must finish before the search in finishes. If it reaches first, cannot reach or they would be the same component, so finishes before is discovered.
Among unreported components, the one with greatest therefore has no incoming edge from another unreported component in . In the graph transpose it has no outgoing edge to any other unreported component. A second-pass search cannot escape it, while strong connectivity lets that search reach all its vertices. Earlier reported components are ignored by the visited flags. Repeating this argument provesBoth depth-first searches visit every vertex and scan every edge once, and transposition costs . Therefore the total time complexity is with adjacency lists. Storage for the transpose is , while visited flags, finishing order and the search stack use additional space. No sorting is needed: reverse the list recorded at finishing time.
Build a maximum spanning tree by the decreasing-weight version of Kruskal's algorithm. For the specified distinct endpoints , find their unique graph path in by depth-first search, retaining predecessors to reconstruct it. Return that path and its minimum edge weight .
For correctness, choose an edge of weight on . Deleting it splits into two connected components, separating from . No edge across this cut of a graph can have : replacing by would give a spanning tree of greater total weight, contradicting maximality of .
Every – graph path must cross the same cut of a graph, so it contains a crossing edge with . Therefore its minimum edge weight is at most . The tree route attains , provingThis is precisely the widest path problem. The proof allows tied, zero and negative edge weights. It depends on maximizing the total tree weight, not on a shortest-distance algorithm.
Let and . Sorting and Kruskal's algorithm take time; the tree search adds . HenceFor a connected simple graph with at least two vertices, , so the time is also . The expression remains valid when parallel edges are permitted. If the two endpoints coincide, the empty path is returned, with bottleneck under the usual empty-minimum convention; otherwise connectedness guarantees a nonempty route.
Depth-first search marks a vertex when first reached, then recursively explores each still-unvisited neighbour before returning. To cover the whole graph, restart from every vertex not reached by earlier searches:For a directed graph, use outgoing neighbours; for an undirected graph, use both incidences of each edge. Each vertex is discovered once and each stored incidence is inspected once. Thus with adjacency lists the running time is and the visited information and recursion stack use space. An adjacency matrix representation instead requires scanning possible neighbours per visited vertex, giving time.
visit(v):
visited[v] = true
for w in adjacency[v]:
if not visited[w]:
visit(w)
for v in V:
if not visited[v]:
visit(v)Augment depth-first search with a two-valued colour:Each tree edge receives opposite endpoint colours. If an inspected edge has equal colours, the preceding parity argument produces an odd cycle, so the graph is not bipartite. If the procedure succeeds, every edge has differently coloured endpoints, and the two colour classes are a bipartition. The outer loop handles disconnected graphs and isolated vertices.
paint(v, c):
colour[v] = c
for w in adjacency[v]:
if colour[w] is unset:
if not paint(w, 1-c):
return false
else if colour[w] == c:
return false
return true
for v in V:
if colour[v] is unset:
if not paint(v, 0):
return false
return trueEach vertex is coloured once and each edge incidence is checked once, giving time and extra space with adjacency lists. No separate cycle enumeration is required.
Start a depth-first search from each as-yet unlabelled vertex, propagating a fresh component number:A search follows only edges, so every vertex it labels is reachable from its root. Conversely, if a reachable vertex remained unlabelled, a path to it would contain a first edge from a visited vertex to an unvisited one; scanning that edge would visit the latter, a contradiction. Thus each search labels exactly one connected component, proving both directions of the required equivalence.
component = 0
for u in V:
cc[u] = UNLABELLED
for u in V:
if cc[u] == UNLABELLED:
visit(u, component)
component = component + 1
visit(u, label):
cc[u] = label
for v in adjacency[u]:
if cc[v] == UNLABELLED:
visit(v, label)1. Run depth-first search over all vertices of , recording their finish order.
2. Construct the graph transpose by reversing every edge.
3. Mark every vertex unvisited again. In decreasing first-pass finish order, start a transpose search whenever the next vertex is unvisited. The vertices newly reached by each search form one strongly connected component.
2. Construct the graph transpose by reversing every edge.
3. Mark every vertex unvisited again. In decreasing first-pass finish order, start a transpose search whenever the next vertex is unvisited. The vertices newly reached by each search form one strongly connected component.
The finish order prevents a second-pass search from entering a different still-unassigned component, while mutual reachability makes it visit the entire current component. With adjacency lists, each traversal and the transposition scan takes time. Thus the total worst-case time is , including isolated vertices.
