【数据结构】图

在这里插入图片描述

上期回顾: 【数据结构】堆
个人主页:C_GUIQU
归属专栏:数据结构

在这里插入图片描述

正文

1. 图的基本概念

1.1 图的定义

介绍图在数据结构中的正式定义,说明它是由顶点集合和边集合构成的一种数据结构,用于描述事物之间的关系等内容。

1.2 图的相关术语

  • 1.2.1 顶点(Vertex)
    解释顶点的概念,即图中的节点元素。

  • 1.2.2 边(Edge)
    阐述边在图中连接顶点的作用,区分有向边和无向边等情况。

  • 1.2.3 有向图与无向图
    对比有向图(边有方向)和无向图(边无方向)的特点,可举例说明不同场景下的应用。

以下是简单判断有向图和无向图的C++代码示例(仅示意,可完善):

#include <iostream>
#include <vector>

// 定义图结构体(这里简单示意,实际可更复杂)
struct Graph {
    std::vector<int> vertices;
    std::vector<std::pair<int, int>> edges;
};

// 判断是否为有向图的函数(简单依据边的属性判断)
bool isDirectedGraph(const Graph& graph) {
    for (const auto& edge : graph.edges) {
        // 如果边的两个端点顺序有意义(比如反向不存在),则为有向图
        if (std::find(graph.edges.begin(), graph.edges.end(), std::make_pair(edge.second, edge.first)) == graph.edges.end()) {
            return true;
        }
    }
    return false;
}
  • 1.2.4 度(Degree)
    讲解顶点的度在不同类型图中的含义及计算方式。

1.3 图的表示方法

  • 1.3.1 邻接矩阵表示法
    说明邻接矩阵如何用二维数组来表示图中顶点之间的连接关系,矩阵元素取值表示边的有无等情况。

以下是使用邻接矩阵表示图并初始化的C++代码示例:

#include <iostream>
#include <vector>

// 用邻接矩阵表示图(这里假设图是简单无向图,顶点编号从0开始)
class AdjacencyMatrixGraph {
public:
    AdjacencyMatrixGraph(int numVertices) : numVertices(numVertices) {
        adjMatrix.resize(numVertices, std::vector<int>(numVertices, 0));
    }

    void addEdge(int source, int destination) {
        if (source >= 0 && source < numVertices && destination >= 0 && destination < numVertices) {
            adjMatrix[source][destination] = 1;
            adjMatrix[destination][source] = 1;  // 对于无向图,对称设置
        }
    }

private:
    int numVertices;
    std::vector<std::vector<int>> adjMatrix;
};
  • 1.3.2 邻接表表示法
    描述邻接表是如何通过链表或动态数组等形式来存储每个顶点的邻接顶点信息的。

C++代码示例展示邻接表表示图(简单的链表形式示例,可优化):

#include <iostream>
#include <vector>
#include <list>

// 用邻接表表示图(顶点结构体)
struct Vertex {
    int vertexId;
    std::list<int> adjacentVertices;
};

// 图类采用邻接表表示
class AdjacencyListGraph {
public:
    AdjacencyListGraph(int numVertices) : numVertices(numVertices) {
        vertices.resize(numVertices);
        for (int i = 0; i < numVertices; ++i) {
            vertices[i].vertexId = i;
        }
    }

    void addEdge(int source, int destination) {
        if (source >= 0 && source < numVertices && destination >= 0 && destination < numVertices) {
            vertices[source].adjacentVertices.push_back(destination);
            // 对于无向图,双向添加
            vertices[destination].adjacentVertices.push_back(source);
        }
    }

private:
    int numVertices;
    std::vector<Vertex> vertices;
};
  • 1.3.3 十字链表(针对有向图)
    讲解十字链表这种更适合有向图存储的特殊结构及它的优势等。

  • 1.3.4 邻接多重表(针对无向图)
    介绍邻接多重表对于处理无向图中边的重复遍历等问题时的作用和构造方式。

2. 图的遍历

2.1 深度优先搜索(DFS)

  • 2.1.1 DFS基本原理
    解释深度优先搜索是如何沿着图的深度方向优先遍历顶点的,类似于树的先序遍历思想等内容。

  • 2.1.2 DFS算法实现(递归与非递归方式)

