This documentation is being rewritten. If something looks off, please cross-check with the Boost 1.91.0 Boost.Graph docs and open an issue.

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

vecS

Fast traversal, graph structure is stable

Vertex removal invalidates all descriptors

listS

You add or remove vertices at runtime

Stable descriptors, slightly higher memory

setS

You need to prevent duplicate edges

Slower add_edge, faster remove_edge

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).