邻接表 `vector<pair<int, int>>` 超详细完整版笔记

邻接表 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 → 指向的终点 v
    • second → 这条边的边权 w

特别强调:

  • 节点编号必须是合法下标,一般从 0 或 1 开始
  • 不支持负数节点,除非额外做偏移映射

1. 边的数量规则(有向/无向/重边/自环)

1.1 无向图

  • 每条边 u ↔ v 必须存两次:
    • u → v
    • v → u
  • 总 pair 数量 = 2 × m
  • 遍历时每条物理边会被访问两次

1.2 有向图

  • 每条边 u → v 只存一次
  • 总 pair 数 = m

1.3 重边

  • 几条重边就存入几个独立 pair
  • 不会自动合并、不会自动去重
  • 最短路、网络流、贪心算法中重边会影响结果
  • 若需要单一边,必须手动去重

1.4 自环(u → u)

  • 无向图自环:只存 1 次,不会重复添加
  • 有向图自环:也只存 1 次
  • 遍历时会正常遍历到自环,DFS/BFS 中可能造成死循环,需特别判断

2. 存储顺序(严格不混乱)

核心规则

  1. 边严格按输入顺序加入
  2. 每次 push_back/emplace_back 加到 graph[u] 尾部
  3. 不同起点的边完全独立、互不干扰
  4. 无向图反向边紧跟正向边之后加入,顺序固定

示例

输入顺序:

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. 常见易错点

  1. 无向图只存一次 → 直接变成残缺有向图
  2. pair 顺序写反 {w, v} → 程序崩溃/死循环
  3. 节点编号越界 → 段错误(RE)
  4. 局部声明大数组 → 栈溢出
  5. 多组样例未清空 → 残留数据导致答案错误
  6. 误以为邻接表 O(1) 查询边 → 实际需遍历,O(deg(u))
  7. 自环/重边未处理 → DFS 死循环、最短路异常
  8. 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) 查边
上一篇: L2-001 紧急救援
下一篇: 绝版图书购书方案问题(折半枚举 / Meet-in-the-Middle)
ab123768
博客等级 码龄3年 43粉丝 72原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ab123768

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值