递归方式的C++代码示例实现深度优先搜索(简单示例,假设用邻接表表示图):

#include <iostream>
#include <vector>
#include <list>
#include <stack>

// 用邻接表表示图(同前面定义类似,这里简化重复代码)
class Graph {
public:
    Graph(int numVertices) : numVertices(numVertices) {
        vertices.resize(numVertices);
        for (int i = 0; i < numVertices; ++i) {
            vertices[i].id = i;
        }
    }

    void addEdge(int source, int destination) {
        vertices[source].adjacentVertices.push_back(destination);
    }

    void DFS(int startVertex) {
        std::vector<bool> visited(numVertices, false);
        DFSUtil(startVertex, visited);
    }

private:
    int numVertices;
    struct Vertex {
        int id;
        std::list<int> adjacentVertices;
    };
    std::vector<Vertex> vertices;

    void DFSUtil(int vertex, std::vector<bool>& visited) {
        visited[vertex] = true;
        std::cout << vertex << " ";
        for (int adjacent : vertices[vertex].adjacentVertices) {
            if (!visited[adjacent]) {
                DFSUtil(adjacent, visited);
            }
        }
    }
};

非递归方式(借助栈)的C++代码示例:

#include <iostream>
#include <vector>
#include <list>
#include <stack>

// 同样基于邻接表表示的图类(部分代码重复,可提取公共部分优化)
class Graph {
public:
    Graph(int numVertices) : numVertices(numVertices) {
        vertices.resize(numVertices);
        for (int i = 0; i < numVertices; ++i) {
            vertices[i].id = i;
        }
    }

    void addEdge(int source, int destination) {
        vertices[source].adjacentVertices.push_back(destination);
    }

    void DFSNonRecursive(int startVertex) {
        std::vector<bool> visited(numVertices, false);
        std::stack<int> stack;
        stack.push(startVertex);
        visited[startVertex] = true;

        while (!stack.empty()) {
            int currentVertex = stack.top();
            stack.pop();
            std::cout << currentVertex << " ";
            for (int adjacent : vertices[currentVertex].adjacentVertices) {
                if (!visited[adjacent]) {
                    stack.push(adjacent);
                    visited[adjacent] = true;
                }
            }
        }
    }

private:
    int numVertices;
    struct Vertex {
        int id;
        std::list<int> adjacentVertices;
    };
    std::vector<Vertex> vertices;
};
  • 2.1.3 DFS的应用举例
    列举一些深度优先搜索在实际问题中的应用场景,比如检测图中是否存在环等,并给出对应简单代码思路。

2.2 广度优先搜索(BFS)

  • 2.2.1 BFS基本原理
    说明广度优先搜索是按照层的顺序来遍历图中顶点的原理,类似树的层次遍历思想。

  • 2.2.2 BFS算法实现(队列辅助)

C++代码示例实现广度优先搜索(基于邻接表表示图):

#include <iostream>
#include <vector>
#include <queue>
#include <list>

class Graph {
public:
    Graph(int numVertices) : numVertices(numVertices) {
        vertices.resize(numVertices);
        for (int i = 0; i < numVertices; ++i) {
            vertices[i].id = i;
        }
    }

    void addEdge(int source, int destination) {
        vertices[source].adjacentVertices.push_back(destination);
    }

    void BFS(int startVertex) {
        std::vector<bool> visited(numVertices, false);
        std::queue<int> queue;
        visited[startVertex] = true;
        queue.push(startVertex);

        while (!queue.empty()) {
            int currentVertex = queue.front();
            queue.pop();
            std::cout << currentVertex << " ";
            for (int adjacent : vertices[currentVertex].adjacentVertices) {
                if (!visited[adjacent]) {
                    visited[adjacent] = true;
                    queue.push(adjacent);
                }
            }
        }
    }

private:
    int numVertices;
    struct Vertex {
        int id;
        std::list<int> adjacentVertices;
    };
    std::vector<Vertex> vertices;
};
  • 2.2.3 BFS的应用场景
    讲述广度优先搜索在如求最短路径(无权图情况下)等方面的应用,并简单阐述代码实现思路上如何拓展来解决此类问题。

3. 图的最小生成树

3.1 最小生成树的概念

