boost中graph_traits和adjacency_list 的内存布局以及最小图示例

一、 graph_traits 的完整展开

graph_traits<Graph> = 图对算法暴露的"最小公共接口"

算法只认识 traits,不认识 adjacency_list


1 graph_traits 是什么?

cpp 复制代码
template <class Graph>
struct graph_traits;

针对每一种图类型(如 adjacency_list),Boost 特化graph_traits


2 graph_traits 提供了什么?

以常用配置为例:

cpp 复制代码
using Graph = boost::adjacency_list<
    boost::vecS,
    boost::vecS,
    boost::directedS,
    VertexProperty,
    EdgeProperty
>;

2.1 核心类型(算法最关心的)

cpp 复制代码
using Traits = boost::graph_traits<Graph>;
类型 含义
Traits::vertex_descriptor 顶点句柄
Traits::edge_descriptor 边句柄
Traits::vertices_size_type 顶点数类型
Traits::edges_size_type 边数类型
Traits::degree_size_type 度数类型

2.2 iterator 类型(算法遍历依赖)

cpp 复制代码
Traits::vertex_iterator
Traits::edge_iterator
Traits::out_edge_iterator
Traits::adjacency_iterator

算法写法:

cpp 复制代码
Traits::vertex_iterator vi, vi_end;
for (tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi)

算法完全不管 iterator 背后是 vector 还是 list


2.3 图方向能力(编译期)

cpp 复制代码
Traits::directed_category
Traits::edge_parallel_category
Traits::traversal_category

例如:

cpp 复制代码
using Cat = Traits::directed_category;
static_assert(std::is_same_v<Cat, boost::directed_tag>);

算法在编译期就能裁剪路径


3 descriptor 本质是什么

当使用时:

cpp 复制代码
VertexListS = vecS
OutEdgeListS = vecS
实际类型
cpp 复制代码
vertex_descriptor = std::size_t
edge_descriptor   = detail::edge_desc_impl

顶点 = 索引
边 = (source, index into out-edge vector)

这就是为什么 vecS 性能最好


4 graph_traits 的核心思想

Traits 把"图的实现细节"全部屏蔽掉,只留下算法需要的抽象能力。


二、 adjacency_list 的内存布局示意

下面用最常用的配置来讲:

cpp 复制代码
adjacency_list<
    vecS,       // OutEdgeList
    vecS,       // VertexList
    directedS,
    VertexProp,
    EdgeProp
>

1 整体内存结构

text 复制代码
Graph
│
├── vector<Vertex> vertices_
│       │
│       ├── Vertex 0
│       │     ├── VertexProperty
│       │     └── vector<Edge> out_edges
│       │             ├── Edge 0
│       │             │     ├── target vertex
│       │             │     └── EdgeProperty
│       │             └── Edge 1
│       │
│       ├── Vertex 1
│       │     └── vector<Edge> out_edges
│       │
│       └── Vertex N

没有"Edge 对象池"
边是嵌在源顶点里的


2 Vertex 的真实结构(简化版)

cpp 复制代码
struct Vertex {
    VertexProperty property;
    std::vector<Edge> out_edges;
};

3 Edge 的真实结构(简化版)

cpp 复制代码
struct Edge {
    std::size_t target;   // 目标顶点 index
    EdgeProperty property;
};

edge_descriptor 并不等于 Edge *

它是:

text 复制代码
(vertex_index, edge_index)

4 edge_descriptor 如何定位一条边?

text 复制代码
edge_descriptor e:
    e.source = u
    e.idx    = k

访问流程:

cpp 复制代码
auto& edge = vertices_[u].out_edges[k];

O(1)


5 为什么 reverse_edge 必须显式存?

因为内存结构是:

text 复制代码
u.out_edges  → v
v.out_edges  → u   (如果存在)

Boost 不假设对称性

所以:

cpp 复制代码
edge_reverse_t : edge_descriptor

必须由使用者维护。


6 adjacency_list 的三种常见布局对比

VertexList OutEdgeList 内存特征
vecS vecS 最紧凑、cache-friendly
vecS listS 出边插删稳定
listS listS 高度动态,但慢

