图结构
图(Graph)结构也是是一种描述非线性关系
的数据结构。在树
结构中,结点之间是具有层次关系的,每一层的结点可以和多个下层结点关联,但是只能和一个上层结点关联。而图结构中,每个结点之间可以任意关联。
组成
- 顶点(Vertex):图中的结点。
- 边(Edge):图中连接结点的线。
所有的顶点构成一个顶点集合,所有的边构成一个边集合,一个完整的图结构就是由顶点集合和边集合组成。在数学上一般标记为:G = (V, E)
或者 G = (V(G), E(G))
注意:图结构中顶点集合 V(G)
必须为非空,即必须包含一个顶点;而边集合可以为空,此时表示没有边。
图的基本概念
- 无向图:如果一个图结构中,所有边都没有方向性,则称为无向图(表示边时,对两个顶点的顺序没有要求,用圆括号表示:
(V3, V4)
)。
- 有向图:如果一个图结构中,边是有方向性的,则称为有向图(表示边时,对两个顶点的顺序有所要求,用尖括号表示:
<V3, V4>
)。
- 顶点的度(Degree):连接顶点的边的数量称为该顶点的度(在无向图中,简单记为:
D(V)
;在有向图中,D(V) = OD(V) + ID(V)
)。
- 出度:是以该顶点为端点的出边数量,记为:
OD(V)
。
- 入度:是以该顶点为端点的入边数量,记为:
ID(V)
。
- 邻接顶点:是指图结构中一条边的两个顶点(在有向图中,区分为:起始顶点(起点或始点)和结束顶点(终点) )。
- 入边邻接顶点:连接该顶点的边中的起始顶点。
- 出边邻接顶点:连接该顶点的边中的结束顶点。
- 无向完全图:如果在一个无向图结构中,每两个顶点之间都存在一条边,则称为无向完全图(N 个顶点的无向完全图,其总边数为:
(N * (N - 1)) / 2
)。
- 有向完全图:如果在一个有向图结构中,每两个顶点之间都存在方向相反的两条边,则称为有向完全图(N 个顶点的有向完全图,其总边数为:
(N * (N - 1))
。
- 子图:如果一个图结构的顶点和边都是另一个图结构的子集合,则称该图结构是另一个图结构的子图。
- 路径:是指图结构中两个顶点之间的连线,路径上边的数量称之为
路径长度
。
- 简单路径:在图结构中,如果一条路径上顶点不重复出现,则称之为简单路径。
- 环(回路):在图结构中,如果路径的第一个顶点和路径的最后一个顶点相同,则称之为环,有时也称之为回路。
- 简单回路:在图结构中,如果除第一个顶点和最后一个顶点相同外,其余各顶点都不重复的回路称为简单回路。
- 连通:如果图结构中,两个顶点之间有路径,则称这两个顶点是连通的(注意:连通的两个顶点可以不是邻接顶点,只要有路径连接即可,可以途径多个顶点)。
- 连通图(无向图):如果无向图中任意两个顶点都是连通的,则称之为连通图(如果无向图结构图中,包含两个顶点是不连通的,则称之为非连通图)。
- 连通分量:无向图的极大连通子图(即任意连通子图都是连通分量)称为该图的连通分量。
- 强连通图(有向图):
- 如果两个顶点之间有路径,也称为这两个顶点是连通的(注意:有向图中边是有方向的。因此,有时从
V1
到 V2
是连通的,但从 V2
到 V1
却不一定连通)。
- 如果有向图中任意两个顶点都是连通的,,则称之为强连通图(如果有向图中包含两个顶点不是连通的,,则称之为非强连通图)。
- 强连通分量:有向图的极大连通子图(即任意强连通子图都是强连通分量)称为该图的连通分量(强连通图只有一个强连通分量,那就是该图本事)。
- 权(Weight):在实际应用中需要将边表示成某种数值,这个数值便是该边的权(无向图中加入权值,则称之为
无向带权图
;有向图中加入权值,则称之为有向带权图
)。
- 网:网就是边上带有权值的图的另一种名称。
图的遍历
图的遍历是指从图中的某一顶点出发,按照一定的策略访问图中的每一个顶点。而且每个顶点有且只能被访问一次;也是将网络结构按某种规则线性化的过程。对图的遍历通常有**”深度优先搜索(遍历)”和“广度优先搜索(遍历)”**方法。
深度优先搜索(Depth First Search:DFS)
算法思路:类似树的先根遍历。设初始化时,图中各顶点均未被访问,从图中某个顶点(设为V0
)出发,访问V0
;然后搜索V0
的一个邻接点Vi
,若Vi
未被访问,则访问之;再搜索Vi
的一个邻接点(深度优先);不断地沿着顶点的深度方向(顶点的邻接点方向)遍历…。若某顶点的邻接点全部访问完毕,则回溯(Backtracking)到它的上一顶点,然后再从此顶点又按深度优先的方法搜索下去,…,直到能访问的顶点都访问完毕为止。
广度优先搜索(Breadth First Search:BFS)
算法思路:类似树的按层次遍历。设初始时,图中各顶点均未被访问,从图中某顶点(设为V0
)出发,访问V0
,并依次访问V0
的各个邻接点(广度优先)。然后,分别从这些被访问过的顶点出发,仍按照广度优先的策略搜索其它顶点,先访问顶点的邻接点先于后访问顶点的邻接点被访问….直到能访问的顶点都访问完毕为止。
技巧:为控制广度优先的正确搜索,要用到队列技术,即访问完一个顶点后,让该顶点的序号入队。然后取相应队头(出队),考察访问过的顶点的各邻接点,将未访问过的邻接点访问 后再依次进队,…,直到队空为止。
图操作实例代码
数据准备
定义图结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #define MATRIX_NUM 20
#define MAX_VALUE 65535
#define TRAVERSE_YES 1
#define TRAVERSE_NO 0
typedef struct GraphMatrix{ char vertex[MATRIX_NUM]; int type; int vertexNum; int edgeNum; int edgeWeight[MATRIX_NUM][MATRIX_NUM]; int isTraverse[MATRIX_NUM]; }GMType;
|
相关操作
创建图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| void GraphCreate(GMType* gm) { if (NULL == gm) { printf("图结构指针不存在,无法创建图结构!\n"); return; } int weight; char eStartV; char eEndV; printf("请输入图中各顶点信息!\n"); for (int i = 0; i < gm->vertexNum; i++) { getchar(); printf("第 %d 个顶点:", i + 1); scanf("%c", &(gm->vertex[i])); } printf("\n输入构成各边的顶点及权值,格式:起始顶点 结束顶点 权值 \n"); for (int j = 0; j < gm->edgeNum; j++) { getchar(); printf("第 %d 条边:", j + 1); scanf("%c %c %d", &eStartV, &eEndV, &weight); int startIndex, endIndex; for (startIndex = 0; startIndex < gm->vertexNum; startIndex++) { if (eStartV == gm->vertex[startIndex]) { break; } } for (endIndex = 0; endIndex < gm->vertexNum; endIndex++) { if (eEndV == gm->vertex[endIndex]) { break; } } if (gm->vertexNum <= startIndex) { printf("起始顶点不存在,请重新输入!\n"); j--; continue; } if (gm->vertexNum <= endIndex) { printf("结束顶点不存在,请重新输入!\n"); j--; continue; } gm->edgeWeight[startIndex][endIndex] = weight; if (0 == gm->type) { gm->edgeWeight[endIndex][startIndex] = weight; } } }
|
清空图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void GraphClear(GMType* gm) { if (NULL == gm) { printf("图结构指针不存在,无法清空图结构!\n"); return; } for (int i = 0; i < gm->vertexNum; i++) { for (int j = 0; j < gm->vertexNum; j++) { gm->edgeWeight[i][j] = MAX_VALUE; } } }
|
访问某个顶点的值
1 2 3 4 5 6 7 8 9
| void GraphVisitVertex(GMType* gm, unsigned int index) { if (NULL == gm) { printf("图结构指针不存在,无法访问某个顶点的值!\n"); return; } printf("-->%c", gm->vertex[index]); }
|
查询关键字-key 对应的顶点下标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| unsigned int GraphNodeIndex(GMType* gm, char key) { if (NULL == gm) { printf("图结构指针不存在,无法查询关键字-key 对应的顶点下标!\n"); return MAX_VALUE; } for (int i = 0; i < gm->vertexNum; i++) { if (key == gm->vertex[i]) { return i; } } return MAX_VALUE; }
|
显示图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| void GraphShowAll(GMType* gm) { if (NULL == gm) { printf("图结构指针不存在,无法显示图结构!\n"); return; } for (int i = 0; i < gm->vertexNum; i++) { printf("\t%c", gm->vertex[i]); } printf("\n"); for (int j = 0; j < gm->vertexNum; j++) { printf("%c", gm->vertex[j]); for (int k = 0; k < gm->vertexNum; k++) { if (MAX_VALUE == gm->edgeWeight[j][k]) { printf("\tZ"); } else { printf("\t%d", gm->edgeWeight[j][k]); } } printf("\n"); } }
|
深度优先遍历单个顶点的邻接顶点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void GraphDFTOne(GMType* gm, unsigned int index) { if (NULL == gm) { printf("图结构指针不存在,无法深度优先遍历单个顶点的邻接顶点!\n"); return; } gm->isTraverse[index] = TRAVERSE_YES; printf("-->%c", gm->vertex[index]); for (int i = 0; i < gm->vertexNum; i++) { if (MAX_VALUE != gm->edgeWeight[index][i] && TRAVERSE_NO == gm->isTraverse[i]) { GraphDFTOne(gm, i); } } }
|
深度优先遍历(或深度优先搜索:Depth First Search)图结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void GraphDepthFirstSearch(GMType* gm) { if (NULL == gm) { printf("图结构指针不存在,无法深度优先遍历图结构!\n"); return; } for (int i = 0; i < gm->vertexNum; i++) { gm->isTraverse[i] = TRAVERSE_NO; } printf("深度优先遍历图结构结点!\n"); for (int j = 0; j < gm->vertexNum; j++) { if (TRAVERSE_NO == gm->isTraverse[j]) { GraphDFTOne(gm, j); } } printf("\n"); }
|
广度优先遍历(或广度优先搜索:Breadth First Search)图结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| void GraphBreadthFirstSearch(GMType* gm) { if (NULL == gm) { printf("图结构指针不存在,无法广度优先遍历图结构!\n"); return; } for (int i = 0; i < gm->vertexNum; i++) { gm->isTraverse[i] = TRAVERSE_NO; } char qGraph[MATRIX_NUM]; unsigned int tail = 0; unsigned int head = 0; printf("广度度优先遍历图结构结点!\n"); for (int j = 0; j < gm->vertexNum; j++) { if (TRAVERSE_NO == gm->isTraverse[j]) { gm->isTraverse[j] = TRAVERSE_YES; GraphVisitVertex(gm, j); qGraph[tail++] = gm->vertex[j]; while (head != tail) { int outIndex = GraphNodeIndex(gm, qGraph[head++]); if (gm->vertexNum <= outIndex) { break; } for(int k = 0; k < gm->vertexNum; k++) { if(MAX_VALUE != gm->edgeWeight[outIndex][k] && TRAVERSE_NO == gm->isTraverse[k]) { gm->isTraverse[k] = TRAVERSE_YES; GraphVisitVertex(gm, k); qGraph[tail++] = gm->vertex[k]; } } } } } printf("\n"); }
|