数据结构 第6章(图)

1. 图的定义和基本术语

1.1 图的定义

  • **图(Graph)**G 由两个集合 V 和 E 组成,记为 G = ( V ,E )

    • V :顶点的有穷非空集合
      • V(G): 图 G 的顶点集合
    • E :V 中顶点偶对的有穷集合
      • E(G): 图 G 的边集合
        • E(G)可以为空集:图 G 只有顶点而没有边
        • E(G)为有向边的集合:有向图
        • E(G)为无向边的集合:无向图
  • 在有向图中,顶点对 <x, y> 是有序的

    • <x, y> :从顶点 x 到顶点 y 的一条有向边

      • x 是有向边的始点,y 是有向边的终点
      • <x, y> 也称为一条弧,x 为弧尾,y 为弧头

    有向图的示例

  • 在无向图中,顶点对 (x, y) 是无序的

    • (x, y) :顶点 x 和顶点 y 相关联的一条边

    无向图的示例

1.2 图的基本术语

  • 用 n 表示图中顶点的数目,用 e 表示边的数目

  • 子图:假设有 2 个图 G = ( V ,E )和 G’ = ( V’ ,E’ ),V’ ⊆ V 且 E’ ⊆ E ,G‘ 为 G 的子图

    子图的示例

  • 无向完全图:对于无向图,具有 n(n-1)/2 条边

  • 有向完全图:对于有向图,具有 n(n-1) 条弧

  • 稀疏图:有很少边或弧(如 e < nlog_2(n))的图

  • 稠密图:有较多边或弧的图

  • :每条边上具有某种含义的数值

  • :带权的图

  • 邻接点:对于无向图 G ,边(v, v’)∈ E,v 和 v’ 互为邻接点,v 和 v‘ 相邻接

    • 边(v, v’)依附于顶点 v 和 v’
    • 边(v, v’)与顶点 v 和 v’ 相关联
  • :和顶点 v 相关联的边的数目,记为 TD(v)

  • 入度:以顶点 v 为头的弧的数目,记为 ID(v)

  • 出度:以顶点 v 为尾的弧的数目,记为 OD(v)

  • 路径:从顶点 v 到顶点 v’

    • 无向图:顶点序列(v = v_ { i , 0 }, v_ { i , 1 }, ··· , v_ { i , 0 } = v’),(v_ { i , j-1 } , v_ { i , j })∈ E ,1 ≤ j ≤ m
    • 有向图:路径也是有向的,顶点序列应满足 <v_ { i , j-1 } , v_ { i , j }> ∈ E ,1 ≤ j ≤ m
  • 路径长度:一条路径上经过的边或弧的数目

  • 回路(环):第一个二顶点和最后一个顶点相同的路径

  • 简单路径:序列中顶点不重复出现的路径

  • 简单回路(简单环):除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路

  • 连通:在无向图 G 中,从顶点 v 到顶点 v’ 有路径,v 和 v’ 是连通

  • 连通图:对于图中任意两个顶点 v_i ,v_j ∈ V ,v_i 和 v_j 都是连通的

  • 连通分量:无向图中的极大连通子图

  • 强连通图:在有向图 G 中,对于每一对 v_i ,v_j ∈ V,v_i ≠ v_j ,从 v_i 到 v_j 和 v_j 到 v_i 都存在路径

  • 强连通分量:有向图中的极大强连通子图

  • 连通图的生成树:一个极小连通子图,其含有图中全部顶点,但只有足以构成一棵树的 n-1 条边

  • 有向树:有一个顶点的入度为 0 ,其余顶点的入度均为 1 的有向图

  • 生成森林:由若干棵有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧

2. 图的存储结构

2.1 邻接矩阵