解释最小生成树的定义,即在一个连通图中,选取一些边使得所有顶点连通且边的权值总和最小的子图等概念。

3.2 普里姆(Prim)算法

  • 3.2.1 Prim算法思想
    阐述Prim算法从一个顶点开始,逐步添加权值最小的边来构建最小生成树的贪心思想。

  • 3.2.2 Prim算法步骤
    详细说明算法每一步的操作,比如初始化、选择最小权值边、更新顶点集合等步骤。

以下是Prim算法的C++代码示例(简单示意,假设图用邻接矩阵表示且有权值,这里用简单的整数表示权值):

#include <iostream>
#include <vector>
#include <climits>

// 用邻接矩阵表示有权图(这里简化了图的构造等部分,专注算法逻辑)
class Graph {
public:
    Graph(int numVertices) : numVertices(numVertices) {
        adjMatrix.resize(numVertices, std::vector<int>(numVertices, INT_MAX));
    }

    void addEdge(int source, int destination, int weight) {
        adjMatrix[source][destination] = weight;
        adjMatrix[destination][source] = weight;  // 对于无向图对称设置权值
    }

    int primMST() {
        std::vector<bool> inMST(numVertices, false);
        std::vector<int> key(numVertices, INT_MAX);
        key[0] = 0;
        int result = 0;

        for (int count = 0; count < numVertices - 1; ++count) {
            int minKey = INT_MAX, minIndex = -1;
            for (int v = 0; v < numVertices; ++v) {
                if (!inMST[v] && key[v] < minKey) {
                    minKey = key[v];
                    minIndex = v;
                }
            }

            inMST[minIndex] = true;
            result += minKey;

            for (int v = 0; v < numVertices; ++v) {
                if (adjMatrix[minIndex][v] &&!inMST[v] && adjMatrix[minIndex][v] < key[v]) {
                    key[v] = adjMatrix[minIndex][v];
                }
            }
        }

        return result;
    }

private:
    int numVertices;
    std::vector<std::vector<int>> adjMatrix;
};
  • 3.2.3 Prim算法复杂度分析
    分析算法的时间复杂度和空间复杂度,比如时间复杂度一般是 O ( V 2 ) O(V^2) O(V2)(V是顶点数量,采用邻接矩阵时简单分析情况),并说明在不同实现和图表示方式下复杂度的变化情况。

3.3 克鲁斯卡尔(Kruskal)算法

  • 3.3.1 Kruskal算法思想
    讲解Kruskal算法基于边的权值从小到大排序,依次选择不构成环的边来构建最小生成树的思想,体现贪心策略。

  • 3.3.2 Kruskal算法步骤
    具体说明如边的排序、并查集判断是否构成环、添加边到最小生成树等操作步骤。

C++代码示例展示Kruskal算法(这里用到简单的并查集辅助实现,代码可进一步优化和完善):

#include <iostream>
#include <algorithm>
#include <vector>

// 边结构体,用于表示图中的边(包含起点、终点、权值)
struct Edge {
    int source;
    int destination;
    int weight;
};

// 并查集相关函数(简单实现,可更完善)
class UnionFind {
public:
    UnionFind(int n) : parent(n), rank(n, 0) {
        for (int i = 0; i < n; ++i) {
            parent[i] = i;
        }
    }

    int find(int x) {
        if (parent[x]!= x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    void unionSet(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);

        if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else {
            parent[rootY] = rootX;
            rank[rootX]++;
        }
    }

private:
    std::vector<int> parent;
    std::vector<int> rank;
};

// 图类(这里简化了一些图的常规操作,重点在Kruskal算法实现)
class Graph {
public:
    Graph(int numVertices, std::vector<Edge>& edges) : numVertices(numVertices), edges(edges) {}

    int kruskalMST() {
        std::sort(edges.begin(), edges.end(), [](const Edge& a, const Edge& b) {
            return a.weight < b.weight;
        });

        UnionFind uf(numVertices);

        int result = 0;
        int edgeCount = 0;

        for (const Edge& edge : edges) {
            int set_u = uf.find(edge.source);
            int set_v = uf.find(edge.destination);

            if (set_u!= set_v) {
                edgeCount++;
                result += edge.weight;
                uf.unionSet(set_u, set_v);
            }

            if (edgeCount == numVertices - 1) {
                break;
            }
        }

        return result;
    }

private:
    int numVertices;
    std::vector<Edge> edges;
};
  • 3.3.2 Kruskal算法复杂度分析
    分析其时间复杂度主要受边排序和并查集操作影响,通常时间复杂度为 O ( E log ⁡ E ) O(E \log E) O(ElogE)(E是边的数量)等情况,空间复杂度方面也进行相应分析。

4. 最短路径问题

4.1 单源最短路径