SLAM / 流 / 最短路 → vecS + vecS


7 adjacency_list vs adjacency_matrix

adjacency_list adjacency_matrix
内存 O(V + E) O(V²)
稀疏图
遍历邻接
动态修改

8 把 traits + 内存模型连起来

text 复制代码
算法
 │
 │ uses
 ▼
graph_traits
 │
 │ returns descriptor
 ▼
adjacency_list 内部结构

Traits 是"接口",adjacency_list 是"实现"


九、总结

Boost.Graph = traits 定义能力,property_map 承载数据,adjacency_list 只是其中一种实现。


三、 自己实现一个「最小 Graph」

目标:不使用 adjacency_list,但能直接喂给 BFS / DFS / Dijkstra


1 Boost.Graph 对 Graph 的"最低要求"

Boost.Graph 并不要求继承任何基类,只要求满足:

三个维度的"概念(Concept)"

  1. graph_traits
  2. 自由函数接口
  3. property_map(可选)

2 一个最小可用 Graph 的功能目录

最小有向图 + BFS 为例:

必须提供:

能力 函数
顶点遍历 vertices(g)
邻接遍历 out_edges(v, g)
边终点 target(e, g)
顶点数 num_vertices(g)
traits graph_traits<Graph>

3 Step 1:定义 Graph 内部结构(最小)

cpp 复制代码
struct SimpleGraph {
    using Vertex = std::size_t;

    std::vector<std::vector<Vertex>> adj; // 邻接表
};

4 Step 2:graph_traits 特化(核心)

cpp 复制代码
namespace boost {

template <>
struct graph_traits<SimpleGraph> {
    using vertex_descriptor = std::size_t;
    using edge_descriptor   = std::pair<std::size_t, std::size_t>;

    using vertex_iterator = counting_iterator<std::size_t>;
    using out_edge_iterator = counting_iterator<std::size_t>;

    using directed_category = directed_tag;
    using edge_parallel_category = allow_parallel_edge_tag;
    using traversal_category = incidence_graph_tag;
};

}

注意

Boost.Graph 的算法只看 traits


5 Step 3:实现自由函数接口

顶点遍历

cpp 复制代码
std::pair<
    boost::counting_iterator<std::size_t>,
    boost::counting_iterator<std::size_t>
>
vertices(const SimpleGraph& g) {
    return {0, g.adj.size()};
}

出边遍历

cpp 复制代码
std::pair<
    boost::counting_iterator<std::size_t>,
    boost::counting_iterator<std::size_t>
>
out_edges(std::size_t v, const SimpleGraph& g) {
    return {0, g.adj[v].size()};
}

target()

cpp 复制代码
std::size_t target(
    std::size_t edge_idx,
    const SimpleGraph& g,
    std::size_t v
) {
    return g.adj[v][edge_idx];
}

6 现在可以直接跑 BFS

cpp 复制代码
SimpleGraph g;
g.adj = {{1,2}, {2}, {}};

boost::breadth_first_search(g, 0,
    boost::visitor(boost::make_bfs_visitor(
        boost::null_visitor()
    ))
);

完全不依赖 adjacency_list


相关推荐
微风中的麦穗2 小时前
K8s(kubernetes)部署Mivus向量数据库集群 在线和离线两种部署方式
数据库
Go高并发架构_王工2 小时前
Redis命令执行原理与源码分析:深入理解内部机制
数据库·redis·后端
佛系DBA2 小时前
数据库性能之旅(四)关于NULL值
数据库·postgresql
学习3人组2 小时前
Conda虚拟环境迁移指南导出依赖库并跨设备重建环境
java·数据库·conda
hgz07102 小时前
MySQL索引数据结构:B+树 vs 哈希索
数据库·sql·mysql
GISERLiu2 小时前
Mapper 怎么能找到实体和数据库
数据库·oracle·mybatis
自信150413057592 小时前
数据结构初阶之单链表
c语言·数据结构
技术不打烊2 小时前
MySQL锁机制全解:彻底理解行锁、表锁与死锁原理
数据库·mysql
报错小能手2 小时前
数据结构 AVL树讲解
数据结构·算法