邻接表
邻接表 vector<pair<int, int>> 超详细完整版笔记
0. 基础定义
const int MAXN = 1e5 + 10;
vector<pair<int, int>> graph[MAXN];
含义:
graph:静态数组,大小为MAXN- 数组下标
u:代表起点节点编号 - 每个元素
graph[u]:动态数组 vector,存储从u出发的所有出边 - 每条边用
pair<int, int>表示:first→ 指向的终点vsecond→ 这条边的边权w
特别强调:
- 节点编号必须是合法下标,一般从
0或1开始 - 不支持负数节点,除非额外做偏移映射
1. 边的数量规则(有向/无向/重边/自环)
1.1 无向图
- 每条边
u ↔ v必须存两次:u → vv → u
- 总 pair 数量 = 2 × m
- 遍历时每条物理边会被访问两次
1.2 有向图
- 每条边
u → v只存一次 - 总 pair 数 = m
1.3 重边
- 几条重边就存入几个独立 pair
- 不会自动合并、不会自动去重
- 最短路、网络流、贪心算法中重边会影响结果
- 若需要单一边,必须手动去重
1.4 自环(u → u)
- 无向图自环:只存 1 次,不会重复添加
- 有向图自环:也只存 1 次
- 遍历时会正常遍历到自环,DFS/BFS 中可能造成死循环,需特别判断
2. 存储顺序(严格不混乱)
核心规则
- 边严格按输入顺序加入
- 每次
push_back/emplace_back加到graph[u]尾部 - 不同起点的边完全独立、互不干扰
- 无向图反向边紧跟正向边之后加入,顺序固定
示例
输入顺序:
0 1 1
1 3 2
0 3 3
0 2 2
2 3 2
实际存储结构:
graph[0]: (1,1) → (3,3) → (2,2)
graph[1]: (0,1) → (3,2)
graph[2]: (0,2) → (3,2)
graph[3]: (1,2) → (0,3) → (2,2)
3. 内存连续性(底层原理)
3.1 单个 vector 内部
- 内存连续,底层为动态数组,支持随机访问
graph[u][i],时间复杂度 O(1)
3.2 不同 vector 之间
- 内存完全不连续,
graph[0]、graph[1]分布在堆的不同区域,彼此地址无关联
3.3 整体结构
- 整体为链式离散存储,每个“链表头”内部连续,链表头之间离散
一句话总结:块内连续,块间离散,整体非连续。
4. 内存位置(栈/堆/全局区)
4.1 graph[MAXN] 数组位置
- 全局声明(main 外)→ 存放在全局静态区,安全无溢出
- 局部声明(main/函数内)→ 存放在栈上,
MAXN ≥ 1e4极易栈溢出
4.2 vector 数据位置
无论全局/局部,pair 真实数据永远在堆上!
- vector 底层由
allocator堆分配内存 - 仅 vector 小对象本体在栈/全局,数据全在堆
4.3 完整总结
- graph 数组:全局区 / 栈
- vector 本体:全局区 / 栈
- pair 边数据:堆
5. 容量限制(内存精准计算)
5.1 限制条件
- 操作系统可用内存
- 题目空间限制(常见 256MB / 512MB)
MAXN过大导致静态数组爆炸
5.2 精准内存估算
- 单个
pair<int, int>= 8 字节 - 1e6 条边 ≈ 8MB
- 1e7 条边 ≈ 80MB
5.3 算法题安全范围
- 点数 n ≤ 1e5:完全安全
- 边数 m ≤ 1e6:完全安全
- 常规题目(n ≤ 500,m ≤ 500):内存可忽略
6. 遍历方式(全覆盖无死角)
6.1 遍历 u 的出边(推荐)
for (auto &edge : graph[u]) {
int v = edge.first;
int w = edge.second;
}
- 加
&引用避免拷贝,提升效率
6.2 下标遍历(普通 for 循环)
for (int i = 0; i < graph[u].size(); i++) {
int v = graph[u][i].first;
int w = graph[u][i].second;
}
6.3 遍历整张图
for (int u = 0; u < n; u++) {
for (auto &edge : graph[u]) {
int v = edge.first;
int w = edge.second;
}
}
6.4 反向遍历
for (auto it = graph[u].rbegin(); it != graph[u].rend(); ++it) {
int v = it->first;
int w = it->second;
}
7. 常见易错点
- 无向图只存一次 → 直接变成残缺有向图
- pair 顺序写反
{w, v}→ 程序崩溃/死循环 - 节点编号越界 → 段错误(RE)
- 局部声明大数组 → 栈溢出
- 多组样例未清空 → 残留数据导致答案错误
- 误以为邻接表 O(1) 查询边 → 实际需遍历,O(deg(u))
- 自环/重边未处理 → DFS 死循环、最短路异常
size()无符号特性 → 空 vector 时size()-1变超大数,死循环
8. 邻接表 vs 邻接矩阵(体系对比)
| 特性 | 邻接表 | 邻接矩阵 |
|---|---|---|
| 空间复杂度 | O(m) | O(n²) |
| 遍历出边 | 快,仅遍历存在边 | 慢,需扫全部 n 个点 |
| 查询 u-v 边 | 慢,O(deg(u)) | O(1) |
| 适合场景 | 稀疏图(m << n²) | 稠密图(m ≈ n²) |
| 重边/自环 | 自然支持 | 需额外标记处理 |
| 修改边权 | 需遍历查找 | 直接赋值 |
9. 邻接表常用方法大全
9.1 建图
9.1.1 无向图建边
void add(int u, int v, int w) {
graph[u].emplace_back(v, w);
graph[v].emplace_back(u, w);
}
emplace_back直接原地构造,效率高于push_back
9.1.2 有向图建边
void add(int u, int v, int w) {
graph[u].emplace_back(v, w);
}
9.2 遍历操作
9.2.1 遍历出边
for (auto &e : graph[u]) {
int v = e.first;
int w = e.second;
}
9.2.2 下标随机访问
int v = graph[u][k].first;
int w = graph[u][k].second;
9.3 查询操作
9.3.1 查询出度
int out_degree = graph[u].size();
9.3.2 无向图总边数
int total = 0;
for (int u = 0; u < n; ++u)
total += graph[u].size();
int m = total / 2;
9.3.3 查询 u→v 是否存在边
bool has_edge(int u, int v) {
for (auto &e : graph[u])
if (e.first == v) return true;
return false;
}
9.4 修改操作
9.4.1 修改边权
for (auto &e : graph[u]) {
if (e.first == v) {
e.second = new_w;
break;
}
}
9.4.2 清空图
// 清空单个点的出边
graph[u].clear();
// 清空整张图
for (int u = 0; u < n; ++u)
graph[u].clear();
9.5 排序操作(最短路/贪心必备)
9.5.1 按终点 v 升序
sort(graph[u].begin(), graph[u].end());
9.5.2 按边权 w 升序
sort(graph[u].begin(), graph[u].end(), [](sslocal://flow/file_open?url=auto+%26a%2C+auto+%26b&flow_extra=eyJsaW5rX3R5cGUiOiJjb2RlX2ludGVycHJldGVyIn0=) {
return a.second < b.second;
});
9.5.3 按边权 w 降序
sort(graph[u].begin(), graph[u].end(), [](sslocal://flow/file_open?url=auto+%26a%2C+auto+%26b&flow_extra=eyJsaW5rX3R5cGUiOiJjb2RlX2ludGVycHJldGVyIn0=) {
return a.second > b.second;
});
9.6 去重操作(处理重边)
sort(graph[u].begin(), graph[u].end());
auto last = unique(graph[u].begin(), graph[u].end());
graph[u].erase(last, graph[u].end());
- 仅去除完全相同的
{v, w},不同权值同起点终点不会去重
9.7 高级操作
9.7.1 拷贝图
vector<pair<int, int>> new_graph[MAXN];
for (int u = 0; u < n; ++u)
new_graph[u] = graph[u];
9.7.2 空间优化
// 预分配空间(加速)
graph[u].reserve(1000);
// 收缩内存
graph[u].shrink_to_fit();
9.7.3 判空
if (graph[u].empty()) {
// 该点无出边
}
10. 邻接表 pair 存储·笔记
【邻接表 pair 存储·笔记】
1. 核心结构
vector<pair<int, int>> g[MAXN];
g[u] 存 u 的出边;pair{终点v, 边权w}
2. 边数规则
有向图:m 条边;无向图:2m 条边
重边:几条存几个;自环:存 1 次
3. 存储规则
按输入顺序加入;块内连续、块间离散
数据存堆,数组存全局/栈
4. 内存要点
单 pair 8 字节;1e6 边 ≈ 8MB
局部开大数组必爆栈
5. 核心遍历
for(auto &e : g[u]) 取 v = e.first、w = e.second
6. 常用操作
出度:g[u].size();查边:遍历查找
清空:clear();排序:sort + lambda
去重:sort + unique + erase
7. 致命坑避坑
无向图必存两次;pair 顺序不反
size() 无符号易错;多组数据必清空
不支持 O(1) 查边



被折叠的 条评论
为什么被折叠?



