数据结构:无向图

一、无向图的定义

无向图是 的一种基础类型,由顶点集合 (也称节点集合)和边集合 组成,其中每条边都没有方向,即两个顶点之间的连接是双向的。若顶点 uv 之间存在一条边,则这条边可表示为 (u, v),也可表示为 (v, u),二者等价。

资料:https://pan.quark.cn/s/43d906ddfa1bhttps://pan.quark.cn/s/90ad8fba8347https://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

六、无向图的典型应用

  1. 路径规划:如地图上两点间的最短路径查询(无权图用BFS,有权图用Dijkstra算法)。
  2. 连通性分析:如判断社交网络中两人是否存在好友关系、电网中两个节点是否连通。
  3. 环检测:如判断无向图是否为树(树是无环的连通图)。
  4. 最小生成树:如在无向连通图中,找到连接所有顶点且总权重最小的边集合(常用Kruskal算法、Prim算法)。
相关推荐
alan07212 小时前
【mysql存储引擎为什么选择B+树】
数据结构
小龙报2 小时前
【算法通关指南:算法基础篇(四)】二维差分专题:1.【模板】差分 2.地毯
c语言·数据结构·c++·深度学习·神经网络·算法·自然语言处理
立志成为大牛的小牛2 小时前
数据结构——五十八、希尔排序(Shell Sort)(王道408)
数据结构·学习·程序人生·考研·算法·排序算法
Han.miracle2 小时前
优选算法-004 盛最多水的容器
数据结构·算法
小白程序员成长日记3 小时前
2025.12.09 力扣每日一题
数据结构·算法·leetcode
天赐学c语言3 小时前
12.10 - 合并两个有序链表 && 对字节对齐的理解
数据结构·c++·leetcode·链表
仰泳的熊猫3 小时前
1092 To Buy or Not to Buy
数据结构·c++·算法·pat考试
罗湖老棍子3 小时前
【深基16.例3】二叉树深度(洛谷P4913)
数据结构·算法·二叉树