2.1.1 邻接矩阵表示法

  • 邻接矩阵(Adjacency Matrix):表示顶点之间相邻关系的矩阵

  • G( V ,E )是具有 n 个顶点的图,G 的邻接矩阵是具有如下性质的 n 阶方阵
    A [ i ] [ j ] = { 1 若 < v i , v j > 或 ( v i , v j ) ∈ E 0 反 之 A[i][j]=\begin{cases} 1 \quad 若 <v_i, v_j> 或 (v_i, v_j) ∈ E\\ 0 \quad 反之\end{cases} A[i][j]={1<vi,vj>(vi,vj)E0

  • G 是网,则其邻接矩阵为
    A [ i ] [ j ] = { w i , j 若 < v i , v j > 或 ( v i , v j ) ∈ E ∞ 反 之 A[i][j]=\begin{cases} w_{i,j} \quad 若 <v_i, v_j> 或 (v_i, v_j) ∈ E\\ ∞ \quad 反之\end{cases} A[i][j]={wi,j<vi,vj>(vi,vj)E

  • 图的邻接矩阵存储表示

#define MAXSIZE 10

typedef struct {
    int vexs[MAXSIZE];								// 顶点表
    int arcs[MAXSIZE][MAXSIZE];						// 邻接矩阵
    int vexnum;										// 图的当前点数
    int arcnum;										// 图的当前边数
} AMGraph;

网及其邻接矩阵

2.1.2 采用邻接矩阵表示法创建无向网

void CreateUDN(AMGraph* G) {
    printf("Enter vexnum , arcnum: ");
    scanf("%d,%d", &(G->vexnum), &(G->arcnum));		// 输入总定点数,总边数

    for (int i = 0; i < G->vexnum; i++)				// 输入点的信息
    {
        printf("Enter vex: ");
        scanf("%d", &(G->vexs[i]));
    }

    for (int i = 0; i < G->vexnum; i++)				// 初始化邻接矩阵,边的权值置为最大值
    {
        for (int j = 0; j < G->vexnum; j++)
        {
            G->arcs[i][j] = INT_MAX;
        }
    }

    for (int i = 0; i < G->arcnum; i++)				// 构造邻接矩阵
    {
        int vex1, vex2, value;
        printf("Enter vex1, vex2, value: ");
        scanf("%d,%d,%d", &vex1, &vex2, &value);	// 输入一条边依附的顶点和权值

        int i = LocateVex(G, vex1);					// 获取 vex1 在 G 中的位置(下标)
        int j = LocateVex(G, vex2);					// 获取 vex2 在 G 中的位置(下标)

        G->arcs[i][j] = G->arcs[j][i] = value;		// 边 <v_1, v_2> 和对称边 <v_2, v_1> 的权值置为 value
    }

    printf("Create Success\n");
}

int LocateVex(AMGraph* G, char vex) {
    int i = 0;
    for (; i < G->vexnum; i++) {
        if (G->vexs[i] == vex) {
            break;
        }
    }
    return i;
}

2.1.3 邻接矩阵表示法的优缺点

  • 优点

    • 便于判断两个顶点之间是否有边,根据 A[ i ] [ j ] = 0 或 1 来判断
    • 便于计算各个顶点的度
      • 无向图:邻接矩阵第 i 行元素之和就是顶点 i 的度
      • 有向图:第 i 行元素之和就是顶点 i 的出度, 第 i 列元素之和就是顶点 i 的入度
  • 缺点

    • 不便于增加和删除顶点
    • 不便于统计边的数目,需要扫描临界矩阵所有元素才能统计完毕,时间复杂度为 O(n^2)
    • 空间复杂度高
      • 有向图:n 个顶点需要 n^2 个单元存储边
      • 无向图:n 个顶点采用压缩存储方法需要 n(n-1)/2 个单元
      • 空间复杂度均为 O(n^2)

测试代码

#include <stdio.h>
#include <stdlib.h>

#define MAXSIZE 10

void CreateUDN(AMGraph);
int LocateVex(AMGraph);
void PrintGraph(AMGraph);

typedef struct {
    int vexs[MAXSIZE];
    int arcs[MAXSIZE][MAXSIZE];
    int vexnum;	
    int arcnum;	
} AMGraph;

int main() {
    AMGraph G;
    CreateUDN(&G);
    printf("****************\n");

    PrintGraph(G);
    printf("****************\n");
}

void CreateUDN(AMGraph* G) {
    printf("Enter vexnum , arcnum: ");
    scanf("%d,%d", &(G->vexnum), &(G->arcnum));

    for (int i = 0; i < G->vexnum; i++)
    {
        printf("Enter vex: ");
        scanf("%d", &(G->vexs[i]));
    }

    for (int i = 0; i < G->vexnum; i++)
    {
        for (int j = 0; j < G->vexnum; j++)
        {
            G->arcs[i][j] = INT_MAX;
        }
    }

    for (int i = 0; i < G->arcnum; i++)
    {
        int vex1, vex2, value;
        printf("Enter vex1, vex2, value: ");
        scanf("%d,%d,%d", &vex1, &vex2, &value);

        int i = LocateVex(G, vex1);
        int j = LocateVex(G, vex2);

        G->arcs[i][j] = G->arcs[j][i] = value;
    }

    printf("Create Success\n");
}

int LocateVex(AMGraph* G, char vex) {
    int i = 0;
    for (; i < G->vexnum; i++) {
        if (G->vexs[i] == vex) {
            break;
        }
    }
    return i;
}

void PrintGraph(AMGraph G) {
    for (int i = 0; i < G.vexnum; i++)
    {
        for (int j = 0; j < G.arcnum; j++)
        {
            printf("%10d  ", G.arcs[i][j]);
        }

        printf("\n");
    }
    printf("Print Success\n");
}

2.2 邻接表

2.2.1 邻接表表示法

  • 邻接表(Adjacency List):图的一种链式存储结构
    • 对图中每个顶点 v_i 建立一个单链表,把与 v_i 相邻接的顶点放在这个链表中
    • 每个链表的第一个结点存放有关顶点的信息,把这一结点看成链表的表头,其余结点存放有关边的信息
  • 构成
    • 表头结点表:由表头结点以顺序结构的形式存储
      • 数据域(data):存储顶点 v_i 的名称或其他有关信息
      • 链域(firstarc):指向链表中第一个结点(与顶点 v_i 邻接的第一个邻接点)
    • 边表:由表示图中顶点间关系的 2n 个边链表组成
      • 邻接点域(adjvex):与顶点 v_i 邻接的点在图中的位置
      • 数据域(info):存储和边相关的信息,如权值等
      • 链域(nextarc):与顶点 v_i 相接的下一条边的结点
#define  MAXSIZE 10									// 最大顶点数

typedef struct ArcNode {							// 边结点
    int adjvex;                         			// 该边所指向的顶点的位置
    struct ArcNode* nextarc;            			// 指向下一条边的指针
    char info;                          			// 和边相关的信息
} ArcNode;

typedef struct VNode {								// 顶点信息
    int data;
    ArcNode* firstarc;                  			// 指向第一条依附该顶点的边的指针
} VNode, AdjList[MAXSIZE];               			// AdjList 表示邻接表类型

typedef struct {									// 邻接表
    AdjList vertices;
    int vexnum, arcnum;                 			// 图的当前顶点数和边数
} ALGraph;	

G_1 的邻接表和逆邻接表

G_2 的邻接表

2.2.2 采用邻接表表示法创建无向图

void CreateUDG(ALGraph* G) {
    printf("Enter vexnum , arcnum: ");
    scanf("%d,%d", &(G->vexnum), &(G->arcnum));		// 输入总定点数,总边数

    for (int i = 0; i < G->vexnum; i++)				// 输入各点,构造表头结点表
    {
        printf("Enter vex: ");
        scanf("%d", &(G->vertices[i].data));		// 输入顶点值
        G->vertices[i].firstarc = NULL;				// 初始化表头结点的指针域为 NULL
    }

    for (int i = 0; i < G->arcnum; i++)				// 输入各边,构造邻接表
    {
        int vex1, vex2;
        printf("Enter vex1, vex2: ");
        scanf("%d,%d", &vex1, &vex2);				// 输入一条边依附的两个顶点

        int i = LocateVex(G, vex1);					// 获取 vex1 在 G 中的序号(从 0 开始)
        int j = LocateVex(G, vex2);					// 获取 vex2 在 G 中的序号(从 0 开始)

        ArcNode* p1 = (ArcNode*)malloc(sizeof(ArcNode)); // 生成新的边结点 p1
        p1->adjvex = j;								// 邻接点序号为 j
        p1->nextarc = G->vertices[i].firstarc;		
        G->vertices[i].firstarc = p1;				// 将新结点 p1 插入顶点 v_i 的边表头部

        ArcNode* p2 = (ArcNode*)malloc(sizeof(ArcNode)); // 生成新的边结点 p2
        p2->adjvex = i;								// 邻接点序号为 i
        p2->nextarc = G->vertices[j].firstarc;
        G->vertices[j].firstarc = p2;				// 将新结点 p2 插入顶点 v_j 的边表头部
    }

    printf("Create Success\n");
}

int LocateVex(ALGraph* G, int vex) {
    for (int i = 0; i < G->vexnum; i++) {
        if (G->vertices[i].data == vex)
            return i;
    }
}

2.2.3 邻接表表示法的优缺点

  • 优点

    • 便于增加和删除顶点
    • 便于统计边的数目,按顶点表顺序扫描所有边表可以得到边的数目,时间复杂度为 O(n+e)
    • 空间效率高
      • 无向图:n 个顶点 e 条边的图,邻接表中有 n 个顶点表结点和 2e 个边表结点
      • 有向图:n 个顶点 e 条边的图,邻接表中有 n 个顶点表结点和 e 个边表结点
      • 空间复杂度均为 O(n+e)
  • 缺点

    • 不便于判断顶点之间是否有边,要判定 v_i 和 v_j 之间是否有边,就需扫描第 i 个边表,最坏情况下要耗费 O(n)时间
    • 不便于计算有向图各个顶点的度
      • 无向图:顶点 v_i 的度是第 i 个边表中的结点个数
      • 有向图:第 i 个边表上的结点个数是顶点 v_i 的出度,但是求 v_i 的入度较困难,需要遍历各顶点的额边表

测试代码

#include <stdio.h>
#include <stdlib.h>

#define  MAXSIZE 10	

void CreateUDG(ALGraph);
int LocateVex(ALGraph);
void PrintGraph(ALGraph);

typedef struct ArcNode {
    int adjvex;
    struct ArcNode* nextarc;
    char info;
} ArcNode;

typedef struct VNode {
    int data;
    ArcNode* firstarc;
} VNode, AdjList[MAXSIZE];

typedef struct {
    AdjList vertices;
    int vexnum, arcnum; 
} ALGraph;

int main() {
    ALGraph G;
    CreateUDG(&G);
    printf("****************\n");

    PrintGraph(G);
    printf("****************\n");
}

void CreateUDG(ALGraph* G) {
    printf("Enter vexnum , arcnum: ");
    scanf("%d,%d", &(G->vexnum), &(G->arcnum));

    for (int i = 0; i < G->vexnum; i++)
    {
        printf("Enter vex: ");
        scanf("%d", &(G->vertices[i].data));
        G->vertices[i].firstarc = NULL;
    }

    for (int i = 0; i < G->arcnum; i++)
    {
        int vex1, vex2;
        printf("Enter vex1, vex2: ");
        scanf("%d,%d", &vex1, &vex2);

        int i = LocateVex(G, vex1);
        int j = LocateVex(G, vex2);

        ArcNode* p1 = (ArcNode*)malloc(sizeof(ArcNode));
        p1->adjvex = j;
        p1->nextarc = G->vertices[i].firstarc;
        G->vertices[i].firstarc = p1;

        ArcNode* p2 = (ArcNode*)malloc(sizeof(ArcNode));
        p2->adjvex = i;
        p2->nextarc = G->vertices[j].firstarc;
        G->vertices[j].firstarc = p2;
    }

    printf("Create Success\n");
}

int LocateVex(ALGraph* G, int vex) {
    for (int i = 0; i < G->vexnum; i++) {
        if (G->vertices[i].data == vex)
            return i;
    }
}

void PrintGraph(ALGraph G) {
    for (int i = 0; i < G.vexnum; i++)
    {
        ArcNode* p = G.vertices[i].firstarc;

        printf("%d", G.vertices[i].data);
    
        while (p)
        {
            printf("->%d", p->adjvex);
            p = p->nextarc;
        }
        
        printf("\n");
    }
    printf("Print Success\n");
}

2.3 十字链表

  • 十字链表(Orthogonal List):有向图的另一种链式存储结构
    • 将有向图的邻接表和逆邻接表结合起来得到的一种链表
    • 在十字链表中,对应于有向图中每一条弧有一个结点,对应于每个顶点也有一个结点
  • 构成
    • 弧结点
      • 尾域(tailvex):指示弧头的顶点在图中的位置
      • 头域(headvex):指示弧头的顶点在图中的位置
      • 链域(hlink):指向弧头相同的下一条弧
      • 链域(tlink):指向弧尾相同的下一条弧
      • 信息域(info):指向弧的相关信息
    • 顶点结点
      • 数据域(data):存储和顶点相关的信息
      • 首入域(firstin):指向以该顶点为弧头的第一个弧结点
      • 首出域(firstout):指向以该顶点为弧尾的第一个弧结点
#define MAXSIZE 10

typedef struct ArcBox {
    int tailvex, headvex;							// 该弧的尾和头顶点的位置
    struct ArcBox* hlik, * tlink;					// 分别为弧头相同和弧尾相同的弧的链域
    int info;										// 存储弧相关的信息
} ArcBox;

typedef struct VexNode {
    int data;
    ArcBox* firstin, * firstout;					// 分别指向该顶点第一条入弧和出弧
} VexNode;

typedef struct {
    VexNode xlist[MAXSIZE];							// 表头向量
    int vexnum, arcnum;								// 有向图的当前顶点数和弧数
} OLGraph;

有向图 G_1 的十字链表

2.4 邻接多重表

  • 邻接多重表(Adjacency Multilist):无向图的另一种链式存储结构
  • 构成
    • 边结点
      • 标志域(mark):标记该条边是否被搜索过
      • 顶点域(ivex):该边依附的顶点位置
      • 顶点域(jvex):该边依附的顶点位置
      • 边域(ilink):指向下一条依附于顶点 ivex 的边
      • 边域(jlink):指向下一条依附于顶点 jvex 的边
      • 信息域(info):指向和边相关的各种信息的指针域
    • 顶点结点
      • 数据域(data):存储和该顶点相关的信息
      • 边域(firstedge):指示第一条依附于该顶点的边
#define MAXSIZE 10

typedef enum { unvisited, visited } VisitIf;

typedef struct EBox {
    VisitIf mark;                                   // 访问标记
    int ivex, jvex;                                 // 该边依附的两个顶点的位置
    struct EBox* ilink, * jlink;                    // 分别指向依附这两个顶点的下一个边
    int info;                                       // 该边的信息
} EBox;

typedef struct VexBox {
    int data;
    EBox* firstedge;                                // 指向第一条依附该顶点的边
} VexBox;

typedef struct {
    VexBox adjmulist[MAXSIZE];
    int vexnum, degenum;                            // 无向图的当前顶点数和边数
} AMLGraph;

3.图的遍历

3.1 深度优先搜索

3.1.1 深度优先搜索遍历的过程

  • 深度优先搜索(Depth First Search, DFS):树的先序遍历的推广
  • 过程
    • 从图中某个顶点 v 出发,访问 v
    • 找出刚访问过的顶点和第一个未被访问的邻接点,访问该顶点;以该顶点为新顶点,重复次步骤,直至刚访问过的顶点没有未被访问的邻接点为止
    • 返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点
    • 重复第二、三步,直至图中所有顶点都被访问过

深度优先遍历过程

  • 根据访问时的路径和结点可以构成深度优先生成树

3.2 广度优先搜索

3.2.1 广度优先搜索遍历的过程

  • 广度优先搜索(Breadth First Search, BFS):类似于树的按层次遍历
  • 过程
    • 从图中某个顶点 v 出发,访问 v
    • 依次访问 v 的各个未曾访问过的邻接点
    • 分别从这些邻接点出发依次访问它们的邻接点,并使 ”先被访问的顶点的邻接点“ 先于 ”后被访问的顶点的邻接点” 被访问;重复此步骤,直至图中所有已被访问的顶点的邻接点都被访问到

广度优先遍历过程

  • 根据访问时的路径和结点可以构成深度优先生成树

4. 图的应用

4.1 最小生成树

  • 最小代价生成树(Minimum Cost Spanning Tree)(最小生成树):在一个连通网的所有生成树中,各边的代价之和最小的那棵生成树

4.1.1 普利姆算法

  • 过程

    假设 N = { V ,E } 是连通网,TE 是 N 上最小生成树中边的集合

    • U = { u_0 }(u_0 ∈ V),TE = {}
    • 在所有 u ∈ U,v ∈ V-U 的边( u ,v )∈ E 中找一条权值最小的边 ( u_0 ,v_0 )并入集合 TE ,同时 v_0 并入 U
    • 重复第二步,直至 U = V 为止

普利姆算法构造最小生成树的过程

4.1.2 克鲁斯卡尔算法

  • 过程

    假设 N = { V ,E } 是连通网,将 N 中的边按权值从小到大的顺序排列

    • 初始状态为只有 n 个顶点而无边的非连通图 T = (V,{}),图中每个顶点自成一个连通分量
    • 在 E 中选择权值最小的边,若该边依附的顶点落在 T 中不同的连通分量上(不形成回路),则将此边加入到 T 中,否则舍去此边而选择下一条权值最小的边
    • 重复第二步,直至 T 中所有顶点都在同一连通分量上为止

克鲁斯卡尔算法构造最小生成树的过程

4.2 最短路径

  • 在带权有向网中,习惯上称路径上的第一个顶点为源点(Source),最后一个顶点为终点(Destination)

4.2.1 从某个源点到其余各顶点的最短路径

  • 迪杰斯特拉(Dijkstra) 算法:按路径长度递增的次序产生最短路径的算法

  • 过程

    • 对于网 N = { V ,E } ,将 N 中的顶点分成两组

      • 第一组 S :已求出的最短路径的终点集合(初始时只包含源点 v_0 )
      • 第二组 V-S :尚未求出的最短路径的顶点集合(初始时为 V-{v_0} )
    • 按各顶点与 v_0 间最短路径长度递增的次序,逐个将集合 V-S 中的顶点加入到集合 S 中去;在这个过程中,始终保持从 v_0 到集合 S 中各顶点的路径长度始终不大于到集合 V-S 中各顶点的路径长度

4.2.2 每一对顶点之间的最短路径(不会)

  • 弗洛伊德(Floyd) 算法:

4.3 拓扑排序

4.3.1 AOV-网

  • 有向无环图(Directed Acycline Graph, DAG):无环的有向图
  • 顶点表示活动的网(Activity On Vertex Network,AOV-网):用顶点表示活动,用弧表示活动间的优先关系的有向图
  • 拓扑排序:将 AOV-网 中所有顶点排成一个线性序列
    • 序列满足:若在 AOV-网 中由顶点 v_i 到顶点 v_j 有一条路径,则在该线性序列中的顶点 v_i 必定在顶点 v_j 之前

4.3.2 拓扑排序的过程

  • 在有向图中选一个无前驱的顶点且输出它
  • 从图中删除该顶点和所有以它为尾的弧
  • 重复第一、二步,直至不存在无前驱的顶点
  • 若此时输出的顶点数小于有向图的顶点数,则说明有向图中存在环,否则输出的顶点序列即为一个拓扑排序

AOV-网及其拓扑有序序列产生的过程

4.4 关键路径

4.4.1 AOE-网

  • 边表示活动的网(Activity On Edge Network,AOV-网):用边表示活动,带权的有向无环图

    • 顶点:事件
    • 弧:活动
    • 权:活动持续的事件
  • 源点:网中只有一个入度为 0 的点

  • 汇点:网中只有一个出度为 0 的点

  • 带权路径长度(路径长度):一条路径各弧上的权值之和

  • 关键路径(Critical Path):一条从源点到汇点的带权路径长度最长得到路径

  • 关键活动:关键路径上的活动

4.4.2 关键路径序的求解过程

  • 对图中顶点进行排序,在排序过程中按拓扑排序求出每个事件的最早发生时间 ve(i)
  • 按拓扑排序求出每个事件的最晚发生时间 vl(i)
  • 求出每个活动 a_i 的最早开始时间 e(i)
  • 求出每个活动 a_i 的最晚开始时间 l(i)
  • 找出 e(i) = l(i) 的活动 a_i,即为关键活动;由关键活动形成的由源点到汇点的每一条路径就是关键路径,关键路径可能不止一条

AOE-网的关键路径

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值