图的定义和基本术语

图(graph):图(graph)由两个集合 V(vertex)和 E(edge)组成,记为 G=(V, E)。

顶点(vertex):V 是顶点(vertex)的有穷非空集合。顶点即数据元素

边(edge):E 是 V 中顶点偶对的有穷集合。这些顶点偶对称为边(edge)

V(G) E(G) 分别表示图 G 的顶点集合和边集合。

E(G) 可以是空集。若 E(G) 为空,则图 G 只有顶点而没有边。图可以只有顶点而没有边

有向图:对于图 G,若 E(G) 为有向边的集合,则图 G 称为有向图

无向图:对于图 G,若 E(G) 为无向边的集合,则图 G 称为无向图

有向图中,每一条边都是有方向的;无向图中,每一条边都是没有方向的。

对于图中的边,为了区分有向图和无向图,有向图中的边称为弧,无向图中的边称为边。即有向边称为弧,无向边称为边。

:有向图的顶点偶对 <x, y> 用尖括号括起来,表示有序序偶,称为从顶点 x 到顶点 y 的一条有向边,有向边也称为弧。<x, y> 和 <y, x> 是两条不同的弧

:无向图的顶点偶对 (x, y) 用圆括号括起来,表示无序序偶,称为与顶点 x 和顶点 y 相关联的一条无向边,无向边也称为边。(x, y) 和 (y, x) 是同一条无向边

弧尾、弧头:弧 <x, y> 中,x 为弧的起点,称为弧尾,y 为弧的终点,称为弧头

图的示例:

假设一个图中有 n 个顶点,e 条边。

子图:假设有两个图 G=(V,E)G=(V,E),如果 VVEE,则称 G' 为 G 的子图。

完全图:任意两个顶点之间都有边/弧相连。

无向完全图:有 n(n-1)/2 条边的无向图。任意两个顶点之间都有一条边。

有向完全图:有 n(n-1) 条弧的有向图。任意两个顶点之间都有两条方向相反的弧。

例如:

稀疏图和稠密图:有很少条边或弧(如 e<nlog2n)的图称为稀疏图,反之称为稠密图。

:在实际应用中,每一条边/弧可以标上带有某种含义的数值(比如表示两个顶点之间的距离或者耗费等),这个数值称为这条边/弧的权。

:边/弧带权的图称为网。边有向则称为有向网;边无向则称为无向网

至此,图可以分为四类:无向图(undirectedgraph)无向网(undirectednet)有向图(directedgraph)有向网(directednet)

示意图:

邻接:有边/弧相连的两个顶点之间的关系。

邻接点:对于无向图,如果存在 (x, y),则称 x 和 y 互为邻接点,即 x 和 y 相邻接,边 (x, y) 依附于顶点 x 和 y,或者说边 (x, y) 和顶点 x 和 y 相关联。对于有向图,如果存在 <x, y>,则称 x 邻接到 y,或者 y 邻接于 x。

关联/依附:边/弧与顶点之间的关系。如果存在 (x, y)/<x, y>,则称这条边/弧关联于/依附于 x 和 y。

:顶点的度指的是和顶点 x 相关联的边的数目,记为 TD(x)。(TD:total degree ??)

入度,出度:对于有向图,每一个顶点的度,可以分为入度和出度。入度是指以顶点为弧头的弧的数目,即进入这个顶点的弧的数目,记作 ID(x);出度是指以顶点为弧尾的弧的数目,即从这个顶点发出的弧的数目,记作 OD(x)。顶点 x 的度就等于两者之和,即 TD(x) = ID(x) + OD(x)

例如:

一般地,对于有 n 个顶点,e 条边的图,有 e=12i=1nTD(vi)。即边数等于所有的顶点的度的和除以 2

路径:接续的边构成的顶点序列。如果是有向图,则路径也是有向的。

路径长度:路径上边/弧的数目/权值之和(不带权时为数目之和,带权时为权值之和)。

回路/环:第一个顶点和最后一个顶点相同的路径称为回路/环。回路是特殊的路径。

简单路径:除了第一个顶点和最后一个顶点可以相同之外,其他所有顶点均不相同的路径。

简单回路/简单环:第一个和最后一个顶点相同,除此之外再无别的顶点相同的路径。

例如:

连通:如果两个顶点之间有路径,则称这两个顶点是连通的。

连通图:一个无向图,如果任意两个点之间都有路径,即任意两个顶点都是连通的,则称这个图是连通图。这里从一个顶点到另一个顶点可以经过其他顶点,即两个顶点之间不必非得是直达的,可以中转。

强连通图:一个有向图,如果任意两个点之间都有路径,即任意两个顶点都是连通的,则称这个图是强连通图。从 x 到 y 有路径,不代表从 y 到 x 就有路径,这一点和无向图不同。和连通图一样,这里的路径也是允许中转的,不是非得直达。

连通图指的是无向图,如果是有向图,则用强连通图来描述,即强连通图是有向的连通图。如果连通图的边是有向的,则它是强连通图。

对于有向的连通图,即强连通图,每一个顶点都必须既有入度也有出度

例如:

在上面的连通图和强连通图中,从 v1 到 v3 都需要中转,但它们仍然都是连通的。

极大连通子图:该子图是图 G 的连通子图,但是将 G 中任何不在该子图的顶点加入该子图后,该子图都将不再连通。

极大连通子图,首先要是子图,其次,子图是连通的,再其次,将不在子图中的任意一个其他顶点加入子图后,子图将不再连通。

极大强连通子图:该子图是图 G 的强连通子图,但是将 G 中任何不在该子图的顶点加入该子图后,该子图都将不再强连通。

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

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

非连通图有多个极大(强)连通子图,每一个极大(强)连通子图都是一个(强)连通分量,因此非连通图有多个(强)连通分量。

如图:

极小连通子图:该子图是图 G 的连通子图,在该子图中删除任意一条边后,子图将不再连通。

生成树:一个极小连通子图,它含有全部的顶点,但只含有足以构成一棵树的 n-1 条边,这样的连通子图称为连通图生成树。如果在一棵生成树上添加一条边,必定构成一个环,因为这条边使得它依附的那两个顶点之间有了第二条路径。如果在一棵生成树上删除任意一条边,子图将不再连通。

生成树本来就是连通的,说明任意两个顶点之间都是有路径的,假设任意添加一条边,必将导致这两个顶点之间又多了一条路径,即构成了一个环。

由连通图或者非连通图的连通分量进行某种转化可以得到生成树。

如图:

生成森林:对于非连通图,由各个连通分量的生成树构成的集合。

一棵有 n 个顶点的生成树,有且仅有 n-1 条边。如果一个图有 n 个顶点和小于 n-1 条边,则是非连通图,如果多于 n-1 条边,则一定有环,但是有 n-1 条边不一定是生成树

有向树:仅有一个顶点入度为 0,其余顶点的入度均为 1 的有向图称为有向树。如图:

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

图的存储结构

图没有顺序存储结构。可以用二维数组来表示,即图的邻接矩阵(Adjacency Matrix)表示法。也可以用链式存储方式来存储,图的链式存储有很多种,如邻接表(Adjacency List)十字链表(Orthogonal List)邻接多重表(Adjacency Multilist)等。

邻接矩阵

邻接矩阵(Adjacency Matrix)表示法也叫数组表示法(不能叫数组存储法)。

邻接矩阵是表示顶点之间相邻关系的矩阵

设 G(V, E) 是具有 n 个顶点的图,则 G 的邻接矩阵是具有如下性质的 n 阶方阵:

A[i][j]={1<vi,vj>(vi,vj)E0

如图:

邻接矩阵的对角线元素一定为 0

无向图的邻接矩阵一定是对称矩阵

完全图的邻接矩阵的对角线元素全部为 0,其余元素全部为 1,即完全图的邻接矩阵一定是对称矩阵

邻接矩阵是唯一的

有向图的邻接矩阵一般都是不对称的,只有当有向图为完全图时其邻接矩阵才是对称矩阵

有向图的每一个顶点的出度是第 i 行元素之和,每一个元素的入度是第 i 列元素之和。顶点的度等于第 i 行元素之和加上第 i 列元素之和

若 G 是网,则邻接矩阵可以定义为:

A[i][j]={ωi,j<vi,vj>(vi,vj)E

其中,ωi,j 表示边上的权值, 表示计算机允许的、大于所有边上权值的数。

如图:

使用邻接矩阵表示一个图时,除了一个用于存储邻接矩阵的二维数组外,还需要一个一维数组来存储顶点信息,即顶点数组。 顶点数组也叫顶点表。

即用邻接矩阵存储一个图时,要用到两个数组,一个一维数组,存储顶点信息,一个二维数组,表示邻接矩阵,存储边的信息

图的邻接矩阵存储表示:

//图/网的邻接矩阵的存储表示(适用于图/网、有向/无向)
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 假设顶点的数据类型为 char
typedef int edgetype; // 假设边的权值类型为 int
typedef struct adjacencymatrix {
vertextype vertexes[MAXVERTEXNUM]; // 顶点数组(一维数组)
edgetype edges[MAXVERTEXNUM][MAXVERTEXNUM]; // 邻接矩阵(二维数组)
int vertexnum, edgenum; // 图的顶点数和边数
}adjacencymatrix;

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

【算法步骤】

  1. 输入顶点数(vertexnum)和边数(edgenum)。
  2. 依次输入顶点的信息存入顶点表(vertexes)中。
  3. 初始化邻接矩阵(edges),使每一个权值初始化为极大值(MAXINT)。
  4. 构造邻接矩阵。依次输入每一条边依附的顶点和其权值,确定两个顶点在图中的位置后,使相应边赋予相应权值,同时使其对称边赋予相同的权值。

因为是创建网,所以在初始化邻接矩阵时,要把权值全部初始化为最大值,在构造邻接矩阵时,除了要输入相关联的顶点外,还要输入权值,确定了邻接矩阵中的一个位置的值后,还要使对称位置赋予相同的权值。

【算法描述】

//采用邻接矩阵表示法创建无向网
#include <stdio.h>
//图/网的邻接矩阵的存储表示(适用于图/网、有向/无向)
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 假设顶点的数据类型为 char
typedef int edgetype; // 假设边的权值类型为 int
//邻接矩阵
typedef struct adjacencymatrix {
vertextype vertexes[MAXVERTEXNUM]; // 顶点数组(一维数组)
edgetype edges[MAXVERTEXNUM][MAXVERTEXNUM]; // 邻接矩阵(二维数组)
int vertexnum, edgenum; // 图的顶点数和边数
}adjacencymatrix;
//找某一个顶点在顶点表中的位置
int locate(adjacencymatrix G, vertextype vertex) {
for (int i = 0; i < G.vertexnum; i++)
if (G.vertexes[i] == vertex)
return i;
return -1;
}
//创建无向网(邻接矩阵)
void CreatUndirectedNet(adjacencymatrix* G) {
//输入顶点数和边数
printf("请输入无向网的顶点数和边数(用空格分隔开):");
scanf("%d %d", &((*G).vertexnum), &((*G).edgenum));
//输入顶点信息,创建顶点表
printf("请依次输入顶点,如 a\n");
for (int i = 0; i < (*G).vertexnum; i++) {
printf("请输入第 %d 个顶点的信息:", i + 1);
scanf(" %c", &((*G).vertexes[i]));
}
//初始化邻接矩阵,全部权值均为最大值
for (int i = 0; i < (*G).edgenum; i++)
for (int j = 0; j < (*G).edgenum; j++)
(*G).edges[i][j] = MAXINT;
//输入每条边所依附的两个点以及该边的权值,创建邻接矩阵
printf("请依次输入每一条边依附的两个顶点以及该边的权值,如 a b 9(用空格分隔开)\n");
for (int i = 0; i < (*G).edgenum; i++) {
printf("请输入第 %d 条边依附的两个顶点以及该边的权值:", i + 1);
vertextype vertex1, vertex2;
int location1, location2;
edgetype weight;
scanf(" %c %c %d", &vertex1, &vertex2, &weight);
location1 = locate(*G, vertex1);
location2 = locate(*G, vertex2);
(*G).edges[location1][location2] = weight; //赋予权值
(*G).edges[location2][location1] = weight; //对称位置赋予相同权值
}
}
//打印邻接矩阵
void print(adjacencymatrix G) {
for (int i = 0; i < G.vertexnum; i++) {
for (int j = 0; j < G.vertexnum; j++) {
if (G.edges[i][j] != MAXINT) {
if (j != G.vertexnum - 1)
printf("%d\t", G.edges[i][j]);
else
printf("%d\n", G.edges[i][j]);
}
else {
if (j != G.vertexnum - 1)
printf("∞\t");
else
printf("∞\n");
}
}
}
}
int main(void) {
adjacencymatrix G;
CreatUndirectedNet(&G);
print(G);
return 0;
}

执行结果:

请输入无向网的顶点数和边数(用空格分隔开):5 6
请依次输入顶点,如 a
请输入第 1 个顶点的信息:a
请输入第 2 个顶点的信息:b
请输入第 3 个顶点的信息:c
请输入第 4 个顶点的信息:d
请输入第 5 个顶点的信息:e
请依次输入每一条边依附的两个顶点以及该边的权值,如 a b 9(用空格分隔开)
请输入第 1 条边依附的两个顶点以及该边的权值:a b 1
请输入第 2 条边依附的两个顶点以及该边的权值:a d 2
请输入第 3 条边依附的两个顶点以及该边的权值:b c 3
请输入第 4 条边依附的两个顶点以及该边的权值:b e 5
请输入第 5 条边依附的两个顶点以及该边的权值:c d 4
请输入第 6 条边依附的两个顶点以及该边的权值:c e 6
∞ 1 ∞ 2 ∞
1 ∞ 3 ∞ 5
∞ 3 ∞ 4 6
2 ∞ 4 ∞ ∞
∞ 5 6 ∞ ∞

该算法的时间复杂度为 O(n2)

将上面代码稍作修改便可写出无向图、有向网和有向图的代码。如图:

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

在无向网的代码基础上,做三处修改:

  1. 初始化邻接矩阵时,邻接矩阵的元素全部初始化为 0。
  2. 当读入有关联的顶点时,不要再读入权值。
  3. 构建邻接矩阵时,有关联的两个顶点的对应到邻接矩阵中的位置为 1 而不是某一个权值。

【算法步骤】

  1. 输入顶点数(vertexnum)和边数(edgenum)。
  2. 依次输入顶点的信息存入顶点表(vertexes)中。
  3. 初始化邻接矩阵(edges),使每一个权值初始化为 0。
  4. 构造邻接矩阵。依次输入每一条边依附的顶点,确定两个顶点在图中的位置后,使相应边的赋予为 1,同时使其对称边的权值也为 1。

代码:

//采用邻接矩阵表示法创建无向图
#include <stdio.h>
//图/网的邻接矩阵的存储表示(适用于图/网、有向/无向)
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 假设顶点的数据类型为 char
typedef int edgetype; // 假设边的权值类型为 int
//邻接矩阵
typedef struct adjacencymatrix {
vertextype vertexes[MAXVERTEXNUM]; // 顶点数组(一维数组)
edgetype edges[MAXVERTEXNUM][MAXVERTEXNUM]; // 邻接矩阵(二维数组)
int vertexnum, edgenum; // 图的顶点数和边数
}adjacencymatrix;
//找某一个顶点在顶点表中的位置
int locate(adjacencymatrix G, vertextype vertex) {
for (int i = 0; i < G.vertexnum; i++)
if (G.vertexes[i] == vertex)
return i;
return -1;
}
//创建无向图(邻接矩阵)
void CreatUndirectedGraph(adjacencymatrix* G) {
//输入顶点数和边数
printf("请输入无向图的顶点数和边数(用空格分隔开):");
scanf("%d %d", &((*G).vertexnum), &((*G).edgenum));
//输入顶点信息,创建顶点表
printf("请依次输入顶点,如 a\n");
for (int i = 0; i < (*G).vertexnum; i++) {
printf("请输入第 %d 个顶点的信息:", i + 1);
scanf(" %c", &((*G).vertexes[i]));
}
//初始化邻接矩阵,全部权值均为0
for (int i = 0; i < (*G).edgenum; i++)
for (int j = 0; j < (*G).edgenum; j++)
(*G).edges[i][j] = 0;
//输入每条边所依附的两个点,创建邻接矩阵
printf("请依次输入每一条边依附的两个顶点,如 a b(用空格分隔开)\n");
for (int i = 0; i < (*G).edgenum; i++) {
printf("请输入第 %d 条边依附的两个顶点:", i + 1);
vertextype vertex1, vertex2;
int location1, location2;
scanf(" %c %c", &vertex1, &vertex2);
location1 = locate(*G, vertex1);
location2 = locate(*G, vertex2);
(*G).edges[location1][location2] = 1; //赋予权值 1
(*G).edges[location2][location1] = 1; //对称位置赋予相同权值 1
}
}
//打印邻接矩阵
void print(adjacencymatrix G) {
for (int i = 0; i < G.vertexnum; i++) {
for (int j = 0; j < G.vertexnum; j++) {
if (j != G.vertexnum - 1)
printf("%d\t", G.edges[i][j]);
else
printf("%d\n", G.edges[i][j]);
}
}
}
int main(void) {
adjacencymatrix G;
CreatUndirectedGraph(&G);
print(G);
return 0;
}

执行结果:

请输入无向图的顶点数和边数(用空格分隔开):5 6
请依次输入顶点,如 a
请输入第 1 个顶点的信息:a
请输入第 2 个顶点的信息:b
请输入第 3 个顶点的信息:c
请输入第 4 个顶点的信息:d
请输入第 5 个顶点的信息:e
请依次输入每一条边依附的两个顶点,如 a b(用空格分隔开)
请输入第 1 条边依附的两个顶点:a b
请输入第 2 条边依附的两个顶点:a d
请输入第 3 条边依附的两个顶点:b c
请输入第 4 条边依附的两个顶点:b e
请输入第 5 条边依附的两个顶点:c d
请输入第 6 条边依附的两个顶点:c e
0 1 0 1 0
1 0 1 0 1
0 1 0 1 1
1 0 1 0 0
0 1 1 0 0

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

只需要在无向网的代码的基础上删除一句话,即删除在构建邻接矩阵时给对称位置赋予相同权值的那句话即可。

代码:

//采用邻接矩阵表示法创建有向网
#include <stdio.h>
//图/网的邻接矩阵的存储表示(适用于图/网、有向/无向)
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 假设顶点的数据类型为 char
typedef int edgetype; // 假设边的权值类型为 int
//邻接矩阵
typedef struct adjacencymatrix {
vertextype vertexes[MAXVERTEXNUM]; // 顶点数组(一维数组)
edgetype edges[MAXVERTEXNUM][MAXVERTEXNUM]; // 邻接矩阵(二维数组)
int vertexnum, edgenum; // 图的顶点数和边数
}adjacencymatrix;
//找某一个顶点在顶点表中的位置
int locate(adjacencymatrix G, vertextype vertex) {
for (int i = 0; i < G.vertexnum; i++)
if (G.vertexes[i] == vertex)
return i;
return -1;
}
//创建有向网(邻接矩阵)
void CreatDirectedNet(adjacencymatrix* G) {
//输入顶点数和边数
printf("请输入有向网的顶点数和边数(用空格分隔开):");
scanf("%d %d", &((*G).vertexnum), &((*G).edgenum));
//输入顶点信息,创建顶点表
printf("请依次输入顶点,如 a\n");
for (int i = 0; i < (*G).vertexnum; i++) {
printf("请输入第 %d 个顶点的信息:", i + 1);
scanf(" %c", &((*G).vertexes[i]));
}
//初始化邻接矩阵,全部权值均为最大值
for (int i = 0; i < (*G).edgenum; i++)
for (int j = 0; j < (*G).edgenum; j++)
(*G).edges[i][j] = MAXINT;
//输入每条边所依附的两个点以及该边的权值,创建邻接矩阵
printf("请依次输入每一条边依附的两个顶点以及该边的权值,如 a b 9(用空格分隔开)\n");
for (int i = 0; i < (*G).edgenum; i++) {
printf("请输入第 %d 条边依附的两个顶点以及该边的权值:", i + 1);
vertextype vertex1, vertex2;
int location1, location2;
edgetype weight;
scanf(" %c %c %d", &vertex1, &vertex2, &weight);
location1 = locate(*G, vertex1);
location2 = locate(*G, vertex2);
(*G).edges[location1][location2] = weight; //赋予权值
}
}
//打印邻接矩阵
void print(adjacencymatrix G) {
for (int i = 0; i < G.vertexnum; i++) {
for (int j = 0; j < G.vertexnum; j++) {
if (G.edges[i][j] != MAXINT) {
if (j != G.vertexnum - 1)
printf("%d\t", G.edges[i][j]);
else
printf("%d\n", G.edges[i][j]);
}
else {
if (j != G.vertexnum - 1)
printf("∞\t");
else
printf("∞\n");
}
}
}
}
int main(void) {
adjacencymatrix G;
CreatDirectedNet(&G);
print(G);
return 0;
}

执行结果:

请输入有向网的顶点数和边数(用空格分隔开):5 6
请依次输入顶点,如 a
请输入第 1 个顶点的信息:a
请输入第 2 个顶点的信息:b
请输入第 3 个顶点的信息:c
请输入第 4 个顶点的信息:d
请输入第 5 个顶点的信息:e
请依次输入每一条边依附的两个顶点以及该边的权值,如 a b 9(用空格分隔开)
请输入第 1 条边依附的两个顶点以及该边的权值:a b 1
请输入第 2 条边依附的两个顶点以及该边的权值:a d 2
请输入第 3 条边依附的两个顶点以及该边的权值:b c 3
请输入第 4 条边依附的两个顶点以及该边的权值:b e 5
请输入第 5 条边依附的两个顶点以及该边的权值:c d 4
请输入第 6 条边依附的两个顶点以及该边的权值:c e 6
∞ 1 ∞ 2 ∞
∞ ∞ 3 ∞ 5
∞ ∞ ∞ 4 6
∞ ∞ ∞ ∞ ∞
∞ ∞ ∞ ∞ ∞

采用邻接矩阵表示法创建有向图

在无向图的基础上删掉在构建邻接矩阵时给对称位置赋值 1 的语句。

代码:

//采用邻接矩阵表示法创建有向图
#include <stdio.h>
//图/网的邻接矩阵的存储表示(适用于图/网、有向/无向)
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 假设顶点的数据类型为 char
typedef int edgetype; // 假设边的权值类型为 int
//邻接矩阵
typedef struct adjacencymatrix {
vertextype vertexes[MAXVERTEXNUM]; // 顶点数组(一维数组)
edgetype edges[MAXVERTEXNUM][MAXVERTEXNUM]; // 邻接矩阵(二维数组)
int vertexnum, edgenum; // 图的顶点数和边数
}adjacencymatrix;
//找某一个顶点在顶点表中的位置
int locate(adjacencymatrix G, vertextype vertex) {
for (int i = 0; i < G.vertexnum; i++)
if (G.vertexes[i] == vertex)
return i;
return -1;
}
//创建有向图(邻接矩阵)
void CreatDirectedGraph(adjacencymatrix* G) {
//输入顶点数和边数
printf("请输入有向图的顶点数和边数(用空格分隔开):");
scanf("%d %d", &((*G).vertexnum), &((*G).edgenum));
//输入顶点信息,创建顶点表
printf("请依次输入顶点,如 a\n");
for (int i = 0; i < (*G).vertexnum; i++) {
printf("请输入第 %d 个顶点的信息:", i + 1);
scanf(" %c", &((*G).vertexes[i]));
}
//初始化邻接矩阵,全部权值均为0
for (int i = 0; i < (*G).edgenum; i++)
for (int j = 0; j < (*G).edgenum; j++)
(*G).edges[i][j] = 0;
//输入每条边所依附的两个点,创建邻接矩阵
printf("请依次输入每一条边依附的两个顶点,如 a b(用空格分隔开)\n");
for (int i = 0; i < (*G).edgenum; i++) {
printf("请输入第 %d 条边依附的两个顶点:", i + 1);
vertextype vertex1, vertex2;
int location1, location2;
scanf(" %c %c", &vertex1, &vertex2);
location1 = locate(*G, vertex1);
location2 = locate(*G, vertex2);
(*G).edges[location1][location2] = 1; //赋予权值 1
}
}
//打印邻接矩阵
void print(adjacencymatrix G) {
for (int i = 0; i < G.vertexnum; i++) {
for (int j = 0; j < G.vertexnum; j++) {
if (j != G.vertexnum - 1)
printf("%d\t", G.edges[i][j]);
else
printf("%d\n", G.edges[i][j]);
}
}
}
int main(void) {
adjacencymatrix G;
CreatDirectedGraph(&G);
print(G);
return 0;
}

执行结果:

请输入有向图的顶点数和边数(用空格分隔开):5 6
请依次输入顶点,如 a
请输入第 1 个顶点的信息:a
请输入第 2 个顶点的信息:b
请输入第 3 个顶点的信息:c
请输入第 4 个顶点的信息:d
请输入第 5 个顶点的信息:e
请依次输入每一条边依附的两个顶点,如 a b(用空格分隔开)
请输入第 1 条边依附的两个顶点:a b
请输入第 2 条边依附的两个顶点:a d
请输入第 3 条边依附的两个顶点:b c
请输入第 4 条边依附的两个顶点:b e
请输入第 5 条边依附的两个顶点:c d
请输入第 6 条边依附的两个顶点:c e
0 1 0 1 0
0 0 1 0 1
0 0 0 1 1
0 0 0 0 0
0 0 0 0 0

邻接矩阵表示法的优缺点

(1) 优点

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

(2) 缺点

  1. 不便于增加和删除顶点。
  2. 不便于统计边的数目, 需要扫描邻接矩阵所有元素才能统计完毕, 时间复杂度为 O(n2)
  3. 空间复杂度高。如果是有向图, n 个顶点需要 n2 个单元存储边。如果是无向图, 因其邻接矩阵是对称的, 所以对规模较大的邻接矩阵可以采用压缩存储的方法, 仅存储下三角 (或上三角) 的元素, 这样需要 n(n-1)/2 个单元即可。但无论以何种方式存储, 邻接矩阵表表示法的空间复杂度均为 O(n2), 这对于稀疏图而言尤其浪费空间。

邻接表

邻接表(Adjacency List)表示法是图的一种链式存储结构。

在邻接表中,对图中每一个顶点 vi 建立一个单链表,把与 vi 相邻的顶点放在这个链表中。邻接表中的每一个单链表的第一个结点都存放着有关顶点的信息,把这一个结点看成是链表的表头,其余结点存放有关边的信息,这样邻接表便有两部分组成:表头结点表边表

邻接表不唯一,链表中先取哪一个元素,后取哪一个元素无所谓。

表头结点表:所有的表头结点以顺序表的形式存储,以便可以随机地访问任一顶点的边链表,即顶点储存在一维数组中。表头结点包括数据域(data)链域(FirstEdge)两个部分。数据域用于存储顶点 vi 的名称或者其他相关信息。链域用于指向链表中的第一个结点(即与顶点 vi 相邻接的第一个邻接点)。

边表:边链表中边结点包括邻接点域(AdjacencyVertex)数据域(info)链域(NextEdge)三部分。邻接点域指明与顶点 vi 邻接的点在图中的位置(其实应该是在表头结点表中的索引)。数据域存储和边相关的信息,如权值。链域指示与顶点 vi 邻接的其他边的结点。单链表中的每一个结点都表示了一个邻接关系,即图中的一条边,这条边的一个点是这个单链表的头结点所指示出的表头结点表中的顶点,这条边的另一个点就是这个单链表的结点的邻接点域(AdjacencyVertex)所指示出的表头结点表中的对应索引的顶点。

表头结点表中的元素称为表头结点头结点,边表中的结点称为边结点

顶点的个数、表头结点表中的元素个数以及边表中的单链表的个数三者相同。

在无向图中,若有 n 个顶点和 e 条边,则邻接表的表头结点表有 n 个表头结点,边表中一共有 2e 个结点,因为每一条边,即每一个邻接关系,都在单链表中存储了两次。

无向图中顶点 vi 的度为第 i 个单链表中的结点数(不包括头结点)。

有向图中,若有 n 个顶点和 e 条边,则邻接表的表头结点表有 n 个表头结点,边表中一共有 e 个结点,因为每一条边,即每一个邻接关系,都在单链表中存储了一次。

有向图的邻接表的表头结点表中的每一个顶点,都是弧尾逆邻接表的表头结点表中的每一个顶点,都是弧头

根据上述讨论, 要定义一个邻接表, 需要先定义其存放顶点的头结点和表示边的边结点

图的邻接表存储表示

//图的邻接表存储表示
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 顶点的数据类型
typedef int otherinfotype; // 边的其他信息的数据类型
//边结点类型
typedef struct edgenode {
int adjacencyvertex; // 边所依赖的两个顶点中的另一个在表头结点表中的索引(其中一个就是头结点)
otherinfotype info; // 该边的其他信息
struct edgenode* nextedge; // 一个链域,指向下一条边
}edgenode;
//表头结点类型
typedef struct vertexnode {
vertextype vertex; // 顶点数据
edgenode* firstedge; // 一个链域,指向第一条依附该顶点的边
}vertexnode;
//邻接表
typedef struct adjacencylist {
vertexnode vertexes[MAXVERTEXNUM]; // 表头结点表
int vertexnum, edgenum; // 顶点数和边数
}adjacencylist;

基于上述的邻接表表示法,要创建一个图则需要创建其相应的顶点表(即表头结点表)和边表。

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

【算法步骤】

  1. 输入顶点数和边数。
  2. 建立顶点表:依次输入顶点的信息存入顶点表中,使每个表头结点的指针域初始化为 NULL。
  3. 创建邻接表:依次输入每条边依附的两个顶点,确定这两个顶点的序号 i 和 j 之后,建立边结点,将此边结点分别插入 vivj 对应的两个边链表的头部。(头插法)(因为是无向图,所以要插两次)

【算法描述】

//(算法6.2)采用邻接表创建无向图
#include <stdio.h>
#include <stdlib.h>
//图的邻接表存储表示
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 顶点的数据类型
typedef int otherinfotype; // 边的其他信息的数据类型
//边结点类型
typedef struct edgenode {
int adjacencyvertex; // 边所依赖的两个顶点中的另一个在表头结点表中的索引(其中一个就是头结点)
otherinfotype info; // 该边的其他信息
struct edgenode* nextedge; // 一个链域,指向下一条边
}edgenode;
//表头结点类型
typedef struct vertexnode {
vertextype vertex; // 顶点数据
edgenode* firstedge; // 一个链域,指向第一条依附该顶点的边
}vertexnode;
//邻接表
typedef struct adjacencylist {
vertexnode vertexes[MAXVERTEXNUM]; // 表头结点表
int vertexnum, edgenum; // 顶点数和边数
}adjacencylist;
//找某一个顶点的表头结点表中的索引
int locate(adjacencylist G, vertextype v) {
for (int i = 0; i < G.vertexnum; i++)
if (G.vertexes[i].vertex == v)
return i;
return -1;
}
//创建无向图(邻接表)
void CreatUndirectedGraph(adjacencylist* G) {
//输入顶点数和边数
printf("请输入顶点数和边数(用空格分隔开):");
scanf("%d %d", &((*G).vertexnum), &((*G).edgenum));
//输入顶点信息,创建表头结点表
printf("请依次输入顶点的信息,如 a\n");
for (int i = 0; i < (*G).vertexnum; i++) {
printf("请输入第 %d 个顶点的信息:", i + 1);
scanf(" %c", &((*G).vertexes[i].vertex));
(*G).vertexes[i].firstedge = NULL; // 每一个表头结点的链域都初始化为空
}
//构建邻接表
printf("请依次输入每一条边依附的两个顶点,如 a b\n");
for (int i = 0; i < (*G).edgenum; i++) {
vertextype v1, v2;
int location1, location2;
printf("请输入第 %d 条边依附的两个顶点:", i + 1);
scanf(" %c %c", &v1, &v2);
location1 = locate(*G, v1); // 找出这两个顶点在表头结点表中的索引
location2 = locate(*G, v2);
edgenode* tmpedge1 = (edgenode*)malloc(sizeof(edgenode)); // 新建一个边结点,准备插入到链表中,采用头插法
(*tmpedge1).adjacencyvertex = location2;
(*tmpedge1).nextedge = (*G).vertexes[location1].firstedge;
(*G).vertexes[location1].firstedge = tmpedge1;
edgenode* tmpedge2 = (edgenode*)malloc(sizeof(edgenode)); // 新建另一个边结点,插到另一个链表中,因为是无向的,所以一条边要出现两次,因此要插入两次
(*tmpedge2).adjacencyvertex = location1;
(*tmpedge2).nextedge = (*G).vertexes[location2].firstedge;
(*G).vertexes[location2].firstedge = tmpedge2;
}
}
void print(adjacencylist G) {
for (int i = 0; i < G.vertexnum; i++) {
printf("%c", G.vertexes[i].vertex); // 先打印表头结点表的顶点
if (G.vertexes[i].firstedge != NULL) { // 如果后面有链表,就接着打印链表,直到链表结束
edgenode* tmp = G.vertexes[i].firstedge;
while (tmp != NULL) {
printf(" --> %d", tmp->adjacencyvertex);
tmp = tmp->nextedge;
}
}
printf("\n");
}
}
int main(void) {
adjacencylist G;
CreatUndirectedGraph(&G);
print(G);
return 0;
}

执行结果:

请输入顶点数和边数(用空格分隔开):5 6
请依次输入顶点的信息,如 a
请输入第 1 个顶点的信息:a
请输入第 2 个顶点的信息:b
请输入第 3 个顶点的信息:c
请输入第 4 个顶点的信息:d
请输入第 5 个顶点的信息:e
请依次输入每一条边依附的两个顶点,如 a b
请输入第 1 条边依附的两个顶点:a b
请输入第 2 条边依附的两个顶点:a d
请输入第 3 条边依附的两个顶点:b e
请输入第 4 条边依附的两个顶点:b c
请输入第 5 条边依附的两个顶点:c d
请输入第 6 条边依附的两个顶点:c e
a --> 3 --> 1
b --> 2 --> 4 --> 0
c --> 4 --> 3 --> 1
d --> 2 --> 0
e --> 2 --> 1

该算法的时间复杂度是 O(n+e)。建立有向图的邻接表与此类似, 只是更加简单, 每读入一个顶点对序号 <i, j>, 仅需生成个邻接点序号为 j 的边表结点, 并将其插入到 vi 的边链表头头部即可。若要创建网的邻接表, 可以将边的权值存储在 info 域中。

值得注意的是, 一个图的邻接矩阵表示是唯一的, 但其其邻接表表示不唯一, 这是因为邻接表表示中, 各边表结点的链接次序取决于建立邻接表的算法, 以及边的输入次序。

创建有向图的邻接表

只需要在创建无向图的代码中删除第二次插入结点的那几条语句即可。

//采用邻接表创建有向图
#include <stdio.h>
#include <stdlib.h>
//图的邻接表存储表示
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 顶点的数据类型
typedef int otherinfotype; // 边的其他信息的数据类型
//边结点类型
typedef struct edgenode {
int adjacencyvertex; // 边所依赖的两个顶点中的另一个在表头结点表中的索引(其中一个就是头结点)
otherinfotype info; // 该边的其他信息
struct edgenode* nextedge; // 一个链域,指向下一条边
}edgenode;
//表头结点类型
typedef struct vertexnode {
vertextype vertex; // 顶点数据
edgenode* firstedge; // 一个链域,指向第一条依附该顶点的边
}vertexnode;
//邻接表
typedef struct adjacencylist {
vertexnode vertexes[MAXVERTEXNUM]; // 表头结点表
int vertexnum, edgenum; // 顶点数和边数
}adjacencylist;
//找某一个顶点的表头结点表中的索引
int locate(adjacencylist G, vertextype v) {
for (int i = 0; i < G.vertexnum; i++)
if (G.vertexes[i].vertex == v)
return i;
return -1;
}
//创建有向图(邻接表)
void CreatDirectedGraph(adjacencylist* G) {
//输入顶点数和边数
printf("请输入顶点数和边数(用空格分隔开):");
scanf("%d %d", &((*G).vertexnum), &((*G).edgenum));
//输入顶点信息,创建表头结点表
printf("请依次输入顶点的信息,如 a\n");
for (int i = 0; i < (*G).vertexnum; i++) {
printf("请输入第 %d 个顶点的信息:", i + 1);
scanf(" %c", &((*G).vertexes[i].vertex));
(*G).vertexes[i].firstedge = NULL; // 每一个表头结点的链域都初始化为空
}
//构建邻接表
printf("请依次输入每一条边依附的两个顶点,如 a b\n");
for (int i = 0; i < (*G).edgenum; i++) {
vertextype v1, v2;
int location1, location2;
printf("请输入第 %d 条边依附的两个顶点:", i + 1);
scanf(" %c %c", &v1, &v2);
location1 = locate(*G, v1); // 找出这两个顶点在表头结点表中的索引
location2 = locate(*G, v2);
edgenode* tmpedge1 = (edgenode*)malloc(sizeof(edgenode)); // 新建一个边结点,准备插入到链表中,采用头插法
(*tmpedge1).adjacencyvertex = location2;
(*tmpedge1).nextedge = (*G).vertexes[location1].firstedge;
(*G).vertexes[location1].firstedge = tmpedge1;
}
}
void print(adjacencylist G) {
for (int i = 0; i < G.vertexnum; i++) {
printf("%c", G.vertexes[i].vertex); // 先打印表头结点表的顶点
if (G.vertexes[i].firstedge != NULL) { // 如果后面有链表,就接着打印链表,直到链表结束
edgenode* tmp = G.vertexes[i].firstedge;
while (tmp != NULL) {
printf(" --> %d", tmp->adjacencyvertex);
tmp = tmp->nextedge;
}
}
printf("\n");
}
}
int main(void) {
adjacencylist G;
CreatDirectedGraph(&G);
print(G);
return 0;
}

执行结果:

请输入顶点数和边数(用空格分隔开):4 4
请依次输入顶点的信息,如 a
请输入第 1 个顶点的信息:1
请输入第 2 个顶点的信息:2
请输入第 3 个顶点的信息:3
请输入第 4 个顶点的信息:4
请依次输入每一条边依附的两个顶点,如 a b
请输入第 1 条边依附的两个顶点:1 2
请输入第 2 条边依附的两个顶点:1 3
请输入第 3 条边依附的两个顶点:3 4
请输入第 4 条边依附的两个顶点:4 1
1 --> 2 --> 1
2
3 --> 3
4 --> 0

示意图:

创建有向图的逆邻接表

只需要修改插入边结点的位置即可。

//采用逆邻接表创建有向图
#include <stdio.h>
#include <stdlib.h>
//图的邻接表存储表示
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 顶点的数据类型
typedef int otherinfotype; // 边的其他信息的数据类型
//边结点类型
typedef struct edgenode {
int adjacencyvertex; // 边所依赖的两个顶点中的另一个在表头结点表中的索引(其中一个就是头结点)
otherinfotype info; // 该边的其他信息
struct edgenode* nextedge; // 一个链域,指向下一条边
}edgenode;
//表头结点类型
typedef struct vertexnode {
vertextype vertex; // 顶点数据
edgenode* firstedge; // 一个链域,指向第一条依附该顶点的边
}vertexnode;
//邻接表
typedef struct adjacencylist {
vertexnode vertexes[MAXVERTEXNUM]; // 表头结点表
int vertexnum, edgenum; // 顶点数和边数
}adjacencylist;
//找某一个顶点的表头结点表中的索引
int locate(adjacencylist G, vertextype v) {
for (int i = 0; i < G.vertexnum; i++)
if (G.vertexes[i].vertex == v)
return i;
return -1;
}
//创建有向图(逆邻接表)
void CreatDirectedGraph(adjacencylist* G) {
//输入顶点数和边数
printf("请输入顶点数和边数(用空格分隔开):");
scanf("%d %d", &((*G).vertexnum), &((*G).edgenum));
//输入顶点信息,创建表头结点表
printf("请依次输入顶点的信息,如 a\n");
for (int i = 0; i < (*G).vertexnum; i++) {
printf("请输入第 %d 个顶点的信息:", i + 1);
scanf(" %c", &((*G).vertexes[i].vertex));
(*G).vertexes[i].firstedge = NULL; // 每一个表头结点的链域都初始化为空
}
//构建逆邻接表
printf("请依次输入每一条边依附的两个顶点,如 a b\n");
for (int i = 0; i < (*G).edgenum; i++) {
vertextype v1, v2;
int location1, location2;
printf("请输入第 %d 条边依附的两个顶点:", i + 1);
scanf(" %c %c", &v1, &v2);
location1 = locate(*G, v1); // 找出这两个顶点在表头结点表中的索引
location2 = locate(*G, v2);
edgenode* tmpedge1 = (edgenode*)malloc(sizeof(edgenode)); // 新建一个边结点,准备插入到链表中,采用头插法
(*tmpedge1).adjacencyvertex = location1;
(*tmpedge1).nextedge = (*G).vertexes[location2].firstedge;
(*G).vertexes[location2].firstedge = tmpedge1;
}
}
void print(adjacencylist G) {
for (int i = 0; i < G.vertexnum; i++) {
printf("%c", G.vertexes[i].vertex); // 先打印表头结点表的顶点
if (G.vertexes[i].firstedge != NULL) { // 如果后面有链表,就接着打印链表,直到链表结束
edgenode* tmp = G.vertexes[i].firstedge;
while (tmp != NULL) {
printf(" --> %d", tmp->adjacencyvertex);
tmp = tmp->nextedge;
}
}
printf("\n");
}
}
int main(void) {
adjacencylist G;
CreatDirectedGraph(&G);
print(G);
return 0;
}

执行结果:

请输入顶点数和边数(用空格分隔开):4 4
请依次输入顶点的信息,如 a
请输入第 1 个顶点的信息:1
请输入第 2 个顶点的信息:2
请输入第 3 个顶点的信息:3
请输入第 4 个顶点的信息:4
请依次输入每一条边依附的两个顶点,如 a b
请输入第 1 条边依附的两个顶点:1 2
请输入第 2 条边依附的两个顶点:1 3
请输入第 3 条边依附的两个顶点:3 4
请输入第 4 条边依附的两个顶点:4 1
1 --> 3
2 --> 0
3 --> 0
4 --> 2

示意图:

邻接表表示法的优缺点

(1) 优点

  1. 便于增加和删除顶点。
  2. 便于统计边的数目, 按顶点表顺序扫描所有边表可得到过边的数目, 时间复杂度为 O(n + e)。空间效率高。对于一个具有 n 个顶点 e 条边的图 G, 若 G 是无向图, 则在其邻接表表示中有 n 个顶点表结点和 2e 个边表结点; 若 G 是有向图, 则在它的邻接表表示或逆邻接表表示中均有 n 个顶点表结点和 e 个边表结点。因此, 邻接表或逆邻接表表示的空间复杂度为 O(n+e),适合表示稀疏图。对于稠密图, 考虑到邻接表中要附加链域 ,因此常采取邻接矩阵表示法。

(2) 缺点

  1. 不便于判断顶点之间是否有边, 要判定 vivj 之间是否有边, 就需扫描第 i 个边表, 最坏情况下要耗费 O(n) 时间。
  2. 不便于计算有向图各个顶点的度。对于无向图, 在邻接表表示中顶点 vi 的度是第 i 个边表中的结点个数。在有向图的邻接表中, 第 i 个边表上的结点个数是顶点 vi 的出度, 但求 vi 的入度较困难, 需遍历各顶点的边表。若有向图采用逆邻接表表示, 则与邻接表表示相反, 求顶点的入度容易, 而求顶点的出度较难。

邻接矩阵与邻接表之间的关系:

十字链表

十字链表(Orthogonal List)是有向图的另一种链式存储结构。可以看成是将有向图的邻接表和逆邻接表结合起来得到的一种链表。在十字链表中,对应于有向图中的每一条弧都有一个结点,对应于有向图中的每一个顶点,也都有一个结点。

弧结点和顶点结点的结构如图所示:

弧结点有 5 个域:

尾域(tailvex):指示弧尾顶点在图中的位置。

头域(headvex):指示弧头顶点在图中的位置。

链域(hlink):指向弧头相同的下一条弧。

链域(tlink):指向弧尾相同的下一条弧。

info 域:指示该弧的相关信息。

顶点域由三个域组成:

data 域:存储和顶点有关的信息,如顶点的名称等。

firstin 域和 firstout 域:这是两个链域,分别指向以这个顶点为弧头和弧尾的第一个弧结点。

弧头相同的弧,在同一链表上,弧尾相同的弧,也在同一链表上,它们的头结点即为顶点结点。

顶点结点是顺序存储的。

十字链表的存储表示(有向图)

// 十字链表的存储表示(有向图)
#define MAX_VERTEX_NUM 20
typedef int InfoType; // 弧的其他信息的类型,可根据具体类型进行更改
typedef char VertexType; // 顶点的数据类型
// 弧结点
typedef struct ArcNode {
int tailvex, headvex; // 弧所依附的尾顶点和头顶点
struct ArcNode* hlink, * tlink; // 两个链域分别指向下一条弧头相同的弧和下一条弧尾相同的弧
InfoType* info; // 与这条弧相关的其他信息
}ArcNode;
// 顶点结点
typedef struct VexNode {
VertexType data; // 顶点的数据域
ArcNode* firstin, * firstout; // 两个链域分别指向该顶点的第一条入弧和第一条出弧
}VexNode;
// 十字链表
typedef struct OLG {
VexNode xlist[MAX_VERTEX_NUM]; // 顶点数组
int vexnum, arcnum; // 有向图的当前的顶点数目和边数目
}OLG;

示例 1:

示例 2:

只要输入 n 个顶点的信息和 e 条弧的信息,便可建立该有向图的十字链表。

建立十字链表的时间复杂度和建立邻接表是相同的。

在十字链表中既容易找到以 vi 为尾的弧,也容易找到以 vi 为头的弧,因而容易求的顶点的入度和出度。(或需要,可在建立十字链表的同时求出)。

邻接多重表

邻接多重表(Adjacency Multilist)是无向图的另一种链式存储结构。

邻接多重表中,每一条边用一个结点表示。边结点有 6 个域。

mark:标志域,可以用来标记该边是否被搜索过。

ivex,jvex:该边依附的两个顶点在图中的位置。

ilink:指向下一条依附于顶点 ivex 的边。

jlink:指向下一条依附于顶点 jvex 的边。

info:指向和边有关的各种信息的指针域。

每一个顶点也用一个结点来表示,每一个顶点结点有 2 个域。

data:存储和该顶点有关的信息。

firstedge:指示第一条依附于该顶点的边。

示例 1:

示例 2:

在邻接多重表中, 所有依附于同一顶点的边串联在同一链表中, 由于每条边依附于两个顶点, 则每个边结点同时链接在两个链表中。可见, 对无向图而言, 其邻接多重表和邻接表的差别, 仅仅在于同一条边在邻接表中用两个结点表示, 而在邻接多重表中只有一个结点。因此, 除了在边结点中增加一个标志域外, 邻接多重表所需的的存储量和邻接表相同。

邻接多重表的存储表示(无向图)

// 邻接多重表的存储表示(无向图)
#define MAX_VERTEX_NUM 20
typedef int InfoType; // 弧的其他信息的类型,可根据具体类型进行更改
typedef char VertexType; // 顶点的数据类型
typedef enum { unvisited, visited } VisitIf;
// 弧结点
typedef struct ArcNode {
VisitIf mark;
int ivex, jvex; // 边所依赖的两个顶点的位置
struct ArcNode* ilink, * jlink; // 分别指向依附这两个顶点的下一条边
InfoType* info; // 该边的信息指针
}ArcNode;
// 顶点结点
typedef struct VexNode {
VertexType data; // 顶点的数据域
ArcNode* firstedget; // 指向第一条依附该顶点的边
}VexNode;
// 十字链表
typedef struct AMG {
VexNode xlist[MAX_VERTEX_NUM]; // 顶点数组
int vexnum, arcnum; // 有向图的当前的顶点数目和边数目
}AMG;

图的遍历

图的遍历是从图中的某一个顶点出发,按照某种方法对图中所有的顶点进行访问且仅访问一次。

为了避免同一个顶点被访问多次,在遍历图的过程中,必须记下每一个已经访问过的顶点。为此,设一个辅助数组 visited[n],其初始值置为 false 或者 0,一旦访问了顶点 vi,便置 visited[i] 为 true 或者 1。

根据搜索路径的方向,通常有两条遍历图的路径:深度优先搜索和广度优先搜索。它们对于无向图和有向图都适用。

深度优先搜索

深度优先搜索(Depth First Search, DFS)遍历类似于树的先序遍历,是树的先序遍历的推广。

对于一个连通图,深度优先搜索遍历的过程如下:

  1. 从图中某一个顶点 v 出发,访问 v。

  2. 找出刚刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新的顶点,重复此步骤,直到刚刚访问的顶点没有未被访问的邻接点为止。

  3. 返回前一个已经被访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点。

  4. 重复步骤 2 和 3,直至图中所有顶点都被访问过,搜索结束。

深度优先搜索遍历连通图是一个递归的过程,为了在遍历的过程中便于区分顶点是否已经被访问,需要附设访问标志数组 visited[n],其初始值为 false,一旦某一个顶点被访问,则其相应的分量设置为 true。

深度优先、邻接矩阵、连通图、递归算法

//算法6.3 深度优先、邻接矩阵、连通图、递归算法
#include <stdio.h>
#include <stdbool.h>
//图/网的邻接矩阵的存储表示(适用于图/网、有向/无向)
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 假设顶点的数据类型为 char
typedef int edgetype; // 假设边的权值类型为 int
//邻接矩阵
typedef struct adjacencymatrix {
vertextype vertexes[MAXVERTEXNUM]; // 顶点数组(一维数组)
edgetype edges[MAXVERTEXNUM][MAXVERTEXNUM]; // 邻接矩阵(二维数组)
int vertexnum, edgenum; // 图的顶点数和边数
}adjacencymatrix;
//找某一个顶点在顶点表中的位置
int locate(adjacencymatrix G, vertextype vertex) {
for (int i = 0; i < G.vertexnum; i++)
if (G.vertexes[i] == vertex)
return i;
return -1;
}
//创建无向图(邻接矩阵)
void CreatUndirectedGraph(adjacencymatrix* G) {
//输入顶点数和边数
printf("请输入无向图的顶点数和边数(用空格分隔开):");
scanf("%d %d", &((*G).vertexnum), &((*G).edgenum));
//输入顶点信息,创建顶点表
printf("请依次输入顶点,如 a\n");
for (int i = 0; i < (*G).vertexnum; i++) {
printf("请输入第 %d 个顶点的信息:", i + 1);
scanf(" %c", &((*G).vertexes[i]));
}
//初始化邻接矩阵,全部权值均为0
for (int i = 0; i < (*G).edgenum; i++)
for (int j = 0; j < (*G).edgenum; j++)
(*G).edges[i][j] = 0;
//输入每条边所依附的两个点,创建邻接矩阵
printf("请依次输入每一条边依附的两个顶点,如 a b(用空格分隔开)\n");
for (int i = 0; i < (*G).edgenum; i++) {
printf("请输入第 %d 条边依附的两个顶点:", i + 1);
vertextype vertex1, vertex2;
int location1, location2;
scanf(" %c %c", &vertex1, &vertex2);
location1 = locate(*G, vertex1);
location2 = locate(*G, vertex2);
(*G).edges[location1][location2] = 1; //赋予权值 1
(*G).edges[location2][location1] = 1; //对称位置赋予相同权值 1
}
}
//打印邻接矩阵
void print(adjacencymatrix G) {
for (int i = 0; i < G.vertexnum; i++) {
for (int j = 0; j < G.vertexnum; j++) {
if (j != G.vertexnum - 1)
printf("%d\t", G.edges[i][j]);
else
printf("%d\n", G.edges[i][j]);
}
}
}
bool visited[MAXVERTEXNUM];
void DFS_adjacencymatrix(adjacencymatrix G, int start) {
printf("%c\t", G.vertexes[start]);
visited[start] = true;
for (int location = 0; location < G.vertexnum; location++) {
if (G.edges[start][location] == 1 && visited[location] == false) {
DFS_adjacencymatrix(G, location);
}
}
}
int main(void) {
adjacencymatrix G;
CreatUndirectedGraph(&G);
print(G);
vertextype startvertex;
printf("请输入起点: ");
scanf(" %c", &startvertex);
int start = locate(G, startvertex);
DFS_adjacencymatrix(G, start);
return 0;
}

执行结果:

请输入无向图的顶点数和边数(用空格分隔开):8 9
请依次输入顶点,如 a
请输入第 1 个顶点的信息:1
请输入第 2 个顶点的信息:2
请输入第 3 个顶点的信息:3
请输入第 4 个顶点的信息:4
请输入第 5 个顶点的信息:5
请输入第 6 个顶点的信息:6
请输入第 7 个顶点的信息:7
请输入第 8 个顶点的信息:8
请依次输入每一条边依附的两个顶点,如 a b(用空格分隔开)
请输入第 1 条边依附的两个顶点:1 2
请输入第 2 条边依附的两个顶点:1 3
请输入第 3 条边依附的两个顶点:2 4
请输入第 4 条边依附的两个顶点:2 5
请输入第 5 条边依附的两个顶点:3 6
请输入第 6 条边依附的两个顶点:3 7
请输入第 7 条边依附的两个顶点:4 8
请输入第 8 条边依附的两个顶点:5 8
请输入第 9 条边依附的两个顶点:6 7
0 1 1 0 0 0 0 0
1 0 0 1 1 0 0 0
1 0 0 0 0 1 1 0
0 1 0 0 0 0 0 1
0 1 0 0 0 0 0 1
0 0 1 0 0 0 1 0
0 0 1 0 0 1 0 0
0 0 0 1 1 0 0 0
请输入起点: 1
1 2 4 8 5 3 6 7

示意图:

另一种写法:

//算法6.3 深度优先、邻接矩阵、连通图、递归算法
#include <stdio.h>
#include <stdbool.h>
//图/网的邻接矩阵的存储表示(适用于图/网、有向/无向)
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 假设顶点的数据类型为 char
typedef int edgetype; // 假设边的权值类型为 int
//邻接矩阵
typedef struct adjacencymatrix {
vertextype vertexes[MAXVERTEXNUM]; // 顶点数组(一维数组)
edgetype edges[MAXVERTEXNUM][MAXVERTEXNUM]; // 邻接矩阵(二维数组)
int vertexnum, edgenum; // 图的顶点数和边数
}adjacencymatrix;
//找某一个顶点在顶点表中的位置
int locate(adjacencymatrix G, vertextype vertex) {
for (int i = 0; i < G.vertexnum; i++)
if (G.vertexes[i] == vertex)
return i;
return -1;
}
//创建无向图(邻接矩阵)
void CreatUndirectedGraph(adjacencymatrix* G) {
//输入顶点数和边数
printf("请输入无向图的顶点数和边数(用空格分隔开):");
scanf("%d %d", &((*G).vertexnum), &((*G).edgenum));
//输入顶点信息,创建顶点表
printf("请依次输入顶点,如 a\n");
for (int i = 0; i < (*G).vertexnum; i++) {
printf("请输入第 %d 个顶点的信息:", i + 1);
scanf(" %c", &((*G).vertexes[i]));
}
//初始化邻接矩阵,全部权值均为0
for (int i = 0; i < (*G).edgenum; i++)
for (int j = 0; j < (*G).edgenum; j++)
(*G).edges[i][j] = 0;
//输入每条边所依附的两个点,创建邻接矩阵
printf("请依次输入每一条边依附的两个顶点,如 a b(用空格分隔开)\n");
for (int i = 0; i < (*G).edgenum; i++) {
printf("请输入第 %d 条边依附的两个顶点:", i + 1);
vertextype vertex1, vertex2;
int location1, location2;
scanf(" %c %c", &vertex1, &vertex2);
location1 = locate(*G, vertex1);
location2 = locate(*G, vertex2);
(*G).edges[location1][location2] = 1; //赋予权值 1
(*G).edges[location2][location1] = 1; //对称位置赋予相同权值 1
}
}
//打印邻接矩阵
void print(adjacencymatrix G) {
for (int i = 0; i < G.vertexnum; i++) {
for (int j = 0; j < G.vertexnum; j++) {
if (j != G.vertexnum - 1)
printf("%d\t", G.edges[i][j]);
else
printf("%d\n", G.edges[i][j]);
}
}
}
bool visited[MAXVERTEXNUM];
int firstadjacencyvertex(adjacencymatrix G, int start) {
for (int i = 0; i < G.vertexnum; i++) {
if (G.edges[start][i] == 1 && visited[i] == false)
return i;
}
return -1;
}
int nextadjacencyvertex(adjacencymatrix G, int start, int prior) {
for (int i = prior; i < G.vertexnum; i++) {
if (G.edges[start][i] == 1 && visited[i] == false)
return i;
}
return -1;
}
void DFS_adjacencymatrix(adjacencymatrix G, int start) {
printf("%c\t", G.vertexes[start]);
visited[start] = true;
for (int location = firstadjacencyvertex(G, start); location >= 0; location = nextadjacencyvertex(G, start, location)) {
if (visited[location] == false)
DFS_adjacencymatrix(G, location);
}
}
int main(void) {
adjacencymatrix G;
CreatUndirectedGraph(&G);
print(G);
vertextype startvertex;
printf("请输入起点: ");
scanf(" %c", &startvertex);
int start = locate(G, startvertex);
DFS_adjacencymatrix(G, start);
return 0;
}

执行结果:

请输入无向图的顶点数和边数(用空格分隔开):6 6
请依次输入顶点,如 a
请输入第 1 个顶点的信息:1
请输入第 2 个顶点的信息:2
请输入第 3 个顶点的信息:3
请输入第 4 个顶点的信息:4
请输入第 5 个顶点的信息:5
请输入第 6 个顶点的信息:6
请依次输入每一条边依附的两个顶点,如 a b(用空格分隔开)
请输入第 1 条边依附的两个顶点:1 2
请输入第 2 条边依附的两个顶点:1 3
请输入第 3 条边依附的两个顶点:1 4
请输入第 4 条边依附的两个顶点:2 5
请输入第 5 条边依附的两个顶点:3 5
请输入第 6 条边依附的两个顶点:4 6
0 1 1 1 0 0
1 0 0 0 1 0
1 0 0 0 1 0
1 0 0 0 0 1
0 1 1 0 0 0
0 0 0 1 0 0
请输入起点: 2
2 1 3 5 4 6

示意图:

深度优先、邻接矩阵、非连通图

//算法6.4 深度优先、邻接矩阵、非连通图
#include <stdio.h>
#include <stdbool.h>
//图/网的邻接矩阵的存储表示(适用于图/网、有向/无向)
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 假设顶点的数据类型为 char
typedef int edgetype; // 假设边的权值类型为 int
//邻接矩阵
typedef struct adjacencymatrix {
vertextype vertexes[MAXVERTEXNUM]; // 顶点数组(一维数组)
edgetype edges[MAXVERTEXNUM][MAXVERTEXNUM]; // 邻接矩阵(二维数组)
int vertexnum, edgenum; // 图的顶点数和边数
}adjacencymatrix;
//找某一个顶点在顶点表中的位置
int locate(adjacencymatrix G, vertextype vertex) {
for (int i = 0; i < G.vertexnum; i++)
if (G.vertexes[i] == vertex)
return i;
return -1;
}
//创建无向图(邻接矩阵)
void CreatUndirectedGraph(adjacencymatrix* G) {
//输入顶点数和边数
printf("请输入无向图的顶点数和边数(用空格分隔开):");
scanf("%d %d", &((*G).vertexnum), &((*G).edgenum));
//输入顶点信息,创建顶点表
printf("请依次输入顶点,如 a\n");
for (int i = 0; i < (*G).vertexnum; i++) {
printf("请输入第 %d 个顶点的信息:", i + 1);
scanf(" %c", &((*G).vertexes[i]));
}
//初始化邻接矩阵,全部权值均为0
for (int i = 0; i < (*G).edgenum; i++)
for (int j = 0; j < (*G).edgenum; j++)
(*G).edges[i][j] = 0;
//输入每条边所依附的两个点,创建邻接矩阵
printf("请依次输入每一条边依附的两个顶点,如 a b(用空格分隔开)\n");
for (int i = 0; i < (*G).edgenum; i++) {
printf("请输入第 %d 条边依附的两个顶点:", i + 1);
vertextype vertex1, vertex2;
int location1, location2;
scanf(" %c %c", &vertex1, &vertex2);
location1 = locate(*G, vertex1);
location2 = locate(*G, vertex2);
(*G).edges[location1][location2] = 1; //赋予权值 1
(*G).edges[location2][location1] = 1; //对称位置赋予相同权值 1
}
}
//打印邻接矩阵
void print(adjacencymatrix G) {
for (int i = 0; i < G.vertexnum; i++) {
for (int j = 0; j < G.vertexnum; j++) {
if (j != G.vertexnum - 1)
printf("%d\t", G.edges[i][j]);
else
printf("%d\n", G.edges[i][j]);
}
}
}
bool visited[MAXVERTEXNUM];
void DFS_adjacencymatrix(adjacencymatrix G, int start) {
printf("%c\t", G.vertexes[start]);
visited[start] = true;
for (int location = 0; location < G.vertexnum; location++) {
if (G.edges[start][location] == 1 && visited[location] == false) {
DFS_adjacencymatrix(G, location);
}
}
}
//对非连通图G做深度优先遍历
void DFSTraverse(adjacencymatrix G) {
for (int v = 0; v < G.vertexnum; ++v)
visited[v] = false; //访问标志数组初始化
for (int v = 0; v < G.vertexnum; ++v) //循环调用DFS
if (!visited[v]) DFS_adjacencymatrix(G, v); //对尚未访问的顶点调用DFS
}
int main(void) {
adjacencymatrix G;
CreatUndirectedGraph(&G);
print(G);
printf("非连通图的遍历结果:\n ");
DFSTraverse(G);
return 0;
}

执行结果:

请输入无向图的顶点数和边数(用空格分隔开):14 15
请依次输入顶点,如 a
请输入第 1 个顶点的信息:1
请输入第 2 个顶点的信息:2
请输入第 3 个顶点的信息:3
请输入第 4 个顶点的信息:4
请输入第 5 个顶点的信息:5
请输入第 6 个顶点的信息:6
请输入第 7 个顶点的信息:7
请输入第 8 个顶点的信息:8
请输入第 9 个顶点的信息:a
请输入第 10 个顶点的信息:b
请输入第 11 个顶点的信息:c
请输入第 12 个顶点的信息:d
请输入第 13 个顶点的信息:e
请输入第 14 个顶点的信息:f
请依次输入每一条边依附的两个顶点,如 a b(用空格分隔开)
请输入第 1 条边依附的两个顶点:1 2
请输入第 2 条边依附的两个顶点:1 3
请输入第 3 条边依附的两个顶点:2 4
请输入第 4 条边依附的两个顶点:2 5
请输入第 5 条边依附的两个顶点:3 6
请输入第 6 条边依附的两个顶点:3 7
请输入第 7 条边依附的两个顶点:4 8
请输入第 8 条边依附的两个顶点:5 8
请输入第 9 条边依附的两个顶点:6 7
请输入第 10 条边依附的两个顶点:a b
请输入第 11 条边依附的两个顶点:a c
请输入第 12 条边依附的两个顶点:a d
请输入第 13 条边依附的两个顶点:b e
请输入第 14 条边依附的两个顶点:c e
请输入第 15 条边依附的两个顶点:d f
0 1 1 0 0 0 0 0 0 0 0 0 0 0
1 0 0 1 1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 1 1 0 0 0 0 0 0 0
0 1 0 0 0 0 0 1 0 0 0 0 0 0
0 1 0 0 0 0 0 1 0 0 0 0 0 0
0 0 1 0 0 0 1 0 0 0 0 0 0 0
0 0 1 0 0 1 0 0 0 0 0 0 0 0
0 0 0 1 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 1 1 0 0
0 0 0 0 0 0 0 0 1 0 0 0 1 0
0 0 0 0 0 0 0 0 1 0 0 0 1 0
0 0 0 0 0 0 0 0 1 0 0 0 0 1
0 0 0 0 0 0 0 0 0 1 1 0 0 0
0 0 0 0 0 0 0 0 0 0 0 1 0 0
非连通图的遍历结果:
1 2 4 8 5 3 6 7 a b e c d f

示意图:

采用邻接表表示图的深度优先搜索遍历、递归、连通

代码:

//算法6.6 采用邻接表表示图的深度优先搜索遍历、递归、连通
//(算法6.2)采用邻接表创建无向图
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//图的邻接表存储表示
#define MAXVERTEXNUM 100 // 最大顶点数
typedef char vertextype; // 顶点的数据类型
typedef int otherinfotype; // 边的其他信息的数据类型
//边结点类型
typedef struct edgenode {
int adjacencyvertex; // 边所依赖的两个顶点中的另一个在表头结点表中的索引(其中一个就是头结点)
otherinfotype info; // 该边的其他信息
struct edgenode* nextedge; // 一个链域,指向下一条边
}edgenode;
//表头结点类型
typedef struct vertexnode {
vertextype vertex; // 顶点数据
edgenode* firstedge; // 一个链域,指向第一条依附该顶点的边
}vertexnode;
//邻接表
typedef struct adjacencylist {
vertexnode vertexes[MAXVERTEXNUM]; // 表头结点表
int vertexnum, edgenum; // 顶点数和边数
}adjacencylist;
//找某一个顶点的表头结点表中的索引
int locate(adjacencylist G, vertextype v) {
for (int i = 0; i < G.vertexnum; i++)
if (G.vertexes[i].vertex == v)
return i;
return -1;
}
//创建无向图(邻接表)
void CreatUndirectedGraph(adjacencylist* G) {
//输入顶点数和边数
printf("请输入顶点数和边数(用空格分隔开):");
scanf("%d %d", &((*G).vertexnum), &((*G).edgenum));
//输入顶点信息,创建表头结点表
printf("请依次输入顶点的信息,如 a\n");
for (int i = 0; i < (*G).vertexnum; i++) {
printf("请输入第 %d 个顶点的信息:", i + 1);
scanf(" %c", &((*G).vertexes[i].vertex));
(*G).vertexes[i].firstedge = NULL; // 每一个表头结点的链域都初始化为空
}
//构建邻接表
printf("请依次输入每一条边依附的两个顶点,如 a b\n");
for (int i = 0; i < (*G).edgenum; i++) {
vertextype v1, v2;
int location1, location2;
printf("请输入第 %d 条边依附的两个顶点:", i + 1);
scanf(" %c %c", &v1, &v2);
location1 = locate(*G, v1); // 找出这两个顶点在表头结点表中的索引
location2 = locate(*G, v2);
edgenode* tmpedge1 = (edgenode*)malloc(sizeof(edgenode)); // 新建一个边结点,准备插入到链表中,采用头插法
(*tmpedge1).adjacencyvertex = location2;
(*tmpedge1).nextedge = (*G).vertexes[location1].firstedge;
(*G).vertexes[location1].firstedge = tmpedge1;
edgenode* tmpedge2 = (edgenode*)malloc(sizeof(edgenode)); // 新建另一个边结点,插到另一个链表中,因为是无向的,所以一条边要出现两次,因此要插入两次
(*tmpedge2).adjacencyvertex = location1;
(*tmpedge2).nextedge = (*G).vertexes[location2].firstedge;
(*G).vertexes[location2].firstedge = tmpedge2;
}
}
void print(adjacencylist G) {
for (int i = 0; i < G.vertexnum; i++) {
printf("%c", G.vertexes[i].vertex); // 先打印表头结点表的顶点
if (G.vertexes[i].firstedge != NULL) { // 如果后面有链表,就接着打印链表,直到链表结束
edgenode* tmp = G.vertexes[i].firstedge;
while (tmp != NULL) {
printf(" --> %d", tmp->adjacencyvertex);
tmp = tmp->nextedge;
}
}
printf("\n");
}
}
bool visited[MAXVERTEXNUM];
void DFS_adjacencylist(adjacencylist G, int v) {
printf("%c\t", G.vertexes[v].vertex);
visited[v] = true;
edgenode* p = G.vertexes[v].firstedge;
while (p != NULL) {
int w = p->adjacencyvertex;
if (visited[w] == false)
DFS_adjacencylist(G, w);
p = p->nextedge;
}
}
int main(void) {
adjacencylist G;
CreatUndirectedGraph(&G);
print(G);
printf("请输入起点:");
vertextype start;
scanf(" %c", &start);
int v = locate(G, start);
DFS_adjacencylist(G, v);
return 0;
}

执行结果:

请输入顶点数和边数(用空格分隔开):8 9
请依次输入顶点的信息,如 a
请输入第 1 个顶点的信息:a
请输入第 2 个顶点的信息:b
请输入第 3 个顶点的信息:c
请输入第 4 个顶点的信息:d
请输入第 5 个顶点的信息:e
请输入第 6 个顶点的信息:f
请输入第 7 个顶点的信息:g
请输入第 8 个顶点的信息:h
请依次输入每一条边依附的两个顶点,如 a b
请输入第 1 条边依附的两个顶点:a b
请输入第 2 条边依附的两个顶点:a c
请输入第 3 条边依附的两个顶点:b d
请输入第 4 条边依附的两个顶点:b e
请输入第 5 条边依附的两个顶点:c f
请输入第 6 条边依附的两个顶点:c g
请输入第 7 条边依附的两个顶点:d h
请输入第 8 条边依附的两个顶点:e h
请输入第 9 条边依附的两个顶点:f g
a --> 2 --> 1
b --> 4 --> 3 --> 0
c --> 6 --> 5 --> 0
d --> 7 --> 1
e --> 7 --> 1
f --> 6 --> 2
g --> 5 --> 2
h --> 4 --> 3
请输入起点:a
a c g f b e h d

广度优先搜索

广度优先搜索(Breadth First Search, BFS)遍历类似于树的按层次遍历的过程。

广度优先搜遍历的过程如下。

  1. 从图中某个顶点 v 出发,访问 v。

  2. 依次访问 v 的各个未曾访问过的邻接点。

  3. 分别从这些邻接点出发依次访问它们的邻接点, 并使 “先被访问的顶点的邻接点” 先于 “后被访问的顶点的邻接点” 被访问。重复步骤 3,直至图中所有已被访问的顶点的邻接点都被访问到。

可以看出, 广度优先搜索遍历的特点是: 尽可能先对横向进行搜搜索。设 x 和 y 是两个相继被访问过的顶点, 若当前是以 x 为出发点进行搜索, 则在访问 x 的所有未曾被访问过的邻接点之后, 紧接着是以 y 为出发点进行横向搜索, 并对搜索到的 y 的邻接点中尚未被访问的顶点进行访问。也就是说, 先访问的顶点其邻接点亦先被访问。为此, 算法实现时需引进队列保存已被访问过的顶点。

和深度优先搜索类似,广度优先搜索在遍历的过程中也需要要一个访问标志数组。

广度优先搜索(连通图,无向图,邻接矩阵表示法,非递归)

代码:

//算法6.7 广度优先搜索(连通图,无向图,邻接矩阵表示法,非递归)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define MAXQSIZE 100 //最大队列长度
//图的邻接矩阵存储表示
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAX_VERTEX_NUM 100 // 最大顶点数
typedef char VertexType; // 此处假设顶点的数据类型为 char
typedef int EdgeType; // 此处假设边的权值类型为 int
typedef struct Adjacency_Matrix_Graph {
VertexType vertexes[MAX_VERTEX_NUM]; // 顶点数组(一维数组)
EdgeType edges[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵(二维数组)
int VertexNum, EdgeNum; // 图的顶点数和边数
} Adjacency_Matrix_Graph;
//访问标志数组
bool visited[MAX_VERTEX_NUM]; //访问标志数组,其初值为"false"
//队列的定义及操作
typedef struct SqQueue {
EdgeType* base; //初始化的动态分配存储空间
int front; //头指针,若队列不空,指向队头元素
int rear; //尾指针,若队列不空,指向队尾元素的下一个位置
}SqQueue;
//构造一个空队列Q
void InitQueue(SqQueue* Q) {
(*Q).base = (EdgeType*)malloc(sizeof(EdgeType) * MAXQSIZE);
if (!((*Q).base))
exit(1); //存储分配失败
(*Q).front = (*Q).rear = 0;
}
//插入元素e为Q的新的队尾元素
void EnQueue(SqQueue* Q, EdgeType e) {
if (((*Q).rear + 1) % MAXQSIZE == (*Q).front)
return;
(*Q).base[(*Q).rear] = e;
(*Q).rear = ((*Q).rear + 1) % MAXQSIZE;
}
//判断是否为空队
bool QueueEmpty(SqQueue Q) {
if (Q.rear == Q.front)
return true;
return false;
}
//队头元素出队并置为u
void DeQueue(SqQueue* Q, EdgeType* u) {
(*u) = (*Q).base[(*Q).front];
(*Q).front = ((*Q).front + 1) % MAXQSIZE;
}
//确定点v在G中的位置
int LocateVex(Adjacency_Matrix_Graph G, VertexType v) {
for (int i = 0; i < G.VertexNum; ++i)
if (G.vertexes[i] == v)
return i;
return -1;
}
//采用邻接矩阵表示法,创建无向图G
void Create_Undirected_Graph(Adjacency_Matrix_Graph* G) {
//输入顶点数和边数
printf("请输入顶点数和边数,以空格隔开:");
scanf("%d %d", &(*G).VertexNum, &(*G).EdgeNum);
printf("输入顶点的名称,如 a\n");
//建立顶点表,依次输入顶点的信息存入顶点表中
for (int i = 0; i < (*G).VertexNum; ++i) {
printf("请输入第 %d 个顶点的名称:", i + 1);
scanf(" %c", &((*G).vertexes[i])); //在第一个%c前面加上一个空格,表示跳过输入的内容前面的空白,比如上一次输入留下的换行符
}
//初始化邻接矩阵,使每一个权值初始化为0(在无向网的代码的基础上,修改这里的初始化邻接矩阵的代码,将邻接矩阵初始化为元素全为0)
for (int i = 0; i < (*G).VertexNum; ++i)
for (int j = 0; j < (*G).VertexNum; ++j)
(*G).edges[i][j] = 0;
//构造邻接矩阵
printf("输入边依附的顶点,如 a b \n");
for (int k = 0; k < (*G).EdgeNum; ++k) {
VertexType v1, v2;
int i, j;
printf("请输入第 %d 条边依附的顶点:", k + 1); // 不需要输入权值
scanf(" %c %c", &v1, &v2); //输入一条边依附的顶点,在第一个%c前面加上一个空格,表示跳过输入的内容前面的空白,比如上一次输入留下的换行符
i = LocateVex((*G), v1); //确定v1和v2在G中的位置,即顶点表的下标
j = LocateVex((*G), v2);
(*G).edges[i][j] = 1; //弧<v1, v2>的权值置为1(在无向网的代码的基础上,修改这里的权值为1)
(*G).edges[j][i] = (*G).edges[i][j]; //置<v1, v2>的对称弧<v2, v1>的权值为1
}
}
//返回v的第一个邻接点
int FirstAdjVex(Adjacency_Matrix_Graph G, int v) {
for (int i = 0; i < G.VertexNum; ++i)
if (G.edges[v][i] == 1 && visited[i] == false)
return i;
return -1;
}
//返回v相对于w的下一个邻接点
int NextAdjVex(Adjacency_Matrix_Graph G, int u, int w) {
for (int i = w; i < G.VertexNum; ++i)
if (G.edges[u][i] == 1 && visited[i] == false)
return i;
return -1;
}
//按广度优先非递归遍历连通图G
void BFS(Adjacency_Matrix_Graph G, int v) {
SqQueue Q;
EdgeType u = 0;
EdgeType w = 0;
printf("%c ", G.vertexes[v]);
visited[v] = true; //访问第v个顶点,并置访问标志数组相应分量值为true
InitQueue(&Q); //辅助队列Q初始化,置空
EnQueue(&Q, v); //v进队
while (!QueueEmpty(Q)) { //队列非空
DeQueue(&Q, &u); //队头元素出队并置为u
for (w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w)) {
//依次检查u的所有邻接点w ,FirstAdjVex(G, u)表示u的第一个邻接点
//NextAdjVex(G, u, w)表示u相对于w的下一个邻接点,w≥0表示存在邻接点
if (!visited[w]) { //w为u的尚未访问的邻接顶点
printf("%c ", G.vertexes[w]);
visited[w] = true; //访问w,并置访问标志数组相应分量值为true
EnQueue(&Q, w); //w进队
}
}
}
}
int main() {
printf("************广度优先搜索(连通图,无向图,邻接矩阵表示法,非递归)**************\n\n");
Adjacency_Matrix_Graph G;
Create_Undirected_Graph(&G);
printf("\n\n");
printf("无向连通图G创建完成!\n\n");
printf("请输入遍历连通图的起始点:");
VertexType c;
scanf(" %c", &c);
int i;
for (i = 0; i < G.VertexNum; ++i)
if (c == G.vertexes[i])
break;
printf("\n\n");
while (i >= G.VertexNum) {
printf("该点不存在,请重新输入!\n");
printf("请输入遍历连通图的起始点:");
scanf(" %c", &c);
for (i = 0; i < G.VertexNum; ++i)
if (c == G.vertexes[i])
break;
}
printf("广度优先搜索遍历连通图结果:\n");
BFS(G, i);
printf("\n\n");
return 0;
}

执行结果:

************广度优先搜索(连通图,无向图,邻接矩阵表示法,非递归)**************
请输入顶点数和边数,以空格隔开:8 9
输入顶点的名称,如 a
请输入第 1 个顶点的名称:1
请输入第 2 个顶点的名称:2
请输入第 3 个顶点的名称:3
请输入第 4 个顶点的名称:4
请输入第 5 个顶点的名称:5
请输入第 6 个顶点的名称:6
请输入第 7 个顶点的名称:7
请输入第 8 个顶点的名称:8
输入边依附的顶点,如 a b
请输入第 1 条边依附的顶点:1 2
请输入第 2 条边依附的顶点:1 3
请输入第 3 条边依附的顶点:2 4
请输入第 4 条边依附的顶点:2 5
请输入第 5 条边依附的顶点:3 6
请输入第 6 条边依附的顶点:3 7
请输入第 7 条边依附的顶点:4 8
请输入第 8 条边依附的顶点:5 8
请输入第 9 条边依附的顶点:6 7
无向连通图G创建完成!
请输入遍历连通图的起始点:1
广度优先搜索遍历连通图结果:
1 2 3 4 5 6 7 8

图的应用

最小生成树

生成树一定要是连通的.

生成树: 所有顶点均由边连接在一起, 但不存在回路的图。

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

算法6.8 普里姆算法(邻接矩阵,无向网)

//算法6.8 普里姆算法(邻接矩阵,无向网)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAX_VERTEX_NUM 100 // 最大顶点数
typedef char VertexType; // 此处假设顶点的数据类型为 char
typedef int EdgeType; // 此处假设边的权值类型为 int
typedef struct Adjacency_Matrix_Graph {
VertexType vertexes[MAX_VERTEX_NUM]; // 顶点数组(一维数组)
EdgeType edges[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵(二维数组)
int VertexNum, EdgeNum; // 图的顶点数和边数
} Adjacency_Matrix_Graph;
//确定点v在G中的位置,存在则返回顶点表中的下标,否则返回-1
int LocateVex(Adjacency_Matrix_Graph G, VertexType v) {
for (int i = 0; i < G.VertexNum; ++i)
if (G.vertexes[i] == v)
return i;
return -1;
}
//采用邻接矩阵表示法,创建无向网G
void Create_Undirected_Net(Adjacency_Matrix_Graph* G) {
//输入顶点数和边数
printf("请输入顶点数和边数,以空格隔开:");
scanf("%d %d", &(*G).VertexNum, &(*G).EdgeNum);
printf("输入顶点的名称,如 a\n");
//建立顶点表,依次输入顶点的信息存入顶点表中
for (int i = 0; i < (*G).VertexNum; ++i) {
printf("请输入第 %d 个顶点的名称:", i + 1);
scanf(" %c", &((*G).vertexes[i])); //在第一个%c前面加上一个空格,表示跳过输入的内容前面的空白,比如上一次输入留下的换行符
}
//初始化邻接矩阵,使每一个权值初始化为极大值
for (int i = 0; i < (*G).VertexNum; ++i)
for (int j = 0; j < (*G).VertexNum; ++j)
(*G).edges[i][j] = MAXINT;
//构造邻接矩阵
printf("输入边依附的顶点及权值,如 a b 5\n");
for (int k = 0; k < (*G).EdgeNum; ++k) {
VertexType v1, v2;
EdgeType w;
int i, j;
printf("请输入第 %d 条边依附的顶点及权值:", k + 1);
scanf(" %c %c %d", &v1, &v2, &w); //输入一条边依附的顶点及权值,在第一个%c前面加上一个空格,表示跳过输入的内容前面的空白,比如上一次输入留下的换行符
i = LocateVex((*G), v1); //确定v1和v2在G中的位置,即顶点表的下标
j = LocateVex((*G), v2);
(*G).edges[i][j] = w; //弧<v1, v2>的权值置为w
(*G).edges[j][i] = (*G).edges[i][j]; //置<v1, v2>的对称弧<v2, v1>的权值为w
}
}
//辅助数组的定义,用来记录从顶点集U到V-U的权值最小的边
struct {
VertexType adjvex; //最小边在U中的那个顶点
EdgeType lowcost; //最小边上的权值
}closedge[MAX_VERTEX_NUM];
//返回权值最小的点
int Min(Adjacency_Matrix_Graph G) {
int index = -1;
int min = MAXINT;
for (int i = 0; i < G.VertexNum; ++i) {
if (min > closedge[i].lowcost && closedge[i].lowcost != 0) {
min = closedge[i].lowcost;
index = i;
}
}
return index;
}
//无向网G以邻接矩阵形式存储,从顶点u出发构造G的最小生成树T,输出T的各条边
void MiniSpanTree_Prim(Adjacency_Matrix_Graph G, VertexType u) {
int k, j, i;
VertexType u0, v0;
k = LocateVex(G, u); //k为顶点u的下标
for (j = 0; j < G.VertexNum; ++j) { //对V-U的每一个顶点vi,初始化closedge[i]
if (j != k) {
closedge[j].adjvex = u;
closedge[j].lowcost = G.edges[k][j]; //{adjvex, lowcost}
}
}
closedge[k].lowcost = 0; //初始,U = {u}
for (i = 1; i < G.VertexNum; ++i) { //选择其余n-1个顶点,生成n-1条边(n= G.vexnum)
k = Min(G);
//求出T的下一个结点:第k个顶点,closedge[k]中存有当前最小边
u0 = closedge[k].adjvex; //u0为最小边的一个顶点,u0∈U
v0 = G.vertexes[k]; //v0为最小边的另一个顶点,v0∈V-U
printf("边 %c ---> %c\n", u0, v0);
closedge[k].lowcost = 0; //第k个顶点并入U集
for (j = 0; j < G.VertexNum; ++j)
if (G.edges[k][j] < closedge[j].lowcost) { //新顶点并入U后重新选择最小边
closedge[j].adjvex = G.vertexes[k];
closedge[j].lowcost = G.edges[k][j];
}
}
}
int main() {
printf("************算法6.8 普里姆算法(邻接矩阵,无向网)**************\n\n");
Adjacency_Matrix_Graph G;
Create_Undirected_Net(&G);
printf("\n\n");
printf("无向图G创建完成!\n");
printf("\n");
printf("******利用普里姆算法构造最小生成树结果:******\n");
MiniSpanTree_Prim(G, 'a');
printf("\n");
return 0;
}

执行结果:

************算法6.8 普里姆算法(邻接矩阵,无向网)**************
请输入顶点数和边数,以空格隔开:6 10
输入顶点的名称,如 a
请输入第 1 个顶点的名称:a
请输入第 2 个顶点的名称:b
请输入第 3 个顶点的名称:c
请输入第 4 个顶点的名称:d
请输入第 5 个顶点的名称:e
请输入第 6 个顶点的名称:f
输入边依附的顶点及权值,如 a b 5
请输入第 1 条边依附的顶点及权值:a b 6
请输入第 2 条边依附的顶点及权值:a d 5
请输入第 3 条边依附的顶点及权值:a c 1
请输入第 4 条边依附的顶点及权值:b c 5
请输入第 5 条边依附的顶点及权值:b e 3
请输入第 6 条边依附的顶点及权值:c d 5
请输入第 7 条边依附的顶点及权值:c e 6
请输入第 8 条边依附的顶点及权值:c f 4
请输入第 9 条边依附的顶点及权值:d f 2
请输入第 10 条边依附的顶点及权值:e f 6
无向图G创建完成!
******利用普里姆算法构造最小生成树结果:******
边 a ---> c
边 c ---> f
边 f ---> d
边 c ---> b
边 b ---> e

示意图:

算法6.9 克鲁斯卡尔算法(邻接矩阵,无向网)

代码(仍有问题,需要改正):

//算法6.9 克鲁斯卡尔算法(邻接矩阵,无向网)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAX_VERTEX_NUM 100 // 最大顶点数
typedef char VertexType; // 此处假设顶点的数据类型为 char
typedef int EdgeType; // 此处假设边的权值类型为 int
typedef struct Adjacency_Matrix_Graph {
VertexType vertexes[MAX_VERTEX_NUM]; // 顶点数组(一维数组)
EdgeType edges[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵(二维数组)
int VertexNum, EdgeNum; // 图的顶点数和边数
} Adjacency_Matrix_Graph;
//采用邻接矩阵表示法,创建无向网G
void Create_Undirected_Net(Adjacency_Matrix_Graph* G) {
//输入顶点数和边数
printf("请输入顶点数和边数,以空格隔开:");
scanf("%d %d", &(*G).VertexNum, &(*G).EdgeNum);
printf("输入顶点的名称,如 a\n");
//建立顶点表,依次输入顶点的信息存入顶点表中
for (int i = 0; i < (*G).VertexNum; ++i) {
printf("请输入第 %d 个顶点的名称:", i + 1);
scanf(" %c", &((*G).vertexes[i])); //在第一个%c前面加上一个空格,表示跳过输入的内容前面的空白,比如上一次输入留下的换行符
}
//初始化邻接矩阵,使每一个权值初始化为极大值
for (int i = 0; i < (*G).VertexNum; ++i)
for (int j = 0; j < (*G).VertexNum; ++j)
(*G).edges[i][j] = MAXINT;
//构造邻接矩阵
printf("输入边依附的顶点及权值,如 a b 5\n");
for (int k = 0; k < (*G).EdgeNum; ++k) {
VertexType v1, v2;
EdgeType w;
int i, j;
printf("请输入第 %d 条边依附的顶点及权值:", k + 1);
scanf(" %c %c %d", &v1, &v2, &w); //输入一条边依附的顶点及权值,在第一个%c前面加上一个空格,表示跳过输入的内容前面的空白,比如上一次输入留下的换行符
i = LocateVex((*G), v1); //确定v1和v2在G中的位置,即顶点表的下标
j = LocateVex((*G), v2);
(*G).edges[i][j] = w; //弧<v1, v2>的权值置为w
(*G).edges[j][i] = (*G).edges[i][j]; //置<v1, v2>的对称弧<v2, v1>的权值为w
}
}
//辅助数组Edges的定义
struct {
VertexType Head; //边的始点
VertexType Tail; //边的终点
EdgeType lowcost; //边上的权值
}Edge[(MAX_VERTEX_NUM * (MAX_VERTEX_NUM - 1)) / 2];
int Vexset[MAX_VERTEX_NUM]; //辅助数组Vexset的定义
//确定点v在G中的位置,存在则返回顶点表中的下标,否则返回-1
int LocateVex(Adjacency_Matrix_Graph G, VertexType v) {
for (int i = 0; i < G.VertexNum; ++i)
if (G.vertexes[i] == v)
return i;
return -1;
}
//冒泡排序
void Sort(Adjacency_Matrix_Graph G) {
int m = G.EdgeNum - 2;
int flag = 1;
while ((m > 0) && flag == 1) {
flag = 0;
for (int j = 0; j <= m; j++) {
if (Edge[j].lowcost > Edge[j + 1].lowcost) {
flag = 1;
VertexType temp_Head = Edge[j].Head;
Edge[j].Head = Edge[j + 1].Head;
Edge[j + 1].Head = temp_Head;
VertexType temp_Tail = Edge[j].Tail;
Edge[j].Tail = Edge[j + 1].Tail;
Edge[j + 1].Tail = temp_Tail;
EdgeType temp_lowcost = Edge[j].lowcost;
Edge[j].lowcost = Edge[j + 1].lowcost;
Edge[j + 1].lowcost = temp_lowcost;
}
}
--m;
}
}
//无向网G以邻接矩阵形式存储,构造G的最小生成树T,输出T的各条边
void MiniSpanTree_Kruskal(Adjacency_Matrix_Graph G) {
int i, j, v1, v2, vs1, vs2;
Sort(G); //将数组Edge中的元素按权值从小到大排序
for (i = 0; i < G.VertexNum; ++i) //辅助数组,表示各顶点自成一个连通分量
Vexset[i] = i;
for (i = 0; i < G.EdgeNum; ++i) {
//依次查看排好序的数组Edge中的边是否在同一连通分量上
v1 = LocateVex(G, Edge[i].Head); //v1为边的始点Head的下标
v2 = LocateVex(G, Edge[i].Tail); //v2为边的终点Tail的下标
vs1 = Vexset[v1]; //获取边Edge[i]的始点所在的连通分量vs1
vs2 = Vexset[v2]; //获取边Edge[i]的终点所在的连通分量vs2
if (vs1 != vs2) { //边的两个顶点分属不同的连通分量
printf("%c ---> %c\n", Edge[i].Head, Edge[i].Tail);
for (j = 0; j < G.VertexNum; ++j) //合并vs1和vs2两个分量,即两个集合统一编号
if (Vexset[j] == vs2) Vexset[j] = vs1; //集合编号为vs2的都改为vs1
}
}
}
int main() {
printf("************算法6.9 克鲁斯卡尔算法(邻接矩阵,无向网)**************\n\n");
Adjacency_Matrix_Graph G;
Create_Undirected_Net(&G);
printf("\n");
printf("*****无向网G创建完成!*****\n");
printf("\n");
MiniSpanTree_Kruskal(G);
return 0;
}

最短路径

最短路径与最小生成树不同, 路径上不一定包含n个顶点, 也不一定包含 n-1 条边。

第一类问题: 两点间最短路径 -- 弗洛伊德 (Floyd)

第二类问题: 某源点到其他各点最短路径 -- 迪杰斯特拉 (Dijkstra)

迪杰斯特拉算法,邻接矩阵,无向网

//算法6.10 迪杰斯特拉算法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAX_VERTEX_NUM 100 // 最大顶点数
typedef char VertexType; // 此处假设顶点的数据类型为 char
typedef int EdgeType; // 此处假设边的权值类型为 int
int D[MAX_VERTEX_NUM]; //用于记录最短路的长度
bool S[MAX_VERTEX_NUM]; //标记顶点是否进入S集合
int Path[MAX_VERTEX_NUM]; //用于记录最短路顶点的前驱
typedef struct Adjacency_Matrix_Graph {
VertexType vertexes[MAX_VERTEX_NUM]; // 顶点数组(一维数组)
EdgeType edges[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵(二维数组)
int VertexNum, EdgeNum; // 图的顶点数和边数
} Adjacency_Matrix_Graph;
//确定点v在G中的位置,存在则返回顶点表中的下标,否则返回-1
int LocateVex(Adjacency_Matrix_Graph G, VertexType v) {
for (int i = 0; i < G.VertexNum; ++i)
if (G.vertexes[i] == v)
return i;
return -1;
}
//采用邻接矩阵表示法,创建无向网G
void Create_Undirected_Net(Adjacency_Matrix_Graph* G) {
//输入顶点数和边数
printf("请输入顶点数和边数,以空格隔开:");
scanf("%d %d", &(*G).VertexNum, &(*G).EdgeNum);
printf("输入顶点的名称,如 a\n");
//建立顶点表,依次输入顶点的信息存入顶点表中
for (int i = 0; i < (*G).VertexNum; ++i) {
printf("请输入第 %d 个顶点的名称:", i + 1);
scanf(" %c", &((*G).vertexes[i])); //在第一个%c前面加上一个空格,表示跳过输入的内容前面的空白,比如上一次输入留下的换行符
}
//初始化邻接矩阵,使每一个权值初始化为极大值
for (int i = 0; i < (*G).VertexNum; ++i)
for (int j = 0; j < (*G).VertexNum; ++j)
(*G).edges[i][j] = MAXINT;
//构造邻接矩阵
printf("输入边依附的顶点及权值,如 a b 5\n");
for (int k = 0; k < (*G).EdgeNum; ++k) {
VertexType v1, v2;
EdgeType w;
int i, j;
printf("请输入第 %d 条边依附的顶点及权值:", k + 1);
scanf(" %c %c %d", &v1, &v2, &w); //输入一条边依附的顶点及权值,在第一个%c前面加上一个空格,表示跳过输入的内容前面的空白,比如上一次输入留下的换行符
i = LocateVex((*G), v1); //确定v1和v2在G中的位置,即顶点表的下标
j = LocateVex((*G), v2);
(*G).edges[i][j] = w; //弧<v1, v2>的权值置为w
(*G).edges[j][i] = (*G).edges[i][j]; //置<v1, v2>的对称弧<v2, v1>的权值为w
}
}
//用Dijkstra算法求有向网G的v0顶点到其余顶点的最短路径
void ShortestPath_DIJ(Adjacency_Matrix_Graph G, int v0) {
int v, i, w, min;
int n = G.VertexNum; //n为G中顶点的个数
for (v = 0; v < n; ++v) { //n个顶点依次初始化
S[v] = false; //S初始为空集
D[v] = G.edges[v0][v]; //将v0到各个终点的最短路径长度初始化为弧上的权值
if (D[v] < MAXINT) Path[v] = v0; //如果v0和v之间有弧,则将v的前驱置为v0
else Path[v] = -1; //如果v0和v之间无弧,则将v的前驱置为-1
}
S[v0] = true; //将v0加入S
D[v0] = 0; //源点到源点的距离为0
/*初始化结束,开始主循环,每次求得v0到某个顶点v的最短路径,将v加到S集*/
for (i = 1; i < n; ++i) { //对其余n-1个顶点,依次进行计算
min = MAXINT;
for (w = 0; w < n; ++w)
if (!S[w] && D[w] < min) { //选择一条当前的最短路径,终点为v
v = w;
min = D[w];
}
S[v] = true; //将v加入S
for (w = 0; w < n; ++w) //更新从v0出发到集合V?S上所有顶点的最短路径长度
if (!S[w] && (D[v] + G.edges[v][w] < D[w])) {
D[w] = D[v] + G.edges[v][w]; //更新D[w]
Path[w] = v; //更改w的前驱为v
}
}
}
//显示最短路
void DisplayPath(Adjacency_Matrix_Graph G, int begin, int temp) {
if (Path[temp] != -1) {
DisplayPath(G, begin, Path[temp]);
printf("%c -->", G.vertexes[Path[temp]]);
}
}
int main() {
printf("************算法6.10 迪杰斯特拉算法**************\n\n");
Adjacency_Matrix_Graph G;
int i, j, num_start, num_destination;
VertexType start, destination;
Create_Undirected_Net(&G);
printf("\n");
printf("*****无向网G创建完成!*****\n");
for (i = 0; i < G.VertexNum; ++i) {
for (j = 0; j < G.VertexNum; ++j) {
if (j != G.VertexNum - 1) {
if (G.edges[i][j] != MAXINT)
printf("%d\t", G.edges[i][j]);
else printf("∞\t");
}
else {
if (G.edges[i][j] != MAXINT)
printf("%d\n", G.edges[i][j]);
else printf("∞\n");
}
}
}
printf("\n");
printf("请依次输入起始点、终点名称:");
scanf(" %c %c", &start, &destination);
num_start = LocateVex(G, start);
num_destination = LocateVex(G, destination);
ShortestPath_DIJ(G, num_start);
printf("\n最短路径为:");
DisplayPath(G, num_start, num_destination);
printf("%c\n", G.vertexes[num_destination]);
return 0;
}

执行结果:

************算法6.10 迪杰斯特拉算法**************
请输入顶点数和边数,以空格隔开:7 10
输入顶点的名称,如 a
请输入第 1 个顶点的名称:0
请输入第 2 个顶点的名称:1
请输入第 3 个顶点的名称:2
请输入第 4 个顶点的名称:3
请输入第 5 个顶点的名称:4
请输入第 6 个顶点的名称:5
请输入第 7 个顶点的名称:6
输入边依附的顶点及权值,如 a b 5
请输入第 1 条边依附的顶点及权值:0 1 13
请输入第 2 条边依附的顶点及权值:0 2 8
请输入第 3 条边依附的顶点及权值:0 4 30
请输入第 4 条边依附的顶点及权值:0 6 32
请输入第 5 条边依附的顶点及权值:1 5 9
请输入第 6 条边依附的顶点及权值:1 6 7
请输入第 7 条边依附的顶点及权值:2 3 5
请输入第 8 条边依附的顶点及权值:3 4 6
请输入第 9 条边依附的顶点及权值:4 5 2
请输入第 10 条边依附的顶点及权值:5 6 17
*****无向网G创建完成!*****
∞ 13 8 ∞ 30 ∞ 32
13 ∞ ∞ ∞ ∞ 9 7
8 ∞ ∞ 5 ∞ ∞ ∞
∞ ∞ 5 ∞ 6 ∞ ∞
30 ∞ ∞ 6 ∞ 2 ∞
∞ 9 ∞ ∞ 2 ∞ 17
32 7 ∞ ∞ ∞ 17 ∞
请依次输入起始点、终点名称:0 6
最短路径为:0 -->1 -->6

示意图:

弗洛伊德算法,邻接矩阵,有向网

//算法6.11 弗洛伊德算法,邻接矩阵,有向网
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define MAXINT 32767 // 表示极大值,即 ∞
#define MAX_VERTEX_NUM 100 // 最大顶点数
typedef char VertexType; // 此处假设顶点的数据类型为 char
typedef int EdgeType; // 此处假设边的权值类型为 int
int Path[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //最短路径上顶点vj的前一顶点的序号
int D[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //记录顶点vi和vj之间的最短路径长度
typedef struct Adjacency_Matrix_Graph {
VertexType vertexes[MAX_VERTEX_NUM]; // 顶点数组(一维数组)
EdgeType edges[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵(二维数组)
int VertexNum, EdgeNum; // 图的顶点数和边数
} Adjacency_Matrix_Graph;
//确定点v在G中的位置,存在则返回顶点表中的下标,否则返回-1
int LocateVex(Adjacency_Matrix_Graph G, VertexType v) {
for (int i = 0; i < G.VertexNum; ++i)
if (G.vertexes[i] == v)
return i;
return -1;
}
//采用邻接矩阵表示法,创建有向网G
void Create_Directed_Net(Adjacency_Matrix_Graph* G) {
//输入顶点数和边数
printf("请输入顶点数和边数,以空格隔开:");
scanf("%d %d", &(*G).VertexNum, &(*G).EdgeNum);
printf("输入顶点的名称,如 a\n");
//建立顶点表,依次输入顶点的信息存入顶点表中
for (int i = 0; i < (*G).VertexNum; ++i) {
printf("请输入第 %d 个顶点的名称:", i + 1);
scanf(" %c", &((*G).vertexes[i])); //在第一个%c前面加上一个空格,表示跳过输入的内容前面的空白,比如上一次输入留下的换行符
}
//初始化邻接矩阵,使每一个权值初始化为极大值
for (int i = 0; i < (*G).VertexNum; ++i)
for (int j = 0; j < (*G).VertexNum; ++j)
(*G).edges[i][j] = MAXINT;
//构造邻接矩阵
printf("输入边依附的顶点(先输入弧尾,后输入弧头)及权值,如 a b 5\n");
for (int k = 0; k < (*G).EdgeNum; ++k) {
VertexType v1, v2;
EdgeType w;
int i, j;
printf("请输入第 %d 条边依附的顶点(先输入弧尾,后输入弧头)及权值:", k + 1);
scanf(" %c %c %d", &v1, &v2, &w); //输入一条边依附的顶点及权值,在第一个%c前面加上一个空格,表示跳过输入的内容前面的空白,比如上一次输入留下的换行符
i = LocateVex((*G), v1); //确定v1和v2在G中的位置,即顶点表的下标
j = LocateVex((*G), v2);
(*G).edges[i][j] = w; //弧<v1, v2>的权值置为w(无向网中下面那句给对称位置赋予相同权值的那一条语句需要删除)
}
}
//用Floyd算法求有向网G中各对顶点i和j之间的最短路径
void ShortestPath_Floyed(Adjacency_Matrix_Graph G) {
int i, j, k;
for (i = 0; i < G.VertexNum; ++i) //各对结点之间初始已知路径及距离
for (j = 0; j < G.VertexNum; ++j) {
D[i][j] = G.edges[i][j];
if (D[i][j] < MAXINT && i != j) Path[i][j] = i; //如果i和j之间有弧,则将j的前驱置为i
else Path[i][j] = -1; //如果i和j之间无弧,则将j的前驱置为-1
}
for (k = 0; k < G.VertexNum; ++k)
for (i = 0; i < G.VertexNum; ++i)
for (j = 0; j < G.VertexNum; ++j)
if (D[i][k] + D[k][j] < D[i][j]) { //从i经k到j的一条路径更短
D[i][j] = D[i][k] + D[k][j]; //更新D[i][j]
Path[i][j] = Path[k][j]; //更改j的前驱为k
}
}
//显示最短路径
void DisplayPath(Adjacency_Matrix_Graph G, int begin, int temp) {
if (Path[begin][temp] != -1) {
DisplayPath(G, begin, Path[begin][temp]);
printf("%c -->", G.vertexes[Path[begin][temp]]);
}
}
int main() {
printf("************算法6.11 弗洛伊德算法**************\n\n");
Adjacency_Matrix_Graph G;
char start, destination;
int num_start, num_destination;
Create_Directed_Net(&G);
printf("\n");
printf("*****有向网G创建完成!*****\n");
ShortestPath_Floyed(G);
printf("请依次输入起始点、终点名称:");
scanf(" %c %c", &start, &destination);
num_start = LocateVex(G, start);
num_destination = LocateVex(G, destination);
DisplayPath(G, num_start, num_destination);
printf("%c\n", G.vertexes[num_destination]);
printf("最短路径的长度为:%d\n", D[num_start][num_destination]);
printf("\n");
return 0;
}

执行结果:

示意图:

posted @   有空  阅读(28)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示

目录导航