  • 4.1.1 迪杰斯特拉(Dijkstra)算法
    (续上)
#include <iostream>
#include <vector>
#include <climits>

// 用邻接矩阵表示的有权图(简化了部分图构造相关代码)
class Graph {
public:
    Graph(int numVertices) : numVertices(numVertices) {
        adjMatrix.resize(numVertices, std::vector<int>(numVertices, INT_MAX));
    }

    void addEdge(int source, int destination, int weight) {
        adjMatrix[source][destination] = weight;
    }

    void dijkstra(int source) {
        std::vector<int> dist(numVertices, INT_MAX);
        dist[source] = 0;
        std::vector<bool> sptSet(numVertices, false);

        for (int count = 0; count < numVertices - 1; ++count) {
            int minDist = INT_MAX, minIndex = -1;

            for (int v = 0; v < numVertices; ++v) {
                if (!sptSet[v] && dist[v] < minDist) {
                    minDist = dist[v];
                    minIndex = v;
                }
            }

            sptSet[minIndex] = true;
            for (int v = 0; v < numVertices; ++v) {
                if (!sptSet[v] && adjMatrix[minIndex][v] && dist[minIndex]!= INT_MAX && dist[minIndex] + adjMatrix[minIndex][v] < dist[v]) {
                    dist[v] = dist[minIndex] + adjMatrix[minIndex][v];
                }
            }
        }

        std::cout << "顶点 " << source << " 到各顶点的最短距离如下:" << std::endl;
        for (int i = 0; i < numVertices; ++i) {
            std::cout << "到顶点 " << i << " 的距离: ";
            if (dist[i] == INT_MAX) {
                std::cout << "无穷大" << std::endl;
            } else {
                std::cout << dist[i] << std::endl;
            }
        }
    }

private:
    int numVertices;
    std::vector<std::vector<int>> adjMatrix;
};
  • 4.1.2 迪杰斯特拉算法复杂度分析
    分析其时间复杂度,在采用邻接矩阵存储图时,一般时间复杂度为 O ( V 2 ) O(V^2) O(V2)(其中 V V V 是顶点数量),主要源于每次都要遍历所有顶点来找距离最小的未确定顶点以及更新与之相邻顶点的距离等操作。若采用小根堆等数据结构优化(比如优先队列实现),时间复杂度可以优化到 O ( ( V + E ) log ⁡ V ) O((V + E)\log V) O((V+E)logV) E E E 是边的数量)。空间复杂度通常为 O ( V ) O(V) O(V),用于存储距离数组、标记数组等相关信息。

  • 4.1.3 贝尔曼 - 福特(Bellman - Ford)算法

    • 4.1.3.1 算法思想
      阐述贝尔曼 - 福特算法的基本思路,通过对所有边进行最多 V − 1 V - 1 V1 轮松弛操作(尝试更新顶点间的距离)来逐步逼近最短路径,并且它可以处理存在负权边的情况(但不能处理存在负权回路且要检测负权回路是否存在)。

    • 4.1.3.2 算法步骤
      详细说明每一轮松弛操作具体如何进行,即遍历图中的每一条边,判断通过该边是否能让起始顶点到目标顶点的距离变小,如果可以则更新距离值。经过 V − 1 V - 1 V1 轮后理论上得到了最短路径,不过最后还需要再遍历一遍边来检测是否存在负权回路(如果还能继续松弛距离,就说明有负权回路)。

以下是贝尔曼 - 福特算法的C++代码示例(简单示意,假设图用结构体来存储边的信息,包括起点、终点和边的权值):

#include <iostream>
#include <vector>
#include <climits>

// 边结构体,存储边的起点、终点和权值信息
struct Edge {
    int source;
    int destination;
    int weight;
};

