数据结构——图

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> 表示从 uuuvvv 的有向边。
  • 无向图(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. 顶点数和边数的关系

  • 设图 GGGnnn 个顶点,eee 条边:
    • 无向图:最大边数为 n(n−1)2\frac{n(n-1)}{2}2n(n1)(完全图)。
    • 有向图:最大边数为 n(n−1)n(n-1)n(n1)(完全有向图)。
  • 稀疏图:边数远小于最大边数,e≪n2e \ll n^2en2
  • 稠密图:边数接近最大边数,e≈n2e \approx n^2en2

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. 算法步骤
  1. 访问起始顶点,标记为已访问。
  2. 选择一个未访问的邻接顶点,递归进行 DFS。
  3. 若无未访问的邻接顶点,回溯到上一个顶点。
  4. 重复直到所有顶点被访问。
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 
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱看烟花的码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值