当前位置: 首页 > news >正文

链式前向星、vector存图


场景设定

想象你是一个社交达人,要记录你和所有朋友的关系(这就是“图”)。每个朋友是一个节点,关系是一条边。你需要快速回答:“我有哪些朋友?”(遍历邻居)。


方式1:链式前向星(固定小本本)

  • 核心思想:你买了一个固定页数的笔记本(预分配的数组)。

  • 怎么记?

    1. 给每个朋友一个专属页(head[你])。
    2. 每认识一个新朋友,就在笔记本最新空白页记录:
      • 朋友名字 (to)
      • 和你的关系 (weight,可选)
      • 关键! 还要写上“上一个记录在笔记本第几页?” (next)
    3. 更新你的专属页:写上最新这条记录的页码
  • 通俗比喻

    • 你的笔记本就是 edges[] 数组(每条边占一页)。
    • head[你] 是你名字标签贴纸指向的最新记录页码。
    • next 就像是“上一条记录在第几页?”的提示。
    • 要查所有朋友?从 head[你] 找到最新记录,然后根据 next 翻到前一页,再前一页…直到没记录了(next = -1)。

优点

  1. 省空间(内存少)
    • 笔记本只记核心信息(朋友名、关系、上条页码),没有多余开销。
    • vector 像活页夹,每页本身还有夹子、标签等额外重量(vector 的容量指针、大小等元数据)。
  2. 加朋友超快(O(1))
    • 直接写在最新空白页,更新你的标签贴纸 (head) 就行。固定操作,永不拖延。
  3. 笔记本整齐(内存连续)
    • 所有记录按页码连续存放。CPU 一次“搬”一摞记录进缓存效率高(缓存友好),但翻页查找过程不连续

缺点

  1. 笔记本页数固定(静态大小)
    • 买的时候要猜最多交多少朋友。朋友太多?本子写满了,得重买更大的本子,全部重抄一遍(重新初始化,扩容麻烦)。
  2. 绝交(删除边)超麻烦
    • 想删掉一个朋友的记录?你得顺着链条一页页找,找到后还要修改它前后记录的 next 指向,像拆掉一节火车车厢。几乎没人用链式前向星删边。
  3. 查朋友名单有点慢(遍历效率)
    • 虽然记录在物理上是连续的,但你查名单时是根据 next 跳着翻页(第 5 页 -> 第 2 页 -> 第 8 页)。翻页动作不连贯,CPU 缓存可能帮不上忙。
  4. 写起来费劲(代码复杂)
    • 你得自己维护 head 和每条记录的 next 指针。写代码时容易搞晕,调试也麻烦。

代码:

#include <bits/stdc++.h> 
using namespace std;
const int N = 1000;  // 最大节点数
const int M = 10000; // 最大边数// 定义边的结构体
struct Edge {int to;     // 这条边指向哪个节点int next;   // 同一个起点的下一条边的索引(相当于"上一条记录的页码")int w;      // 边权(可选)
} edges[M];  // 存储所有边的数组int head[N]; // head[u] 表示节点 u 的第一条边的索引(初始为 -1)
int idx = 0;    // 当前边的索引(相当于"最新空白页的页码")// 添加一条从 u 到 v 的边,权重为 w
void addEdge(int u, int v, int w) {edges[idx].to = v;       // 记录这条边指向 vedges[idx].w = w;        // 记录边权edges[idx].next = head[u]; // 新边的 next 指向 u 原来的第一条边head[u] = idx++;         // 更新 u 的第一条边为当前这条边
}// 遍历节点 u 的所有邻居
void printNeighbors(int u) {cout << "节点 " << u << " 的朋友: ";for (int i = head[u]; i != -1; i = edges[i].next) {int v = edges[i].to;int w = edges[i].w;cout << v << "(亲密度:" << w << ") ";}cout << endl;
}int main() {memset(head, -1, sizeof(head)); // 初始化 head 数组为 -1(表示空链表)// 添加边:0 -> 1 (权重 5), 0 -> 2 (权重 3), 1 -> 2 (权重 2)addEdge(0, 1, 5);addEdge(0, 2, 3);addEdge(1, 2, 2);// 打印邻居printNeighbors(0); // 输出: 节点 0 的朋友: 2(亲密度:3) 1(亲密度:5)printNeighbors(1); // 输出: 节点 1 的朋友: 2(亲密度:2)return 0;
}

关键点

  • head[u] 存储节点 u最新一条边的索引(类似链表头指针)。
  • edges[idx] 存储所有边,idx 是当前可用的边索引(类似动态分配的页数)。
  • next 指向同起点的上一条边(类似链表的前驱指针)。
  • 遍历时,从 head[u] 开始,沿着 next 走,直到 -1(类似链表遍历)。

适合谁? 朋友数量上限确定(比如竞赛题已知最大点数/边数),追求极致速度和省内存的“极客”。


方式2:Vector 邻接表(活页文件夹)

  • 核心思想:你买了一个带标签的活页文件夹。给每个朋友(包括你自己)分配一个专属分区

  • 怎么记?

    1. 想加一个新朋友“老王”?
    2. 直接找到你自己的分区
    3. 在分区里新加一页活页纸,写上“老王”和你们的关系(vector[你].push_back(Edge{老王, 关系}))。
  • 通俗比喻

    • 文件夹是 vector > graph
    • 每个分区是 graph[你]graph[朋友A]graph[朋友B]
    • 每个分区里的活页纸就是该节点的所有邻居边记录。