// 图类(这里简化了常规图构建部分,重点体现算法逻辑)
class Graph {
public:
    Graph(int numVertices, std::vector<Edge>& edges) : numVertices(numVertices), edges(edges) {}

    bool bellmanFord(int source) {
        std::vector<int> dist(numVertices, INT_MAX);
        dist[source] = 0;

        // 进行 V - 1 轮松弛操作
        for (int i = 0; i < numVertices - 1; ++i) {
            for (const Edge& edge : edges) {
                if (dist[edge.source]!= INT_MAX && dist[edge.source] + edge.weight < dist[edge.destination]) {
                    dist[edge.destination] = dist[edge.source] + edge.weight;
                }
            }
        }

        // 检测是否存在负权回路
        for (const Edge& edge : edges) {
            if (dist[edge.source]!= INT_MAX && dist[edge.source] + edge.weight < dist[edge.destination]) {
                return false;  // 存在负权回路
            }
        }

        return true;
    }

private:
    int numVertices;
    std::vector<Edge> edges;
};
- 4.1.3.3 复杂度分析

分析其时间复杂度,由于要对所有边进行 V − 1 V - 1 V1 次遍历,所以时间复杂度为 O ( V E ) O(VE) O(VE) V V V 是顶点数, E E E 是边数)。空间复杂度相对较低,为 O ( V ) O(V) O(V),主要用于存储各顶点到源点的距离等相关信息。

4.2 多源最短路径

  • 4.2.1 弗洛伊德(Floyd)算法
    • 4.2.1.1 算法思想
      解释弗洛伊德算法利用动态规划的思想,通过中间顶点来逐步更新任意两个顶点之间的最短距离。初始时假设没有中间顶点,直接使用图中给出的边的权值作为两点间的距离(若无边相连则为无穷大),然后依次考虑每个顶点作为中间顶点,看是否能通过这个中间顶点使得原来的距离变小,不断更新最短距离矩阵。

    • 4.2.1.2 算法步骤
      详细说明算法的具体步骤,首先初始化距离矩阵(二维数组),根据图的初始边情况赋值。然后通过三层嵌套循环,外层循环遍历所有可能的中间顶点,内层两个循环遍历所有的起点和终点顶点组合,在每一轮中间顶点的情况下,判断通过当前中间顶点是否能让起点到终点的距离变短,若能则更新距离矩阵的值。

以下是弗洛伊德算法的C++代码示例(简单示意,假设用邻接矩阵表示图且有权值):

#include <iostream>
#include <vector>

// 用邻接矩阵表示的有权图(简化图构建部分)
class Graph {
public:
    Graph(int numVertices) : numVertices(numVertices) {
        distMatrix.resize(numVertices, std::vector<int>(numVertices, INT_MAX));
        for (int i = 0; i < numVertices; ++i) {
            distMatrix[i][i] = 0;  // 自己到自己距离为0
        }
    }

    void addEdge(int source, int destination, int weight) {
        distMatrix[source][destination] = weight;
    }

    void floydWarshall() {
        for (int k = 0; k < numVertices; ++k) {
            for (int i = 0; i < numVertices; ++i) {
                for (int j = 0; j < numVertices; ++j) {
                    if (distMatrix[i][k]!= INT_MAX && distMatrix[k][j]!= INT_MAX && distMatrix[i][k] + distMatrix[k][j] < distMatrix[i][j]) {
                        distMatrix[i][j] = distMatrix[i][k] + distMatrix[k][j];
                    }
                }
            }
        }

        // 输出最短距离矩阵(可按需调整输出格式等)
        std::cout << "各顶点间的最短距离矩阵如下:" << std::endl;
        for (int i = 0; i < numVertices; ++i) {
            for (int j = 0; j < numVertices; ++j) {
                if (distMatrix[i][j] == INT_MAX) {
                    std::cout << "INF ";
                } else {
                    std::cout << distMatrix[i][j] << " ";
                }
            }
            std::cout << std::endl;
        }
    }

private:
    int numVertices;
    std::vector<std::vector<int>> distMatrix;
};
- 4.2.1.3 复杂度分析

分析其时间复杂度,由于存在三层嵌套循环,每层循环最多执行 V V V 次( V V V 是顶点数量),所以时间复杂度为 O ( V 3 ) O(V^3) O(V3)。空间复杂度同样为 O ( V 2 ) O(V^2) O(V2),主要是用于存储各顶点之间最短距离的二维矩阵。

