news 2026/7/6 13:03:03

数据结构——图:存储结构、DFS、BFS、最小生成树

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据结构——图:存储结构、DFS、BFS、最小生成树

图是数据结构中最后一类重要的非线性结构。前面的链表是一对一,树是一对多,图是多对多。图的遍历(DFS/BFS)和最小生成树是 408 考研的重点。

一、图的基本概念

1. 定义

图由**顶点(Vertex)边(Edge)**组成。

无向图: A —— B | | C —— D 有向图(带箭头): A → B ↓ ↓ C ← D

术语:

  • 顶点:图中的节点
  • :顶点之间的连线
  • 无向图:边没有方向,(A, B) = (B, A)
  • 有向图:边有方向,<A, B> ≠ <B, A>
  • 完全图:任意两个顶点之间都有边
  • :边上的数值(如距离、花费)
  • 连通图:任意两个顶点之间都有路径可达

二、图的存储结构

1. 邻接矩阵

// 用一个二维数组存储顶点之间的邻接关系// graph[i][j] = 1 表示顶点 i 到 j 有边// graph[i][j] = 0 表示没有边publicclassGraphMatrix{privateint[][]matrix;// 邻接矩阵privateintn;// 顶点数privatebooleandirected;// 是否有向publicGraphMatrix(intn,booleandirected){this.n=n;this.directed=directed;matrix=newint[n][n];}// 添加边publicvoidaddEdge(inti,intj,intweight){matrix[i][j]=weight;// i → jif(!directed){matrix[j][i]=weight;// 无向图对称}}// 判断是否有边publicbooleanhasEdge(inti,intj){returnmatrix[i][j]!=0;}}
无向图的邻接矩阵(对称): A B C D A [0, 1, 1, 0] B [1, 0, 0, 1] C [1, 0, 0, 1] D [0, 1, 1, 0]

优点:判断两点是否有边 O(1),实现简单
缺点:占用空间 O(n²),稀疏图浪费空间

2. 邻接表

// 每个顶点维护一个链表,存储与其相连的顶点publicclassGraphList{privateList<Integer>[]adj;// 邻接表数组privateintn;publicGraphList(intn){this.n=n;adj=newList[n];for(inti=0;i<n;i++){adj[i]=newArrayList<>();}}publicvoidaddEdge(inti,intj){adj[i].add(j);adj[j].add(i);// 无向图}publicList<Integer>getNeighbors(intv){returnadj[v];}}
无向图的邻接表: A → [B, C] B → [A, D] C → [A, D] D → [B, C]

优点:节省空间 O(n + e),适合稀疏图
缺点:判断两点是否有边需要遍历链表 O(degree)

3. 邻接矩阵 vs 邻接表

对比邻接矩阵邻接表
空间O(n²)O(n + e)
判边O(1) 🏆O(度)
遍历邻居O(n)O(度) 🏆
适合稠密图稀疏图(常用)🏆

三、图的遍历

1. 深度优先搜索(DFS)

沿着一条路走到黑,走不通了再回头。