优点

  1. 分区独立,无限加页(动态扩容)
    • 你的分区写满了?系统自动给你换更大的分区页夹(vector 自动扩容)。虽然换的时候要复印所有旧页(复制数据),但均摊下来每次加页还是很快(均摊 O(1))。
  2. 查朋友名单超快(遍历高效)
    • 打开你自己的分区,里面的活页纸按添加顺序叠放(内存连续)。你可以一口气从头翻到尾,翻页动作流畅,CPU 缓存疯狂点赞!
  3. 用起来超简单(代码简洁)
    • 加朋友?一句 graph[你].push_back(老王)。查朋友?一个 for 循环遍历 graph[你]。逻辑清晰,不易出错。
  4. 额外功能强大(STL支持)
    • 想按关系亲密度排序朋友?直接用 sort(graph[你].begin(), graph[你].end())。想找特定朋友?可以用 find。活页夹兼容各种“办公用具”(STL算法)。

缺点

  1. 文件夹本身有点重(内存开销大)
    • 每个分区(vector)都要维护自己的“目录”(容量、大小等元数据)。朋友非常多时,比链式前向星多占 20%-50% 内存。
  2. 换分区页夹时手忙脚乱(扩容开销)
    • 虽然系统自动帮你换,但换的那一刻(扩容)要把所有旧页复印到新分区,这时添加操作会短暂变慢(虽然均摊后很快,但有波动)。
  3. 撕掉某页很麻烦(删除边低效)
    • 想删掉分区中间一页?你得把后面所有页往前挪一格(vector 删除中间元素 O(度数))。除非你总是删最后一页(pop_back)。
  4. 分区分散(内存不连续)
    • 你的分区、朋友A的分区、朋友B的分区在文件夹里可能不挨着。虽然每个分区内部连续,但访问不同节点时,CPU 可能要在文件夹里跳来跳去。

适合谁? 朋友数量不确定或经常变,追求代码好写、好读、易维护的“务实派”。绝大多数工程项目的首选。

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1000; // 最大节点数// 定义边的结构体(比链式前向星更简单,不需要 next)
struct Edge {int to; // 这条边指向哪个节点int w;  // 边权(可选)
};vector<Edge> graph[N]; // graph[u] 存储节点 u 的所有邻居// 添加一条从 u 到 v 的边,权重为 w
void addEdge(int u, int v, int w) {graph[u].push_back({v, w}); // 直接 push_back 即可
}// 遍历节点 u 的所有邻居
void printNeighbors(int u) {cout << "节点 " << u << " 的朋友: ";for (Edge e : graph[u]) {cout << e.to << "(亲密度:" << e.w << ") ";}cout << endl;
}int main() {// 添加边:0 -> 1 (权重 5), 0 -> 2 (权重 3), 1 -> 2 (权重 2)addEdge(0, 1, 5);addEdge(0, 2, 3);addEdge(1, 2, 2);// 打印邻居printNeighbors(0); // 输出: 节点 0 的朋友: 1(亲密度:5) 2(亲密度:3)printNeighbors(1); // 输出: 节点 1 的朋友: 2(亲密度:2)return 0;
}

关键点

  • graph[u] 是一个 vector,直接存储 u 的所有邻居边。
  • 添加边直接用 push_back,无需维护 next 指针。
  • 遍历时直接 for (Edge e : graph[u]),比链式前向星更直观。

总结

总的来说,两款存图方式各有优劣,具体取决于需求和喜好

http://www.dtcms.com/a/336105.html

相关文章:

  • Kafka_Broker_副本基本信息
  • 【FreeRTOS】信号量与互斥量
  • LeetCode热题100--104. 二叉树的最大深度--简单
  • 给纯小白的Python操作 PDF 笔记
  • 【牛客刷题】BM63 跳台阶:三种解法深度解析(递归/DP动态规划/记忆化搜索)
  • Baumer高防护相机如何通过YoloV8深度学习模型实现工作设备状态的检测识别(C#代码UI界面版)
  • Shell脚本-流程控制语句基本语法结构
  • Mutually aided uncertainty
  • 5G NR PDCCH之速率匹配
  • 【数据结构】堆和二叉树详解——上
  • 神经网络中的梯度概念
  • 【杂谈】-以质代量:谷歌主动学习范式重构AI训练逻辑
  • 把 AI 变成“气味翻译官”——基于微型电子鼻的低功耗 VOC 实时识别系统
  • 13、系统设计
  • 何为“低空经济”?
  • DeepSider:免费使用顶级大模型、全方位AI赋能的浏览器插件
  • gitee 流水线+docker-compose部署 nodejs服务+mysql+redis
  • Effective C++ 条款46:需要类型转换时请为模板定义非成员函数
  • tauri2项目WindowConfig配置中titleBarStyle样式区别,仅macOS有效
  • 如何在Windows系统中更改用户名(中文转英文全流程)
  • Deepseek一体机
  • 视觉图像界面设计【QT-creator高级编程 - 01】图像显如何保证跟随主窗口变化,且保留必要的设定窗口
  • MiracleVision-美图旗下AI视觉大模型
  • 2001-2024年中国冬小麦30米分辨率种植分布数据集
  • 【Luogu】每日一题——Day20. P4366 [Code+#4] 最短路 (图论)
  • SWE-bench:真实世界软件工程任务的“试金石”
  • 2025年- H97-Lc205--23.合并k个升序链表(链表、小根堆、优先队列)--Java版
  • 【Python练习】097. 编写一个函数,实现简单的版本控制工具
  • C++ 标准模板库 (^^ゞ 致敬 STL 创始人 Alexander Stepanov
  • 基于Python的旅游推荐系统 Python+Django+Vue.js