news 2026/8/15 9:36:22

图的基础概念操作与遍历

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
图的基础概念操作与遍历

一、图的基础概念与术语

  1. 概念:图是一种非线性数据结构,由顶点和边组成,相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,因而更为复杂。

  2. 常见类型

    1. 是否有方向:无向图与有向图

      • 在无向图中,边表示两顶点之间的“双向”连接关系
      • 在有向图中,边具有方向性,即A→B和B→A两个方向的边是相互独立的
    2. 所有顶点是否连通:连通图和非连通图

      • 对于连通图,从某个顶点出发,可以到达其余任意顶点。
      • 对于非连通图,从某个顶点出发,至少有一个顶点无法到达。
    3. 我们还可以为边添加“权重”变量,从而得到有权图

  3. 术语:

    1. 邻接:当两顶点之间存在边相连时,称这两顶点“邻接”。
    2. 路径:从顶点 A 到顶点 B 经过的边构成的序列被称为从 A 到 B 的“路径”。
    3. 度:一个顶点拥有的边数。对于有向图,入度表示有多少条边指向该顶点,出度表示有多少条边从该顶点指出。

二、图的表示

  1. 邻接矩阵:设图的顶点数量为 ,邻接矩阵使用一个 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,用0或1表示两个顶点之间是否存在边。

  1. 邻接表:邻接表(adjacency list)使用n个链表来表示图,链表节点表示顶点。第i个链表对应顶点i,其中存储了该顶点的所有邻接顶点(与该顶点相连的顶点)。

三、图的基础操作

  1. 基于邻接矩阵的实现
/* 基于邻接矩阵实现的无向图类 */ class GraphAdjMat { vector<int> vertices; vector<vector<int>> adjMat; public: /* 构造方法 */ GraphAdjMat(const vector<int> &vertices, const vector<vector<int>> &edges) { // 添加顶点 for (int val : vertices) { addVertex(val); } // 添加边 for (const vector<int> &edge : edges) { addEdge(edge[0], edge[1]); } } /* 获取顶点数量 */ int size() const { return vertices.size(); } /* 添加顶点 */ void addVertex(int val) { int n = size(); vertices.push_back(val); adjMat.emplace_back(vector<int>(n, 0)); for (vector<int> &row : adjMat) { row.push_back(0); } } /* 删除顶点 */ void removeVertex(int index) { if (index >= size()) { throw out_of_range("顶点不存在"); } vertices.erase(vertices.begin() + index); adjMat.erase(adjMat.begin() + index); for (vector<int> &row : adjMat) { row.erase(row.begin() + index); } } /* 添加边 */ // 参数 i, j 对应 vertices 元素索引 void addEdge(int i, int j) { if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw out_of_range("顶点不存在"); } adjMat[i][j] = 1; adjMat[j][i] = 1; } /* 删除边 */ void removeEdge(int i, int j) { if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { throw out_of_range("顶点不存在"); } adjMat[i][j] = 0; adjMat[j][i] = 0; } /* 打印邻接矩阵 */ void print() { cout << "顶点列表 = "; printVector(vertices); cout << "邻接矩阵 =" << endl; printVectorMatrix(adjMat); } };
  1. 基于邻接表的实现
/* 基于邻接表实现的无向图类 */ class GraphAdjList { public: unordered_map<Vertex *, vector<Vertex *>> adjList; /* 在 vector 中删除指定节点 */ void remove(vector<Vertex *> &vec, Vertex *vet) { for (int i = 0; i < vec.size(); i++) { if (vec[i] == vet) { vec.erase(vec.begin() + i); break; } } } /* 构造方法 */ GraphAdjList(const vector<vector<Vertex *>> &edges) { for (const vector<Vertex *> &edge : edges) { addVertex(edge[0]); addVertex(edge[1]); addEdge(edge[0], edge[1]); } } /* 获取顶点数量 */ int size() { return adjList.size(); } /* 添加边 */ void addEdge(Vertex *vet1, Vertex *vet2) { if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) throw invalid_argument("不存在顶点"); adjList[vet1].push_back(vet2); adjList[vet2].push_back(vet1); } /* 删除边 */ void removeEdge(Vertex *vet1, Vertex *vet2) { if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) throw invalid_argument("不存在顶点"); remove(adjList[vet1], vet2); remove(adjList[vet2], vet1); } /* 添加顶点 */ void addVertex(Vertex *vet) { if (adjList.count(vet)) return; adjList[vet] = vector<Vertex *>(); } /* 删除顶点 */ void removeVertex(Vertex *vet) { if (!adjList.count(vet)) throw invalid_argument("不存在顶点"); adjList.erase(vet); for (auto &adj : adjList) { remove(adj.second, vet); } } /* 打印邻接表 */ void print() { cout << "邻接表 =" << endl; for (auto &adj : adjList) { const auto &key = adj.first; const auto &vec = adj.second; cout << key->val << ": "; printVector(vetsToVals(vec)); } } };