5. 图的应用

5.1 社交网络分析

  • 5.1.1 用图表示社交关系
    解释如何将社交网络中的用户看作顶点,用户之间的好友关系、关注关系等看作边,根据不同的社交互动类型可以构建有向图或者无向图。例如在无向图中,两个用户互相关注就是一条无向边连接他们;在有向图里,A用户关注B用户则是一条从A指向B的有向边。

  • 5.1.2 基于图算法的分析应用

    • 5.1.2.1 查找共同好友
      通过图的遍历算法(比如广度优先搜索或深度优先搜索),从两个指定用户对应的顶点出发,找出他们共同的邻接顶点,这些邻接顶点就是共同好友。可以利用邻接表等图的表示方式方便地实现这一操作,代码实现上可以先分别遍历两个用户顶点的邻接顶点列表,然后对比找出相同的元素作为共同好友。

    • 5.1.2.2 推荐好友
      利用图的结构结合一些算法思路来推荐好友。比如可以通过计算用户之间的距离(如最短路径长度等),距离较近但还未建立好友关系的用户可以作为潜在的推荐好友对象;或者统计用户顶点的度(好友数量),将与目标用户的好友有较多共同好友的用户推荐给目标用户等。这里可以结合具体的图算法如广度优先搜索、深度优先搜索以及一些统计方法在代码层面去实现相应的推荐逻辑。

5.2 交通网络规划

  • 5.2.1 用图表示交通网络
    描述把交通网络中的地点(如城市、交通枢纽等)作为顶点,地点之间的道路、航线等连接线路看作边,边的权值可以表示距离、通行时间、费用等实际属性。例如在公路交通网络中,两个城市之间的公路就是连接这两个对应顶点的边,边的权值可以是公路的里程数。

  • 5.2.2 交通网络相关应用

    • 5.2.2.1 最短路径规划
      运用前面提到的最短路径算法(如迪杰斯特拉算法、弗洛伊德算法等),根据交通网络图(考虑边的权值情况),为出行者规划从出发地到目的地的最短路径,无论是按照路程最短还是时间最短等不同的衡量标准(通过设置边权值的含义来体现),都可以通过合适的图算法在代码中实现相应的路径规划功能。

    • 5.2.2.2 关键节点分析
      通过分析图中顶点的度、介数中心性(衡量一个顶点在图中作为最短路径经过点的频繁程度等指标)等属性,找出交通网络中的关键节点(比如重要的交通枢纽城市),对于交通网络的优化、资源分配以及应急处理等方面有着重要意义。在代码实现上,可以通过相关算法来计算各顶点的这些属性值,再进行对比筛选出关键节点。

5.3 电路网络分析

  • 5.3.1 用图表示电路网络
    说明把电路中的各个电子元件(如电阻、电容、电感等)的连接点看作顶点,元件之间的连接导线看作边,并且可以根据电路的特性(如电流流向等)构建有向图或者无向图(比如对于简单的并联、串联电路可能用无向图,对于含有晶体管等有电流方向要求的部分用有向图)。同时边的某些属性可以表示导线的电阻值等相关电学参数。

  • 5.3.2 电路网络相关应用

    • 5.3.2.1 电流路径分析
      利用图的遍历算法(如深度优先搜索或广度优先搜索),从电源接入点对应的顶点出发,遍历整个电路网络图,分析电流在电路中的流动路径,确定哪些元件处于电流通路中,哪些是断路等情况,便于检测电路故障等应用。代码实现时按照相应的图遍历规则,结合电路元件的连接关系(图的边情况)进行逻辑判断和路径记录。

    • 5.3.2.2 等效电阻计算
      对于由多个电阻组成的复杂电路,可以借助图的最小生成树等相关算法概念以及电学原理,将电路简化为等效的简单电路来计算总的等效电阻。例如通过找出电路网络图中合适的子图(类似最小生成树的构建思路,考虑电阻连接和等效关系),再依据串联、并联电阻的计算公式来算出等效电阻,在代码层面通过实现相应的图算法和电学计算逻辑来达到目的。

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Guiat

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

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

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

打赏作者

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

抵扣说明:

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

余额充值