1. 图的基本概念
1.1. 图的定义
图(Graph)是由顶点(Vertex)和边(Edge)组成的一种非线性数据结构,用于表示顶点之间的关系。图可以表示为 G=(V,E)G = (V, E)G=(V,E),其中:
- VVV:顶点的非空有限集合。
- EEE:边的有限集合,边可以是有向或无向的。
1.2. 相关术语
- 顶点(Vertex):图中的基本元素,表示一个节点。
- 边(Edge):连接两个顶点的线,表示顶点之间的关系。
- 有向图(Directed Graph):边有方向,例如 <u,v><u, v><u,v> 表示从 uuu 到 vvv 的有向边。
- 无向图(Undirected Graph):边无方向,(u,v)(u, v)(u,v) 等价于 (v,u)(v, u)(v,u)。
- 权(Weight):边上附带的数值,表示某种度量(如距离、成本)。
- 度(Degree):
- 无向图中,顶点的度是与该顶点关联的边数。
- 有向图中,分为入度(指向该顶点的边数)和出度(从该顶点出发的边数)。
- 路径(Path):从一个顶点到另一个顶点经过的顶点序列。
- 连通图:无向图中,任意两个顶点之间都存在路径。
- 强连通图:有向图中,任意两个顶点之间都存在双向路径。
- 子图(Subgraph):由原图的顶点和边的子集构成的图。
- 生成树(Spanning Tree):包含图中所有顶点的连通子图,且无环。
1.3. 图的分类
- 无向图 vs 有向图:根据边是否有方向。
- 加权图 vs 非加权图:根据边是否带有权值。
- 稀疏图 vs 稠密图:根据边数与顶点数的相对比例。
2. 图的边和顶点个数的计算
2.1. 顶点数和边数的关系
- 设图 GGG 有 nnn 个顶点,eee 条边:
- 无向图:最大边数为 n(n−1)2\frac{n(n-1)}{2}2n(n−1)(完全图)。
- 有向图:最大边数为 n(n−1)n(n-1)n(n−1)(完全有向图)。
- 稀疏图:边数远小于最大边数,e≪n2e \ll n^2e≪n2。
- 稠密图:边数接近最大边数,e≈n2e \approx n^2e≈n2。
2.2. 度的计算
- 无向图:所有顶点的度之和等于边数的两倍,即
∑degree(v)=2e \sum \text{degree}(v) = 2e ∑degree(v)=2e - 有向图:所有顶点的入度之和等于出度之和,等于边数,即
∑in-degree(v)=∑out-degree(v)=e \sum \text{in-degree}(v) = \sum \text{out-degree}(v) = e ∑in-degree(v)=∑out-degree(v)=e
3. 图的存储结构
图的存储结构决定了图的表示方式和操作效率。常见的存储结构包括邻接矩阵和邻接表。
3.1. 邻接矩阵
3.1.1. 定义
邻接矩阵是一个 n×nn \times nn×n 的二维数组 AAA,其中:
- A[i][j]A[i][j]A[i][j] 表示顶点 iii 到顶点 jjj 的边。
- 对于无向图,A[i][j]=A[j][i]A[i][j] = A[j][i]A[i][j]=A[j][i]。
- 对于加权图,A[i][j]A[i][j]A[i][j] 存储权值;若无边,设为无穷大(或特殊值)。
- 对于非加权图,A[i][j]=1A[i][j] = 1A[i][j]=1 表示有边,0 表示无边。
3.1.2. 优缺点
- 优点:
- 查询边是否存在的时间复杂度为 O(1)O(1)O(1)。
- 适合稠密图。
- 缺点:
- 空间复杂度为 O(n2)O(n^2)O(n2),对稀疏图浪费空间。
- 遍历邻接顶点的时间复杂度为 O(n)O(n)O(n),效率较低。
3.1.3. 代码示例
Python 实现(邻接矩阵):
# 邻接矩阵表示无向图
class GraphMatrix:
def __init__(self, n):
# 初始化 n x n 矩阵,0 表示无边,1 表示有边
self.n = n
self.matrix = [[0] * n for _ in range(n)]
def add_edge(self, u, v):
# 添加无向边 (u, v)
self.matrix[u][v] = 1
self.matrix[v][u] = 1
def display(self):
# 打印邻接矩阵
for row in self.matrix:
print(row)
# 示例
g = GraphMatrix(4)
g.add_edge(0, 1)
g.add_edge(1, 2)
g.add_edge(2, 3)
g.add_edge(0, 3)
g.display()
C++ 实现(邻接矩阵):
#include <vector>
#include <iostream>
using namespace std;
class GraphMatrix {
private:
int n; // 顶点数
vector<vector<int>> matrix; // 邻接矩阵
public:
GraphMatrix(int vertices) : n(vertices) {
// 初始化 n x n 矩阵,0 表示无边,1 表示有边
matrix.assign(n, vector<int>(n, 0));
}
void addEdge(int u, int v) {
// 添加无向边 (u, v)
matrix[u][v] = 1;
matrix[v][u] = 1;
}
void display() {
// 打印邻接矩阵
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
}
};
// 示例
int main() {
GraphMatrix g(4);
g.addEdge(0, 1);
g.addEdge(1, 2);
g.addEdge(2, 3);
g.addEdge(0, 3);
g.display();
return 0;
}
3.2. 邻接表
3.2.1. 定义
邻接表为每个顶点维护一个链表,链表存储该顶点的所有邻接顶点。对于加权图,链表节点还需存储权值。
3.2.2. 优缺点
- 优点:
- 空间复杂度为 O(n+e)O(n + e)O(n+e),适合稀疏图。
- 遍历邻接顶点效率高,时间复杂度为 O(degree(v))O(\text{degree}(v))O(degree(v))。
- 缺点:
- 查询边是否存在的时间复杂度为 O(degree(v))O(\text{degree}(v))O(degree(v))。
- 不适合稠密图。
3.2.3. 代码示例
Python 实现(邻接表):
# 邻接表表示无向图
class GraphList:
def __init__(self, n):
# 初始化邻接表,n 个顶点
self.n = n
self.adj_list = [[] for _ in range(n)]
def add_edge(self, u, v):
# 添加无向边 (u, v)
self.adj_list[u].append(v)
self.adj_list[v].append(u)
def display(self):
# 打印邻接表
for i in range(self.n):
print(f"Vertex {
i}: {
self.adj_list[i]}")
# 示例
g = GraphList(4)
g.add_edge(0, 1)
g.add_edge(1, 2)
g.add_edge(2, 3)
g.add_edge(0, 3)
g.display()
C++ 实现(邻接表):
#include <vector>
#include <iostream>
using namespace std;
class GraphList {
private:
int n; // 顶点数
vector<vector<int>> adj_list; // 邻接表
public:
GraphList(int vertices) : n(vertices) {
// 初始化邻接表
adj_list.resize(n);
}
void addEdge(int u, int v) {
// 添加无向边 (u, v)
adj_list[u].push_back(v);
adj_list[v].push_back(u);
}
void display() {
// 打印邻接表
for (int i = 0; i < n; i++) {
cout << "Vertex " << i << ": ";
for (int v : adj_list[i]) {
cout << v << " ";
}
cout << endl;
}
}
};
// 示例
int main() {
GraphList g(4);
g.addEdge(0, 1);
g.addEdge(1, 2);
g.addEdge(2, 3);
g.addEdge(0, 3);
g.display();
return 0;
}
4. 图的遍历
图的遍历是从某个顶点出发,访问图中所有顶点,且每个顶点只访问一次。常见方法包括深度优先搜索(DFS)和广度优先搜索(BFS)。
4.1. 深度优先搜索(DFS)
4.1.1. 定义
DFS 采用栈(显式或递归)的方式,从起始顶点开始,沿着一条路径尽可能深入,直到无法继续,然后回溯。
4.1.2. 算法步骤
- 访问起始顶点,标记为已访问。
- 选择一个未访问的邻接顶点,递归进行 DFS。
- 若无未访问的邻接顶点,回溯到上一个顶点。
- 重复直到所有顶点被访问。
4.1.3. 代码示例
Python 实现(DFS):
# 深度优先搜索(基于邻接表)
class GraphDFS:
def __init__(self, n):
self.n = n
self.adj_list = [[] for _ in range(n)]
self.visited = [False] * n
def add_edge(self, u, v):
self.adj_list[u].append(v)
self.adj_list[v].append(u)
def dfs(self, v):
# 标记当前顶点为已访问
self.visited[v] = True
print(v, end=" ") # 访问顶点
# 遍历所有邻接顶点
for neighbor in self.adj_list[v]:
if not self.visited[neighbor]:
self.dfs(neighbor)
def dfs_start(self, start):
# 初始化 visited 数组
self.visited = [False] * self.n
self.dfs(start)
# 示例
g = GraphDFS(4)
g.add_edge(0, 1)
g.add_edge(1, 2)
g.add_edge(2, 3)
g.add_edge(0, 3)
g.dfs_start(0) # 输出:0 1 2 3
C++ 实现(DFS):
#include <vector>
#include <iostream>
using namespace std;
class GraphDFS {
private:
int n; // 顶点数
vector<vector<int>> adj_list; // 邻接表
vector<bool> visited; // 访问标记
void dfs(int v) {
// 标记当前顶点为已访问
visited[v] = true;
cout << v << " "; // 访问顶点
// 遍历所有邻接顶点
for (int neighbor : adj_list[v]) {
if (!visited[neighbor]) {
dfs(neighbor);
}
}
}
public:
GraphDFS(int vertices) : n(vertices) {
adj_list.resize(n);
visited.assign(n, false);
}
void addEdge(int u, int v) {
adj_list[u].push_back(v);
adj_list[v].push_back(u);
}
void

最低0.47元/天 解锁文章
1205