publicclassGraphDFS{privateList<Integer>[]adj;privateboolean[]visited;publicvoiddfs(intstart){visited=newboolean[adj.length];dfsRecursive(start);}privatevoiddfsRecursive(intv){visited[v]=true;System.out.print(v+" ");// 访问当前顶点for(intneighbor:adj[v]){if(!visited[neighbor]){dfsRecursive(neighbor);// 递归访问未访问的邻居}}}}
从 A 开始 DFS(假设邻接顺序从小到大): A → B → D → C 路径:A → B → D → C 访问顺序:A, B, D, C

时间复杂度:O(n + e)
空间复杂度:O(n)(递归栈)

2. 广度优先搜索(BFS)

一层一层往外扩,像水的波纹。

publicclassGraphBFS{privateList<Integer>[]adj;privateboolean[]visited;publicvoidbfs(intstart){visited=newboolean[adj.length];Queue<Integer>queue=newLinkedList<>();visited[start]=true;queue.offer(start);while(!queue.isEmpty()){intv=queue.poll();System.out.print(v+" ");// 访问当前顶点for(intneighbor:adj[v]){if(!visited[neighbor]){visited[neighbor]=true;queue.offer(neighbor);// 未访问的邻居入队}}}}}
从 A 开始 BFS: 第一层:A 第二层:B, C 第三层:D 访问顺序:A, B, C, D

时间复杂度:O(n + e)
空间复杂度:O(n)(队列)

3. DFS vs BFS

对比DFSBFS
数据结构栈(递归)队列
遍历顺序沿着路径深入逐层扩展
适用场景连通性判断、拓扑排序最短路径、层序遍历
实现难度递归,代码简洁循环,稍复杂

四、最小生成树

用最小的总权值把所有顶点连通。

1. Prim 算法

从一个顶点开始,每次找权值最小的边加入。

publicclassPrim{/** * Prim 算法求最小生成树 * @param graph 邻接矩阵(权值,0表示无边) * @return 最小总权值 */publicstaticintprim(int[][]graph){intn=graph.length;int[]lowCost=newint[n];// 到各顶点的最小权值boolean[]visited=newboolean[n];inttotal=0;// 从顶点0开始visited[0]=true;for(inti=0;i<n;i++){lowCost[i]=graph[0][i]==0?Integer.MAX_VALUE:graph[0][i];}for(inti=1;i<n;i++){// 找权值最小的未访问顶点intmin=Integer.MAX_VALUE;intminIdx=-1;for(intj=0;j<n;j++){if(!visited[j]&&lowCost[j]<min){min=lowCost[j];minIdx=j;}}if(minIdx==-1)break;// 不连通visited[minIdx]=true;total+=min;// 更新 lowCostfor(intj=0;j<n;j++){if(!visited[j]&&graph[minIdx][j]!=0&&graph[minIdx][j]<lowCost[j]){lowCost[j]=graph[minIdx][j];}}}returntotal;}}

2. Kruskal 算法

每次选权值最小的边,只要不形成环就加入。

publicclassKruskal{// 边的定义staticclassEdgeimplementsComparable<Edge>{intu,v,weight;publicintcompareTo(Edgeo){returnthis.weight-o.weight;}}// 并查集staticclassUnionFind{int[]parent;UnionFind(intn){parent=newint[n];for(inti=0;i<n;i++)parent[i]=i;}intfind(intx){returnparent[x]==x?x:(parent[x]=find(parent[x]));}booleanunion(intx,inty){intrx=find(x),ry=find(y);if(rx==ry)returnfalse;parent[rx]=ry;returntrue;}}publicstaticintkruskal(Edge[]edges,intn){Arrays.sort(edges);// 按权值排序UnionFinduf=newUnionFind(n);inttotal=0,count=0;for(Edgee:edges){if(uf.union(e.u,e.v)){// 不会形成环total+=e.weight;count++;if(count==n-1)break;// n个顶点需要n-1条边}}returntotal;}}

3. Prim vs Kruskal

对比PrimKruskal
策略选顶点选边
数据结构邻接矩阵边集 + 并查集
时间复杂度O(n²)O(e log e)
适合稠密图🏆稀疏图🏆

五、408 考研经典考题

题1:邻接矩阵转邻接表

List<Integer>[]matrixToList(int[][]matrix){intn=matrix.length;List<Integer>[]adj=newList[n];for(inti=0;i<n;i++){adj[i]=newArrayList<>();for(intj=0;j<n;j++){if(matrix[i][j]==1){adj[i].add(j);}}}returnadj;}

题2:判断图是否连通

booleanisConnected(List<Integer>[]adj){intn=adj.length;boolean[]visited=newboolean[n];dfs(adj,visited,0);for(booleanv:visited){if(!v)returnfalse;}returntrue;}

题3:拓扑排序

有向无环图(DAG)的顶点线性排序,AOV 网中活动执行的先后顺序。

// 核心思想:不断删除入度为 0 的顶点List<Integer>topologicalSort(List<Integer>[]adj){intn=adj.length;int[]inDegree=newint[n];for(inti=0;i<n;i++){for(intv:adj[i])inDegree[v]++;}Queue<Integer>queue=newLinkedList<>();for(inti=0;i<n;i++){if(inDegree[i]==0)queue.offer(i);}List<Integer>result=newArrayList<>();while(!queue.isEmpty()){intv=queue.poll();result.add(v);for(intneighbor:adj[v]){if(--inDegree[neighbor]==0){queue.offer(neighbor);}}}returnresult;}

六、图的常见考点

1. 有 n 个顶点的无向完全图有 n(n-1)/2 条边 2. 有 n 个顶点的有向完全图有 n(n-1) 条边 3. 连通图至少需要 n-1 条边(树) 4. 图的邻接矩阵是对称矩阵(无向图) DFS/BFS 时间复杂度的写法: 邻接矩阵:O(n²) 邻接表:O(n + e)

💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/6 12:54:01

PyTorch 图像识别数据工程:3步自动化爬取与清洗,构建专属数据集

PyTorch 图像识别数据工程&#xff1a;3步自动化构建高质量数据集在计算机视觉项目中&#xff0c;数据质量往往比模型架构更能决定最终性能。本文将分享一套经过实战检验的PyTorch数据工程方案&#xff0c;通过自动化流程解决图像爬取、清洗和增强三大核心问题。1. 智能爬虫系统…

作者头像 李华
网站建设 2026/7/6 12:53:30

OpenCV CLAHE 实战:3个关键参数调优与医学影像增强效果对比

OpenCV CLAHE 实战&#xff1a;3个关键参数调优与医学影像增强效果对比医学影像分析中&#xff0c;图像质量直接影响诊断准确性。一张对比度不足的X光片可能掩盖早期病灶&#xff0c;而过度增强的CT图像又会引入伪影干扰判断。传统直方图均衡化&#xff08;HE&#xff09;如同用…

作者头像 李华
网站建设 2026/7/6 12:52:42

DBNet 文本检测实战:基于PaddleOCR 2.7 从训练到部署的5个关键步骤

DBNet文本检测实战&#xff1a;基于PaddleOCR 2.7的工业级落地指南当OCR技术遇上复杂多变的现实场景&#xff0c;传统文本检测方法往往在弯曲文本、密集排列或低对比度环境下表现乏力。DBNet&#xff08;Differentiable Binarization&#xff09;通过将二值化过程融入神经网络架…

作者头像 李华