Graph Data Structures
BGL provides three graph data structures, each suited to different assumptions
about mutability, memory, and access patterns.
When in doubt, start with adjacency_list: it covers the majority of use cases.
The other two are specialized for dense or large static graphs.
adjacency_matrix |
adjacency_list |
compressed_sparse_row_graph |
|
|---|---|---|---|
Mutability |
Fixed vertex count at construction |
Add/remove vertices and edges freely |
Immutable after construction |
Memory |
O(V²) |
O(V + E) |
O(V + E), most compact |
Best for |
Dense graphs (E ≈ V²) |
Best for most programs with sparse, dynamic graphs |
Large static graphs, read-heavy workloads |
Edge lookup |
O(1) |
O(degree) |
O(log degree) |
Neighbor iteration |
O(V): must scan the full matrix row |
O(out-degree): direct access |
O(out-degree): direct access, cache-friendly |
The adjacency_list will be the right choice for the majority of programs.
adjacency_list
The general-purpose structure. Parameterized to cover most use cases:
using Graph = boost::adjacency_list
boost::vecS, // OutEdgeList: container for out-edges per vertex
boost::vecS, // VertexList: container for the vertex set
boost::directedS, // Directed: directedS, undirectedS, or bidirectionalS
boost::no_property, // vertex data
boost::no_property, // edge data
boost::no_property // graph data
>;
Internally, adjacency_list stores the vertex set and each vertex’s out-edge list
in STL containers. The selector tags (vecS, listS, setS) control which
container is used:
| Selector | Use when | Trade-off |
|---|---|---|
|
Fast traversal, graph structure is stable |
Vertex removal invalidates all descriptors |
|
You add or remove vertices at runtime |
Stable descriptors, slightly higher memory |
|
You need to prevent duplicate edges |
Slower |
Common configurations:
// Default: fast iteration, allows parallel edges
using Graph = adjacency_list<vecS, vecS, directedS>;
// Prevent duplicate edges
using Graph = adjacency_list<setS, vecS, directedS>;
// Stable descriptors when mutating the graph
using Graph = adjacency_list<vecS, listS, directedS>;
// Undirected
using Graph = adjacency_list<vecS, vecS, undirectedS>;
// Access both in-edges and out-edges
using Graph = adjacency_list<vecS, vecS, bidirectionalS>;
Algorithms return vertex descriptors to identify vertices. Your code
will store them in vectors, maps, predecessor arrays, etc. The concrete type
of a descriptor depends on the VertexList selector: with vecS it happens
to be an integer index, but with listS it is a pointer. Never assume a descriptor is an
integer.
|
With vecS, calling remove_vertex() shifts all subsequent indices,
so every descriptor you previously stored now points to the wrong vertex.
If you plan on removing vertices, use listS.
|
adjacency_matrix
Stores edges in a V×V matrix. Edge existence checks are O(1), but memory is always O(V²) regardless of actual edge count.
using Graph = boost::adjacency_matrix
boost::directedS, // directedS = full V×V matrix
// undirectedS = lower triangle, saves 50% space
boost::no_property, // vertex data
boost::no_property, // edge data
boost::no_property // graph data
>;
Use adjacency_matrix only when your graph is dense (more than ~25% of all
possible edges present) and vertex count is bounded and known upfront.
Above 10K vertices the memory cost becomes prohibitive.
The vertex count is fixed at construction. add_vertex() is not
implemented. Parallel edges are silently rejected (the second add_edge()
returns false). bidirectionalS is not supported: use directedS.
|
Even for sparse graphs, iterating a vertex’s neighbors scans the entire
matrix row in O(V), not O(out-degree). This is the main reason to prefer
adjacency_list for sparse graphs.
|
compressed_sparse_row_graph
The most memory-efficient structure. Immutable after construction: vertices and edges cannot be added or removed. Cache-friendly layout makes traversal fast on large graphs.
using Graph = boost::compressed_sparse_row_graph
boost::directedS, // directedS or bidirectionalS
boost::no_property, // vertex data
boost::no_property, // edge data
boost::no_property, // graph data
uint32_t, // Vertex index type
uint32_t // Edge index type
>;
Use smaller integer types (uint16_t, uint32_t) to reduce memory on very
large graphs. Use bidirectionalS only if your algorithm needs to traverse
edges backward.
Build your edge list first, then construct the graph in one shot.
There is no add_edge() or remove_edge() after construction. The
constructor has several variants (edges_are_sorted, edges_are_unsorted,
construct_inplace_from_sources_and_targets). Passing unsorted edges to the
sorted constructor silently produces a wrong graph. The in-place constructor
mutates your input vectors: do not reuse them.
|
Parallel edges are allowed. Deduplicate your edge list before
construction if you need a simple graph. Only directedS and bidirectionalS
are supported (no undirectedS).
|