零、读前说明
- 本文中所有设计的代码均通过测试,并且在功能性方面均实现应有的功能。
- 设计的代码并非全部公开,部分无关紧要代码并没有贴出来。
- 如果你也对此感兴趣、也想测试源码的话,可以私聊我,非常欢迎一起探讨学习。
- 由于时间、水平、精力有限,文中难免会出现不准确、甚至错误的地方,也很欢迎大佬看见的话批评指正。
- 嘻嘻。。。。 。。。。。。。。收!
一、概述
在图中任何两个顶点之间都可能存在联系,所以图的存储结构应该需要根据具体问题的要求来进行设计。从图的逻辑结构定义来看,图中任何一个顶点都可以看成是第一个顶点。常用的存储结构有邻接矩阵、邻接表(逆邻接表)、十字链表、邻接多重表、 边集数组。那么本博文将带你就 “邻接矩阵” 来窥探一二。。。
二、邻接矩阵
2.1、说明
图 由 顶点集合 和 边(或弧)集合 组成,而且 顶点与顶点 之间可能都存在着关系。
首先,顶点没有主次先后,可以用一个一维数组保存所有顶点信息。
然后,边(或弧)表示顶点与顶点之间的关系,用一个二维数组保存顶点间关系(边或弧)信息。
那么,这个二维数组称为 邻接矩阵 (adjacency matrix) :用于描述顶点之间相互邻接关系的一种形式。
假图
有
个顶点,那么邻接矩阵就是一个
的方阵,对于无权图,如果顶点与顶点之间存在边,则用 1 表示,反之则用 0 表示。可以定义为:
邻接矩阵 又分为 有向图邻接矩阵 和 无向图邻接矩阵。
一、无向图
无向图邻接矩阵是对称的,也就是第 i 行第 j 列的元素与第 j 行第 i 列的元素相同,也就是 。
对于 矩阵中对角线 上的元素,正好表示的就是该顶点到自身的边,也就是 自环 的边(详情可以查看博文 数据结构(廿一) – C语言版 – 图 - 图的基本概念 中图 1.1 中 (e) )。
如下图所示。
二、有向图
在有向图中,边是有方向区分的, 存在并不一定也存在 ,所以 有向图邻接矩阵是不对称 的,并且在有向图中需要考虑到顶点的 出度 与 入度,出度对应于行,入度对应于列,顶点 的入度等于第 i 列的各数之和,顶点 的出度等于第 i 列的各数之和。
如下图所示。
三、网
每条边带有权值的图称为网,那么在用邻接矩阵中,可以将这个权值保存到对应的矩阵单元中,所以,带权图的邻接矩阵则可以使用与对应的正数来表示,可以表示为:
其中:
weight:边的权值
:表示一个计算机允许的、大于所有边的权值的数值
0 :顶点到自身,目前用0来表示,但是不做讨论
如下图所示。
四、邻接矩阵特点
1、无向图的邻接矩阵是对称矩阵,有 个顶点的无向图需要 个空间单元
2、有向图的邻接矩阵不一定对称, 个顶点的有向图需要 的存储空间单元
3、无向图邻接矩阵中第i行(或第i列)的非零元素的个数为顶点vi的度
4、有向图邻接矩阵中第 行非零元素的个数为顶点 的出度,第 列非零元素的个数为顶点 的入度,顶点 的度为第 行与第 列非零元素个数之和
5、用邻接矩阵表示图,很容易确定图中任意两个顶点是否有边相连
6、顶点 的所有临界点就是将矩阵中第i行元素扫描一遍, 为 1 就是临界点
7、如果是有向图,则空间浪费,利用率低
2.2、代码实现
一、数据类型定义
根据图的定义,定义图的数据结构类型中,需要包含两个重要的信息,一为 顶点的集合,二为 边的集合,本测试中加入一个 顶点的个数 的元素。测试代码中定义图的数据结构为:
typedef void MVertex; // 顶点的类型
typedef struct _tag_MGraph
{
int count; // 顶点的个数
MVertex **vertex; // 顶点的集合
int **matrix; // 邻接矩阵,边的集合
} TMGraph;
二、图的操作函数定义
图的操作,本博文中主要涉及三部分,创建销毁等操作、边的集合的操作、顶点的集合的操作。为了方便后续的添加其他的操作等,定义如下面代码所示。
/* 边的操作集合 */
typedef struct __func_Edge
{
int (*add)(MGraph *, int, int, int);
int (*remove)(MGraph *, int, int);
int (*get)(MGraph *, int, int);
int (*count)(MGraph *);
} funcEdge;
/* 顶点的操作集合 */
typedef struct __func_vertex
{
int (*dgree)(MGraph *, int);
int (*count)(MGraph *);
} funcVertex;
/* 图的操作集合 */
typedef struct __func_Graph
{
MGraph *(*create)(MVertex **, int);
void (*destroy)(MGraph *);
void (*clear)(MGraph *);
void (*display)(MGraph *);
funcEdge edge; // 边的集合
funcVertex vertex; //顶点的集合
} funcGraph;
extern funcGraph funMGraph; // 申明外部变量,图的操作函数
三、主要操作的实现源码
图的操作主要实现代码(方便起见,文件名称类型为 .cpp ),注释比较详细,此处不在赘述,代码如下所示。
/**
* 功 能:
* 创建并返回有n个顶点的图
* 参 数:
* v:与顶点相关的数据的指针
* n:顶点的个数
* 返回值:
* 成功:
* 失败:NULL
**/
MGraph *MGraph_Create(MVertex **v, int num) // O(n)
{
TMGraph *ret = NULL;
int *ptmp = NULL;
if ((v == NULL) || (num <= 0)) goto END;
// 返回的图的结构体空间
ret = (TMGraph *)malloc(sizeof(TMGraph));
if (ret == NULL) goto END;
// 上面使用malloc,负责任起见需要将空间清空,方便的可使用 calloc, 动态内存分配并做初始化
memset(ret, 0, sizeof(TMGraph));
// 图的顶点的赋值
ret->count = num;
// 指向与顶点相关的数据指针,指向顶点
ret->vertex = (MVertex **)malloc(sizeof(MVertex *) * num);
// 申请指向关系的二维数组
ret->matrix = (int **)malloc(sizeof(int *) * num);
// 申请矩阵的数据空间
ptmp = (int *)malloc(num * num * sizeof(int));
if (ptmp == NULL) goto END;
memset(ptmp, 0, num * num * sizeof(int));
if ((ret->vertex != NULL) && (ret->matrix != NULL))
{
for (int i = 0; i < num; i++)
{
ret->vertex[i] = v[i]; // 顶点数据指针保存
ret->matrix[i] = ptmp + i * num; // 链接指针到数据空间
}
}
else
{
free(ptmp);
free(ret->matrix);
free(ret->vertex);
free(ret);
ret = NULL;
}
END:
return ret;
}
/**
* 功 能:
* 添加顶点与顶点的关系 - 边
* 在graph所指图中的v1和v2之间加上边,且边的权为w
* 参 数:
* graph:要操作的图
* v1 :顶点(尾)的编号(位置)
* v2 :顶点(头)的编号(位置)
* w :权值,如果为0,表示边不存在
* 返回值:
* 0 :添加成功
* -1:添加失败,参数异常
**/
int MGraph_AddEdge(MGraph *graph, int v1, int v2, int w) // O(1)
{
int ret = -1;
TMGraph *tGraph = NULL;
if (graph == NULL) goto END;
tGraph = (TMGraph *)graph;
if (((v1 >= tGraph->count) && (v1 < 0)) || // 判断顶点的合法性
((v2 >= tGraph->count) && (v2 < 0)) || // 判断顶点的合法性
w < 1) // 判断权值是不是合适,如果为0,则说明边不存在,为参数异常
goto END;
tGraph->matrix[v1][v2] = w; // 权值 或 边 赋值
ret = 0;
END:
return ret;
}
/**
* 功 能:
* 将graph所指图中v1和v2之间的边的权值返回
* 参 数:
* graph:要操作的图
* v1 :顶点(尾)的位置(编号)
* v2 :顶点(头)的位置(编号)
* 返回值:
* 0 :边 不存在
* -1 :参数异常
* 其他:边存在或者权值
**/
int MGraph_GetEdge(MGraph *graph, int v1, int v2) // O(1)
{
TMGraph *tGraph = (TMGraph *)graph;
int ret = -1;
if (graph == NULL) goto END;
if ((0 > v1) && (v1 >= tGraph->count) ||
(0 > v2) && (v2 >= tGraph->count))
goto END;
ret = tGraph->matrix[v1][v2];
END:
return ret;
}
/**
* 功 能:
* 将graph所指图中v1和v2之间的边删除,返回权值
* 参 数:
* graph:要操作的图
* v1 :顶点(尾)的位置(编号)
* v2 :顶点(头)的位置(编号)
* 返回值:
* 0 :删除成功
* -1 :删除失败
**/
int MGraph_RemoveEdge(MGraph *graph, int v1, int v2) // O(1)
{
int ret = 0;
if (graph == NULL) goto END;
// 判断是否存在边
ret = MGraph_GetEdge(graph, v1, v2);
if (ret)
{
((TMGraph *)graph)->matrix[v1][v2] = 0; // 将矩阵设置为0
ret = 0;
}
else
ret = -1;
END:
return ret;
}
/**
* 功 能:
* 将graph所指图中v顶点的度数
* 参 数:
* graph:要操作的图
* v :顶点 的位置(编号)
* 返回值:
* -1 :参数异常
* 其他:顶点的度
**/
int MGraph_VertexDgree(MGraph *graph, int v) // O(n)
{
TMGraph *tGraph = (TMGraph *)graph;
int ret = -1;
if (graph == NULL) goto END;
if ((0 > v) && (v >= tGraph->count)) goto END;
ret = 0;
for (int i = 0; i < tGraph->count; i++)
{
if (tGraph->matrix[v][i] != 0) ret++;
if (tGraph->matrix[i][v] != 0) ret++;
}
END:
return ret;
}
四、测试案例的实现源码
主要进行相关代码的测试,方便起见,测试案例的名称为 main.cpp ),注释比较详细,此处不在赘述说明,代码如下所示。
#include "../src/graph/graph.h"
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// 顶点集合
MVertex const *v[] = {"A", "B", "C", "D", "E", "F"};
// 创建一个图
MGraph *graph = funMGraph.create((MVertex **)v, 6);
// 添加边
funMGraph.edge.add(graph, 0, 1, 1); // <A, B>
funMGraph.edge.add(graph, 0, 2, 1); // <A, C>
funMGraph.edge.add(graph, 0, 3, 1); // <A, D>
funMGraph.edge.add(graph, 1, 5, 1); // <B, F>
funMGraph.edge.add(graph, 1, 4, 1); // <B, E>
funMGraph.edge.add(graph, 2, 1, 1); // <C, B>
funMGraph.edge.add(graph, 3, 4, 1); // <D, E>
printf("\nThe Adjacency Matrix of the graph is :\n\n");
funMGraph.display(graph);
// 添加边,带有权值
funMGraph.edge.add(graph, 4, 2, 9); // <E, C> 9
printf("\nThe Adjacency Matrix of the graph is :\n\n");
funMGraph.display(graph);
// 边的权值
printf("\nThe Weight of Edge <D, F> is : %d\n", funMGraph.edge.get(graph, 3, 4));
// 顶点的个数
printf("\nThe number of vertices in the graph is : %d\n", funMGraph.vertex.count(graph));
// 边的个数
printf("\nThe number of Edge in the graph is : %d\n", funMGraph.edge.count(graph));
// 顶点的度
printf("\nThe Degree of vertex 'C' is : %d\n\n", funMGraph.vertex.dgree(graph, 2));
// 销毁图
funMGraph.destroy(graph);
printf("system exited with return code %d\n\n", 0);
return 0;
}
方便后续的测试与对照,上面测试案例中创建的图的显示效果如下图所示。
2.3、测试效果
本次测试是在 linux 环境下进行,也可以在 windows 环境下操作,详细说明在README.md中查看。
使用 Cmake 编译,使用下面指令
mkdir build && cd build
在windows下使用 : cmake -G"MinGW Makefiles" .. # 注意 .. ,表示上级目录
在linux下使用 : cmake -G"Unix Makefiles" .. # 注意 .. ,表示上级目录
在linux下也可以使用 : cmake .. # 注意 .. ,表示上级目录
make
经过cmake编译之后,可以使用在当前目录下使用指令 ./../runtime/graph
来运行可执行程序,也可以进入到目录runtime中,然后使用指令 ./graph
,即可运行测试程序。实际测试的结果如下图所示。
至此,代码全部运行完成。
三、关联矩阵
对应的顶点与对应的边是否存在关联关系的矩阵 (incidence matrix) 。
如果图有 个顶点,那么关联矩阵有 行,如果有 条边,那么关联矩阵有 列,如果边和顶点有对应关系。则记为 1 ,否则记为 0 。
如下图所示。
在关联矩阵中,第 行所有值的加和为顶点 的度。
对于 有向图,如果边的方向是从顶点 到 的,表示为 ,则在关联矩阵中对应位置设置为 1 ,反之设置为 -1 。如果顶点和边没有关系,则设置为 0 。
如下图所示。
好啦,废话不多说,总结写作不易,如果你喜欢这篇文章或者对你有用,请动动你发财的小手手帮忙点个赞,当然 关注一波 那就更好了,好啦,就到这儿了,么么哒(*  ̄3)(ε ̄ *)。
上一篇:数据结构(廿一) – C语言版 – 图 - 图的基本概念
下一篇:数据结构(廿三) – C语言版 – 图 - 图的存储结构 – 邻接表、逆邻接表