图是一种非常重要的数据结构,在计算机科学的众多领域都有广泛应用。以下是对图的详细介绍:
图的基本概念
- 定义:图是由顶点(Vertex)的有穷非空集合和顶点之间边(Edge)的集合组成,通常表示为 (G(V, E)),其中 (G) 表示一个图,(V) 是图 (G) 中顶点的集合,(E) 是图 (G) 中边的集合。
- 顶点(Vertex):图中的数据元素,也称为节点。例如,在社交网络中,每个人可以看作是一个顶点。
- 边(Edge):连接两个顶点的线段,可以是有向的也可以是无向的。在有向图中,边具有方向性,如从顶点 (A) 指向顶点 (B);在无向图中,边没有方向性,表示顶点 (A) 和顶点 (B) 之间存在连接关系。
- 无向图:图中的每条边都是无向边。例如,城市之间的道路网络可以看作是一个无向图,因为从城市 (A) 到城市 (B) 的道路和从城市 (B) 到城市 (A) 的道路是相同的。
- 有向图:图中的每条边都是有向边。例如,网页之间的超链接可以构成一个有向图,从网页 (A) 到网页 (B) 的超链接表示从顶点 (A) 到顶点 (B) 有一条有向边。
- 权重(Weight):在某些图中,每条边可以有一个与之相关的数值,称为权重。权重可以表示距离、成本、容量等。例如,在交通网络中,边的权重可以表示道路的长度或通行时间。
- 邻接点(Adjacent Vertex):对于无向图,如果顶点 (u) 和顶点 (v) 之间存在一条边,则称 (u) 和 (v) 互为邻接点;对于有向图,如果存在一条从顶点 (u) 到顶点 (v) 的边,则称 (u) 是 (v) 的邻接点,(v) 是 (u) 的邻接点。
- 度(Degree):在无向图中,一个顶点的度是指与该顶点相关联的边的数目;在有向图中,一个顶点的度分为入度和出度,入度是指指向该顶点的边的数目,出度是指从该顶点出发的边的数目。
图的存储结构
- 邻接矩阵(Adjacency Matrix)
- 定义:用一个二维数组来表示图中顶点之间的关系。对于无向图,若顶点 (i) 和顶点 (j) 之间有边,则矩阵中第 (i) 行第 (j) 列的值为 1,否则为 0;对于有向图,若从顶点 (i) 到顶点 (j) 有边,则矩阵中第 (i) 行第 (j) 列的值为 1,否则为 0。如果图是带权图,矩阵中的值可以表示边的权重。
- 优点:容易实现,能够快速判断两个顶点之间是否存在边,以及获取顶点的度等信息。
- 缺点:空间复杂度较高,对于稀疏图(边数较少的图)来说,会浪费大量的空间。对于有 (n) 个顶点的图,邻接矩阵需要 (O(n^2)) 的空间。
- 邻接表(Adjacency List)
- 定义:对图中的每个顶点建立一个单链表,链表中的每个结点表示与该顶点相邻的顶点。对于无向图,每个顶点的链表中存储与该顶点相连的所有顶点;对于有向图,每个顶点的链表中存储从该顶点出发的所有边指向的顶点。如果图是带权图,链表中的结点还可以存储边的权重。
- 优点:空间复杂度相对较低,对于稀疏图来说比较节省空间。能够方便地获取一个顶点的所有邻接点。
- 缺点:判断两个顶点之间是否存在边需要遍历链表,时间复杂度相对较高。
图的遍历
- 深度优先搜索(DFS,Depth-First Search)
- 基本思想:从图中的某个顶点出发,访问该顶点后,选择一个与该顶点相邻且未被访问过的顶点作为下一个访问对象,继续进行深度优先搜索,直到当前的顶点的所有邻接点都被访问过,然后回溯到上一个顶点,继续寻找未被访问的邻接点,重复上述过程,直到图中所有顶点都被访问过。
- 实现方法:通常使用递归或栈来实现。递归方法中,每次访问一个顶点后,递归地访问其未被访问的邻接点;栈方法中,将访问的顶点入栈,然后从栈顶取出顶点,访问其未被访问的邻接点并入栈,直到栈为空。
- 应用场景:用于解决迷宫问题、连通性问题等。例如,在迷宫中寻找从入口到出口的路径,可以使用深度优先搜索来遍历迷宫中的每个格子。
- 广度优先搜索(BFS,Breadth-First Search)
- 基本思想:从图中的某个顶点出发,首先访问该顶点,然后依次访问该顶点的所有未被访问过的邻接点,再按照这些邻接点的顺序,依次访问它们的未被访问过的邻接点,如此循环,直到图中所有顶点都被访问过。
- 实现方法:使用队列来实现。将起始顶点入队,然后循环执行以下操作:从队列中取出一个顶点,访问该顶点的所有未被访问过的邻接点,并将这些邻接点入队,直到队列为空。
- 应用场景:用于解决最短路径问题、层次遍历问题等。例如,在社交网络中,查找一个人与另一个人之间的最短关系路径,可以使用广度优先搜索来找到最少的中间联系人。
图的常见算法
- 最短路径算法
- Dijkstra算法:用于在带权图中找到从单个源点到其他所有顶点的最短路径。算法的基本思想是:设置一个集合 (S),初始时只包含源点,然后不断从集合 (V - S) 中选择一个距离源点最近的顶点加入到集合 (S) 中,并更新从源点到集合 (V - S) 中各顶点的最短路径。重复上述过程,直到集合 (S) 包含所有顶点。
- Floyd算法:用于在带权图中找到所有顶点对之间的最短路径。算法的基本思想是:通过一个三重循环,依次考虑每个顶点作为中间顶点,更新任意两个顶点之间的最短路径。时间复杂度为 (O(n^3)),其中 (n) 是顶点的数目。
- 最小生成树算法
- Prim算法:用于在无向连通图中找到一棵最小生成树。算法的基本思想是:从图中的某个顶点开始,逐步选择与当前已选择的顶点集合相连的、权重最小的边,将该边的另一个顶点加入到集合中,直到所有顶点都被包含在集合中。时间复杂度为 (O(n^2)),其中 (n) 是顶点的数目。
- Kruskal算法:也是用于在无向连通图中找到一棵最小生成树。算法的基本思想是:将图中的所有边按照权重从小到大排序,然后依次选择权重最小的边,如果该边不会与已选择的边构成环,则将其加入到最小生成树中,直到选择的边数等于顶点数减一。时间复杂度主要取决于边的排序,通常为 (O(m \log m)),其中 (m) 是边的数目。
- 拓扑排序算法:用于对有向无环图(DAG)的顶点进行线性排序,使得对于图中的每一条有向边 (u \rightarrow v),顶点 (u) 都在顶点 (v) 之前。算法的基本思想是:从图中选择一个入度为零的顶点,将其输出,然后从图中删除该顶点及其所有出边,重复上述过程,直到图为空。如果图中存在环,则无法进行拓扑排序。
图是一种复杂但功能强大的数据结构,掌握图的存储结构、遍历方法和常见算法对于解决实际问题非常重要。
数据结构与算法中的图(Graph)
图是一种复杂的数据结构,用于表示多对多的关系,由**顶点(Vertex)和边(Edge)**组成。顶点表示数据元素,边表示元素之间的连接关系。图在现实中应用广泛,如社交网络、交通路线规划、计算机网络等。
一、图的基本概念
-
顶点(Vertex/Nodes)
- 图中的数据元素,通常用编号或标签表示(如
A
、B
、C
等)。 - 顶点数称为图的阶(Order),用
n
表示。
- 图中的数据元素,通常用编号或标签表示(如
-
边(Edge)
- 连接两个顶点的关系,分为无向边和有向边:
- 无向边:没有方向(如
(A,B)
表示A
和B
互通)。 - 有向边:有方向(如
<A,B>
表示从A
指向B
,也称弧)。
- 无向边:没有方向(如
- 边数用
m
表示。
- 连接两个顶点的关系,分为无向边和有向边:
-
权重(Weight)
- 边的附加属性,如表示距离、成本等(带权重的图称为带权图/网图)。
-
度数(Degree)
- 无向图:顶点的度数是其连接的边数。
- 有向图:
- 入度(In-degree):指向该顶点的边数。
- 出度(Out-degree):该顶点发出的边数。
二、图的分类
分类标准 | 类型 | 特点 |
---|---|---|
边的方向 | 无向图 | 边无方向,用圆括号 (u,v) 表示边。 |
有向图 | 边有方向,用尖括号 <u,v> 表示边(从 u 到 v )。 | |
是否带权重 | 无权图 | 边无权重,仅表示连接关系。 |
带权图(网图) | 边带权重,如 (u,v,weight) 。 | |
是否有环 | 无环图 | 不存在回路(如树是特殊的无环无向图)。 |
有环图 | 存在至少一个回路。 | |
顶点间连接方式 | 简单图 | 无自环(顶点到自身的边)和重边(两顶点间多条边)。 |
多重图 | 允许重边或自环。 |
三、图的存储结构
图的存储方式需兼顾空间效率和操作便利性,常见方法有:
-
邻接矩阵(Adjacency Matrix)
- 原理:用一个
n×n
的矩阵表示顶点间的连接关系。- 无向图:若
i
和j
相连,矩阵中matrix[i][j] = matrix[j][i] = 1
(无权图)或权重值(带权图)。 - 有向图:若存在边
<i,j>
,则matrix[i][j] = 1
或权重值。
- 无向图:若
- 优点:访问边的时间复杂度为 (O(1)),易于判断两顶点是否相连。
- 缺点:空间复杂度为 (O(n^2)),适用于稠密图(边数接近 (n^2))。
- 原理:用一个
-
邻接表(Adjacency List)
- 原理:为每个顶点建立一个链表,存储其相邻顶点及边的权重。
- 无向图:每个边会在两个顶点的链表中出现(如
(i,j)
出现在i
和j
的链表中)。 - 有向图:边
<i,j>
仅出现在i
的链表中。
- 无向图:每个边会在两个顶点的链表中出现(如
- 优点:空间复杂度为 (O(n+m)),适用于稀疏图(边数 (m \ll n^2))。
- 缺点:判断两顶点是否相连需遍历链表,时间复杂度为 (O(\text{度数}))。
- 原理:为每个顶点建立一个链表,存储其相邻顶点及边的权重。
-
邻接多重表(适用于无向图)
- 改进的邻接表,每条边用一个节点表示,避免无向图中边的重复存储,便于删除操作。
-
十字链表(适用于有向图)
- 结合邻接表和逆邻接表,同时存储顶点的出边和入边,便于处理有向图的入度和出度。
四、图的遍历算法
遍历图指从某顶点出发,访问所有顶点且仅访问一次,常见算法有:
-
深度优先搜索(DFS, Depth-First Search)
- 思想:类似树的先序遍历,从起点出发,尽可能深入遍历,遇到终点或已访问顶点时回溯。
- 实现:用递归或栈实现,需记录顶点访问状态。
- 时间复杂度:邻接表存储为 (O(n+m)),邻接矩阵存储为 (O(n^2))。
-
广度优先搜索(BFS, Breadth-First Search)
- 思想:类似树的层序遍历,从起点出发,先访问所有相邻顶点,再逐层扩展。
- 实现:用队列实现,按层次顺序访问顶点。
- 时间复杂度:与 DFS 相同。
五、图的经典算法
-
最短路径算法
- Dijkstra 算法:求单源点到其他顶点的最短路径(适用于非负权图)。
- Bellman-Ford 算法:支持负权边,但不能处理负权环,可检测图中是否存在负权环。
- Floyd-Warshall 算法:求任意两点间的最短路径(动态规划思想,时间复杂度 (O(n^3)))。
-
最小生成树(MST, Minimum Spanning Tree)
- 生成树:包含图中所有顶点的无环连通子图。
- Kruskal 算法:按边权从小到大排序,用并查集避免环,贪心选择最小边。
- Prim 算法:从某顶点出发,逐步向生成树中添加最小权边,适用于稠密图。
-
拓扑排序(Topological Sort)
- 对有向无环图(DAG)的顶点排序,使得若存在边
<u,v>
,则u
在排序中位于v
之前。 - 实现:每次选择入度为 0 的顶点,删除其出边并更新其他顶点入度,重复直至所有顶点处理完毕(可检测图中是否有环)。
- 对有向无环图(DAG)的顶点排序,使得若存在边
-
强连通分量(SCC, Strongly Connected Components)
- 有向图中,顶点集的最大子集,其中任意两顶点可相互到达。
- Kosaraju 算法:通过两次 DFS 找到所有强连通分量。
- Tarjan 算法:基于 DFS 递归栈,在线性时间内找到强连通分量。
六、应用场景
- 社交网络:用户为顶点,关注关系为有向边,好友关系为无向边。
- 交通网络:城市为顶点,路线为边,权重可表示距离或时间,最短路径算法用于导航。
- 计算机网络:设备为顶点,连接为边,最小生成树算法用于构建高效网络结构。
- 任务调度:拓扑排序用于安排有依赖关系的任务顺序。
- 生物信息学:蛋白质相互作用网络建模。
七、学习建议
- 理解基础概念:熟练掌握图的定义、分类、存储方式,区分无向图与有向图的性质。
- 动手实现算法:用代码实现 DFS、BFS、Dijkstra、Kruskal 等算法,理解其时间复杂度和适用场景。
- 多练真题:通过 LeetCode、力扣等平台练习图相关题目(如岛屿问题、网络连通性等)。
- 结合实际场景:思考图在现实中的应用,如用邻接表存储社交网络,用最短路径算法优化物流路线。
如果需要深入了解某个算法或存储结构的细节,可以进一步提问!