一、无向图的定义
无向图是图 的一种基础类型,由顶点集合 (也称节点集合)和边集合 组成,其中每条边都没有方向,即两个顶点之间的连接是双向的。若顶点 u 和 v 之间存在一条边,则这条边可表示为 (u, v),也可表示为 (v, u),二者等价。
资料:https://pan.quark.cn/s/43d906ddfa1b、https://pan.quark.cn/s/90ad8fba8347、https://pan.quark.cn/s/d9d72152d3cf
无向图可形式化表示为 G = (V, E),其中:
V是顶点的非空有限集合;E是边的有限集合,每条边连接V中的两个不同顶点(无向图通常不允许自环边,即连接同一顶点的边)。
二、无向图的核心概念
1. 顶点的度
顶点的度 是指与该顶点相连的边的数量,记为 deg(v)。例如在无向图中,若顶点 A 连接了边 (A,B)、(A,C),则 deg(A)=2。
- 无向图的总度数 等于边数的 2 倍(每条边贡献两个顶点的度),即
∑deg(v) = 2|E|(|E|为边的数量)。
2. 路径与环
- 路径 :从顶点
u到顶点v的一个顶点序列v₀=u, v₁, v₂, ..., vₖ=v,其中任意相邻两个顶点间都存在边。路径的长度为路径中边的数量。 - 简单路径:路径中所有顶点互不重复的路径。
- 环 :起点和终点为同一个顶点,且长度≥3的简单路径(无向图中不允许重复边),例如顶点序列
A→B→C→A即为一个环。
3. 连通性
- 连通图:若无向图中任意两个顶点之间都存在至少一条路径,则称该无向图为连通图。
- 连通分量:非连通无向图中,每个最大的连通子图称为一个连通分量。连通图的连通分量就是其自身。
4. 完全图
若无向图中任意两个不同顶点 之间都存在一条边,则称为完全无向图。对于包含 n 个顶点的完全无向图,其边数为 n(n-1)/2。
5. 稀疏图与稠密图
- 稀疏图 :边数远小于完全图边数的无向图(通常满足
|E| ≈ |V|)。 - 稠密图 :边数接近完全图边数的无向图(通常满足
|E| ≈ |V|²)。
三、无向图的存储方式
1. 邻接矩阵
用一个 n×n 的二维数组 adj 表示(n 为顶点数),其中 adj[i][j] 表示顶点 i 和顶点 j 之间是否存在边:
- 若
adj[i][j] = 1(或边的权重),表示存在边(i,j); - 若
adj[i][j] = 0(或无穷大),表示不存在边。 - 无向图的邻接矩阵是对称矩阵 ,即
adj[i][j] = adj[j][i]。
优缺点:
- 优点:查询两顶点是否相邻的时间复杂度为
O(1),实现简单; - 缺点:空间复杂度为
O(n²),对于稀疏图会造成大量空间浪费。
2. 邻接表
为每个顶点维护一个链表(或数组),存储与该顶点直接相连的所有顶点。整体可表示为一个数组 adj,其中 adj[v] 是顶点 v 的邻接顶点列表。
优缺点:
- 优点:空间复杂度为
O(|V| + |E|),适合存储稀疏图,遍历顶点邻接边的效率高; - 缺点:查询两顶点是否相邻的时间复杂度为
O(deg(v))(需遍历顶点v的邻接表)。
四、无向图的遍历算法
1. 深度优先搜索(DFS)
从起始顶点出发,沿着一条路径尽可能深地访问,直到无法继续后回溯,再探索其他路径。可通过递归或栈实现。
- 核心思想:"先深后广",标记已访问顶点避免重复。
- 时间复杂度 :邻接矩阵存储为
O(n²),邻接表存储为O(|V| + |E|)。
2. 广度优先搜索(BFS)
从起始顶点出发,先访问其所有邻接顶点,再依次访问邻接顶点的邻接顶点,逐层扩散。通常通过队列实现。
- 核心思想:"先广后深",可用于求解无权图的最短路径。
- 时间复杂度 :邻接矩阵存储为
O(n²),邻接表存储为O(|V| + |E|)。
五、无向图的实现示例
1. 邻接表实现
python
class UndirectedGraph:
def __init__(self, num_vertices):
self.num_vertices = num_vertices
# 初始化邻接表,索引对应顶点编号
self.adj_list = [[] for _ in range(num_vertices)]
def add_edge(self, u, v):
"""添加无向边(u, v)"""
if v not in self.adj_list[u]:
self.adj_list[u].append(v)
if u not in self.adj_list[v]:
self.adj_list[v].append(u)
def remove_edge(self, u, v):
"""删除无向边(u, v)"""
if v in self.adj_list[u]:
self.adj_list[u].remove(v)
if u in self.adj_list[v]:
self.adj_list[v].remove(u)
def dfs(self, start, visited=None):
"""深度优先搜索,递归实现"""
if visited is None:
visited = [False] * self.num_vertices
visited[start] = True
print(start, end=" ")
for neighbor in self.adj_list[start]:
if not visited[neighbor]:
self.dfs(neighbor, visited)
def bfs(self, start):
"""广度优先搜索,队列实现"""
visited = [False] * self.num_vertices
queue = [start]
visited[start] = True
while queue:
vertex = queue.pop(0)
print(vertex, end=" ")
for neighbor in self.adj_list[vertex]:
if not visited[neighbor]:
visited[neighbor] = True
queue.append(neighbor)
使用示例
python
# 初始化包含5个顶点的无向图(顶点编号0-4)
graph = UndirectedGraph(5)
# 添加边
graph.add_edge(0, 1)
graph.add_edge(0, 2)
graph.add_edge(1, 3)
graph.add_edge(2, 3)
graph.add_edge(3, 4)
print("DFS遍历结果:")
graph.dfs(0) # 输出 0 1 3 2 4(遍历顺序可能因邻接表存储顺序略有差异)
print("\nBFS遍历结果:")
graph.bfs(0) # 输出 0 1 2 3 4
六、无向图的典型应用
- 路径规划:如地图上两点间的最短路径查询(无权图用BFS,有权图用Dijkstra算法)。
- 连通性分析:如判断社交网络中两人是否存在好友关系、电网中两个节点是否连通。
- 环检测:如判断无向图是否为树(树是无环的连通图)。
- 最小生成树:如在无向连通图中,找到连接所有顶点且总权重最小的边集合(常用Kruskal算法、Prim算法)。