四、图的遍历

  1. 广度优先搜索:广度优先遍历是一种由近及远的遍历方式,从某个节点出发,始终优先访问距离最近的顶点,并一层层向外扩张
  2. 代码实现:
vector<Vertex *> graphBFS(GraphAdjList &graph, Vertex *startVet) { vector<Vertex *> res; unordered_set<Vertex *> visited = {startVet}; queue<Vertex *> que; que.push(startVet); while (!que.empty()) { Vertex *vet = que.front(); que.pop(); res.push_back(vet); for (auto adjVet : graph.adjList[vet]) { if (visited.count(adjVet)) continue; que.push(adjVet); visited.emplace(adjVet); } } return res; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/14 18:27:31

隐式转换,强制转换,字符串,字符的加操作

一、核心概念梳理1. 隐式转换&#xff08;Implicit Conversion&#xff09;定义&#xff1a;编译器自动进行的类型转换&#xff0c;无需显式指定触发条件&#xff1a;类型兼容且不会丢失数据精度时常见场景&#xff1a;小范围类型 → 大范围类型&#xff08;如 int → double&a…

作者头像 李华
网站建设 2026/8/13 20:46:26

AutoGPT音乐创作实验:旋律生成与编曲建议实现

AutoGPT音乐创作实验&#xff1a;旋律生成与编曲建议实现 在数字音乐制作日益普及的今天&#xff0c;越来越多的内容创作者——从独立游戏开发者到短视频博主——都面临着一个共同挑战&#xff1a;如何快速、高质量地为项目配乐&#xff1f;传统方式要么依赖昂贵的专业作曲家&a…

作者头像 李华
网站建设 2026/8/14 17:11:55

49、Ubuntu 编程工具与 Mono 开发全解析

Ubuntu 编程工具与 Mono 开发全解析 在 Ubuntu 系统中进行 C、C++ 以及 Mono 开发,有许多实用的工具和方法。下面我们将详细介绍这些开发工具的使用和相关技术。 1. C/C++ 开发工具 在 Ubuntu 系统中,如果你安装了开发工具包,就可以使用 GNU C 编译器(gcc)来编译 C 程序…

作者头像 李华
网站建设 2026/8/14 21:02:06

摄影的光线运用

一、光线的概念顺光&#xff1a;光线从拍摄者照向拍摄物体&#xff0c;光线比较均匀&#xff0c;但是画面比较平淡&#xff0c;缺乏立体感。侧光&#xff1a;光线从侧面照过来&#xff0c;照片有立体感&#xff0c;有质感。逆光&#xff1a;光线从后面找过来&#xff0c;容易拍…

作者头像 李华
网站建设 2026/8/14 14:00:08

Origin科研绘图——手把手教你“分段拟合”

更多免费教程和软件 : 分段拟合 【效果图】 分段拟合是一种将数据按不同区间划分,并在每个区间分别采用适合的数学模型进行拟合的方法。它适用于数据整体趋势复杂,但在局部区间内呈现规律性(如线性、指数或多项式变化)的情况。 【为什么需要分段拟合】 在实际数据中,经…

作者头像 李华
网站建设 2026/8/15 1:24:13

MPV播放器窗口管理:5个实用场景让你告别位置烦恼

MPV播放器窗口管理&#xff1a;5个实用场景让你告别位置烦恼 【免费下载链接】mpv &#x1f3a5; Command line video player 项目地址: https://gitcode.com/GitHub_Trending/mp/mpv 还在为MPV播放器窗口总是出现在奇怪的位置而烦恼吗&#xff1f;作为一款轻量级但功能…

作者头像 李华