brain
tamnd's digital brain — notes, problems, research
43815 notes
Minimum spanning tree implementations can look correct while failing on small edge cases.
By this point, you have seen three major minimum spanning tree algorithms: ```text Kruskal Prim
Many graph algorithms repeatedly ask the same question: > Do these two vertices already belong to the same connected component?
After studying cut properties, union-find, Kruskal, Prim, Borůvka, clustering, network design, and complexity analysis, a practical question remains: > How should MST algorithms actually be implemente...
A minimum spanning tree gives the cheapest way to connect all vertices.
Sparse graphs reward algorithms that touch only existing edges.
Minimum spanning tree code often fails for reasons that are easy to miss in clean textbook examples.
Throughout this chapter, we have studied minimum spanning trees from multiple perspectives: * Graph theory * Greedy algorithms * Union-find
Kruskal's algorithm grows a forest by processing edges globally.
Offline connectivity works well when edges are fixed, or when all updates can be processed in a convenient order.
Minimum spanning tree algorithms are usually described in terms of `V` vertices and `E` edges.
Kruskal's algorithm builds a minimum spanning tree by selecting edges globally.
Minimum spanning trees minimize the **total weight** of selected edges.
The cut property identifies edges that can safely be added to a minimum spanning tree.
Path compression makes existing trees flatter, but it does not prevent bad trees from being created.
Many real-world optimization problems can be modeled as graphs.
Given a connected weighted undirected graph, find a spanning tree whose total edge weight is minimum.
Minimum spanning tree algorithms are short.
You have a set of objects and pairwise distances between them.
The basic union-find structure can become highly unbalanced.
Suppose you are given a graph and thousands or millions of connectivity queries: ```text Are vertices u and v connected?
Minimum spanning tree algorithms repeatedly make local decisions.
Minimum spanning tree algorithms become useful only after you can recognize the pattern in unfamiliar problems.
The classical MST algorithms were designed for a single machine processing a graph stored in local memory.
You are given a connected undirected graph.
Use BFS when every edge has the same cost.
Breadth-first search (BFS) is the simplest shortest path algorithm.
Shortest-path implementations are prone to subtle errors.
In the previous recipe, Bellman-Ford was used to compute shortest paths in graphs that contain negative edge weights.
The algorithms covered so far solve the single-source shortest path problem.
Shortest-path algorithms are easy to implement incorrectly.
Many shortest-path algorithms begin at the source and gradually expand outward until the target is reached.
Negative edge weights complicate shortest-path algorithms.
Every shortest-path algorithm in this chapter appears different on the surface.
The previous recipe used Dijkstra’s algorithm with a binary heap and lazy deletion.
This chapter introduced a substantial collection of shortest-path algorithms.
Many shortest-path problems are not given as explicit graph structures.
Shortest-path algorithms are often chosen by correctness first, then by complexity.
Most shortest-path problems begin with a single source vertex.
Breadth-first search works because every edge contributes the same cost.
Dijkstra's algorithm depends on a crucial assumption: edge weights must be nonnegative.
The shortest-path algorithms studied so far naturally divide into two groups.
One of the most powerful ideas in algorithm design is that many problems that do not look like graph problems can be transformed into shortest-path problems.
Many shortest-path algorithms are designed to handle arbitrary graphs.
Between ordinary BFS and Dijkstra lies an interesting class of shortest-path problems.
Shortest-path code usually fails in predictable ways.
Learning shortest-path algorithms is only the first step.
Most shortest-path algorithms compute distances.
Most shortest-path algorithms answer a single question: > What is the shortest path from the source to the destination?
Dijkstra's algorithm finds shortest paths by expanding vertices in order of increasing distance from the source.
You need a graph representation that supports efficient traversal, scales to large datasets, and works naturally with algorithms such as depth-first search, breadth-first search, topological sorting,...
You need to arrange tasks in an order that respects dependencies.
You need basic measurements that describe the local and global structure of a graph.
You need to assign labels, colors, or resources to vertices so that adjacent vertices do not conflict.
You need to find the cheapest route between vertices.
You need to identify independent regions within a graph.
You need to model relationships where connections have different costs.
You need to turn a real problem into a graph before choosing an algorithm.
You need to traverse every edge in a graph exactly once.
You need to explore a graph in order of increasing distance from a starting vertex.
You are given a graph problem and need to quickly identify the correct algorithmic approach.
You need to connect all vertices in a graph while minimizing total cost.
You need confidence that a graph algorithm handles real inputs, edge cases, and malformed assumptions correctly.
You need to visit every vertex exactly once.
You need to divide vertices into two groups such that every edge connects vertices from different groups.
You need to choose the right in-memory representation for a graph.
You need to identify groups of vertices that are mutually reachable.
You need to find vertices whose removal disconnects an undirected graph.
You need to model relationships that are naturally symmetric.
You need to explore a graph systematically.
You need to find edges whose removal disconnects a graph.
You need a graph representation that is simple to construct, compact to store, and efficient for algorithms that process edges directly.
You need a graph representation that can answer edge-existence queries quickly.
You need to model relationships that have direction.
You need to determine whether a graph contains a cycle.
Many tree algorithms process information from a fixed root.
Most tree algorithms are recursive, not because recursion is elegant, but because trees are recursive objects.
Segment Trees provide efficient range queries and point updates.
Not every range-query problem requires the full power of a Segment Tree.
All previous tree algorithms in this chapter assume that the tree structure is fixed.
The diameter of a tree is the length of the longest path between any two nodes.
Most tree representations focus on speed.
This chapter has introduced a wide range of tree algorithms and data structures.
Suppose two computers each store a copy of a large dataset.
Many tree problems ask questions about an entire subtree rather than an individual node.
Many tree algorithms repeatedly ask the same question: > What is the ancestor of this node k levels above?
The Lowest Common Ancestor (LCA) of two nodes is the deepest node that is an ancestor of both.
Breadth-first search (BFS) visits a tree level by level.
Depth-first search (DFS) is the fundamental traversal technique for trees.
Expression trees represent computations as tree structures.
Two trees may look different at first glance yet represent exactly the same structure.
Most data structures answer questions about the present.
Many tree algorithms eventually become range-query problems.
Many tree problems become dramatically easier when you stop thinking of the tree as a tree.
Many search problems involve prefixes.
Many tree problems ask questions about paths.
A tree in memory is not directly portable.
Trees are usually introduced with drawings: circles connected by lines, one circle at the top, several below it, then more below those.
Tree height measures how far a tree extends downward from a node.
Many tree algorithms compute information relative to a fixed root.