最常用做法是vector<vector<pair<int, int>>> graph,每个graph[u]存起点u的所有{v, w}出边;优先用结构体封装边以提升可维护性与扩展性。

用 vector 存邻接表最常用,每个节点配 vector<pair double>></pair>
权重有向图的核心是:对每个起点 u,存它所有出边的终点 v 和对应权重 w。C++ 标准做法是用 vector 套 pair:vector<vector double>>></vector>,其中 pair.first 是终点编号,pair.second 是边权(可按需用 int、float 或自定义类型)。
常见错误是把权重存在单独数组里、靠下标对齐——容易错位,尤其删点或动态增边时;也有人误用 map<int double></int> 导致常数过大,其实绝大多数场景 vector 更快更稳。
- 初始化:假设
n个节点(编号 0 到 n-1),先graph.resize(n) - 加边
u → v权重w:直接graph[u].push_back({v, w}) - 遍历
u的所有出边:for (auto [v, w] : graph[u]) { ... }(C++17 结构化绑定) - 若需频繁查某条边是否存在(如判重),
vector不支持 O(1) 查询,此时得换vector<map double>></map>,但空间和时间开销明显上升
用 struct Edge 封装边更清晰,尤其权重字段多或带元信息
当权重不只是一个数字(比如要存距离+耗时+可靠性),或后续可能扩展边属性(如是否被删除、来源ID),裸 pair 会很快变得难维护。这时定义结构体更稳妥:
struct Edge {
int to;
double weight;
int tag; // 可选:标记类型、批次等
};
vector<vector<Edge>> graph;
优势在于语义明确、易扩展、支持构造函数和成员函数(比如 Edge::is_valid())。缺点是比 pair 略重一点内存,但现代编译器优化后差异极小。
立即学习“C++免费学习笔记(深入)”;
- 加边写成
graph[u].emplace_back(v, w, tag),避免临时对象拷贝 - 若边权类型复杂(如自定义浮点精度类),
struct能显式控制赋值/比较逻辑,pair容易隐式转换出错 - 调试时打印
Edge比打印pair更易读,IDE 也能更好识别字段名
unordered_map 做稀疏图存储?小心哈希开销和迭代顺序
节点编号极大但实际边极少(比如社交网络中 ID 是 long long,但每人只连几十人),用 vector<vector>></vector> 会浪费大量内存。这时可用 unordered_map<int vector double>>></int>,键是起点编号,值是它的出边列表。
但要注意:每次访问 graph[u] 都触发哈希查找,比数组下标访问慢一个数量级;且 unordered_map 迭代顺序不确定,对需要稳定遍历顺序的算法(如某些拓扑排序变种)可能出问题。
- 必须提前
graph.reserve(expected_node_count),否则频繁 rehash 拖慢性能 - 若起点编号是连续小整数(比如 0~10000),哪怕稀疏,
vector仍是首选——现代 CPU 缓存友好性远胜哈希表 - 别用
map替代unordered_map,红黑树的 logN 查找在图遍历中叠加后很可观
读入边时忘记方向?权重类型不匹配导致精度丢失
有向图最常踩的坑是读边时把 u v w 当成无向边双向添加,或者 scanf/cin 读入顺序和变量类型没对齐。例如权重是 double 却用 %d 读,或 cin >> u >> v >> w 但 w 是 int 而文件里是小数,结果全截断为 0。
- 务必确认输入格式:是
u v w还是v u w?有向边u→v对应的是graph[u].push_back({v, w}),不是反过来 - 权重类型统一用
double,除非确定整数且范围可控;避免混用float(精度不足)和long double(ABI 兼容性差) - 调试时加一句
assert(graph[u].back().first == v),能早发现读入错位
vector<vector double>>></vector> 就够了。真正复杂的图结构需求(比如动态删边、多权重、并发访问),往往该考虑用现成图库(如 Boost.Graph),而不是自己硬套容器。


















