数据结构(廿二) -- C语言版 -- 图 - 图的存储结构 -- 邻接矩阵

零、读前说明

  • 本文中所有设计的代码均通过测试,并且在功能性方面均实现应有的功能。
  • 设计的代码并非全部公开,部分无关紧要代码并没有贴出来。
  • 如果你也对此感兴趣、也想测试源码的话,可以私聊我,非常欢迎一起探讨学习。
  • 由于时间、水平、精力有限,文中难免会出现不准确、甚至错误的地方,也很欢迎大佬看见的话批评指正。
  • 嘻嘻。。。。 。。。。。。。。收!

一、概述

  在图中任何两个顶点之间都可能存在联系,所以图的存储结构应该需要根据具体问题的要求来进行设计。从图的逻辑结构定义来看,图中任何一个顶点都可以看成是第一个顶点。常用的存储结构有邻接矩阵邻接表(逆邻接表)、十字链表邻接多重表边集数组。那么本博文将带你就 “邻接矩阵” 来窥探一二。。。

二、邻接矩阵

2.1、说明

  顶点集合边(或弧)集合 组成,而且 顶点与顶点 之间可能都存在着关系。

  首先,顶点没有主次先后,可以用一个一维数组保存所有顶点信息。
  然后,边(或弧)表示顶点与顶点之间的关系,用一个二维数组保存顶点间关系(边或弧)信息。
  那么,这个二维数组称为 邻接矩阵 (adjacency matrix) :用于描述顶点之间相互邻接关系的一种形式。

  假图 G r a p h Graph n n 个顶点,那么邻接矩阵就是一个 n × n n×n 的方阵,对于无权图,如果顶点与顶点之间存在边,则用 1 表示,反之则用 0 表示。可以定义为:
a r c [ i ] [ j ] = { 1 ( v i , v j ) E < v i , v j > E 0 arc[ i ][ j ] =\begin{cases} 1,若(v_i,v_j)\in E 或 <v_i, v_j> \in E\\ 0,反之\end{cases}

  邻接矩阵 又分为 有向图邻接矩阵无向图邻接矩阵

  一、无向图

  无向图邻接矩阵是对称的,也就是第 i 行第 j 列的元素与第 j 行第 i 列的元素相同,也就是 ( i , j ) = ( j , i ) (i, j)=(j, i)

  对于 矩阵中对角线 上的元素,正好表示的就是该顶点到自身的边,也就是 自环 的边(详情可以查看博文 数据结构(廿一) – C语言版 – 图 - 图的基本概念 中图 1.1(e) )。

  如下图所示。

2.1 无向图邻接矩阵示意图
  

  二、有向图

  在有向图中,边是有方向区分的, < v 0 , v 1 > <v_0, v_1> 存在并不一定也存在 < v 1 , v 0 > <v_1, v_0> ,所以 有向图邻接矩阵是不对称 的,并且在有向图中需要考虑到顶点的 出度入度出度对应于行入度对应于列,顶点 v i v_i 入度等于第 i 列的各数之和,顶点 v i v_i 出度等于第 i 列的各数之和

  如下图所示。

2.2 有向图邻接矩阵示意图
  

  三、网

  每条边带有权值的图称为网,那么在用邻接矩阵中,可以将这个权值保存到对应的矩阵单元中,所以,带权图的邻接矩阵则可以使用与对应的正数来表示,可以表示为:

a r c [ i ] [ j ] = { w e i g h t ( v i , v j ) E < v i , v j > E 0 arc[ i ][ j ] =\begin{cases} weight,若(v_i,v_j)\in E 或 <v_i, v_j> \in E\\ 0,顶点到自身\\∞,其他\end{cases}
  其中:
     weight:边的权值
       :表示一个计算机允许的、大于所有边的权值的数值
     0   :顶点到自身,目前用0来表示,但是不做讨论

  如下图所示。

2.3 网邻接矩阵示意图
  

  四、邻接矩阵特点

    1、无向图的邻接矩阵是对称矩阵,有 n n 个顶点的无向图需要 n × ( n 1 ) 2 n \times (n-1)\over 2 个空间单元

    2、有向图的邻接矩阵不一定对称, n n 个顶点的有向图需要 n ² 的存储空间单元

    3、无向图邻接矩阵中第i行(或第i列)的非零元素的个数为顶点vi的度

    4、有向图邻接矩阵中第 i i 行非零元素的个数为顶点 v i v_i 的出度,第 i i 列非零元素的个数为顶点 v i v_i 的入度,顶点 v i v_i 的度为第 i i 行与第 i i 列非零元素个数之和

    5、用邻接矩阵表示图,很容易确定图中任意两个顶点是否有边相连

    6、顶点 v i v_i 的所有临界点就是将矩阵中第i行元素扫描一遍, a r c [ i ] [ j ] arc[i][j] 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.4 测试案例中图的示意图

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 ,即可运行测试程序。实际测试的结果如下图所示。

2.5 测试案例显示的示意图

  
  至此,代码全部运行完成。

三、关联矩阵

  对应的顶点与对应的边是否存在关联关系的矩阵 (incidence matrix)

  如果图有 n n 个顶点,那么关联矩阵有 n n 行,如果有 e e 条边,那么关联矩阵有 e e 列,如果边和顶点有对应关系。则记为 1 ,否则记为 0

  如下图所示。

3.1 无向图关联矩阵示意图
  

  在关联矩阵中,第 i i 行所有值的加和为顶点 v i v_i 的度。

  对于 有向图,如果边的方向是从顶点 v i v_i v j v_j 的,表示为 < v i , v j > <v_i, v_j> ,则在关联矩阵中对应位置设置为 1 ,反之设置为 -1 。如果顶点和边没有关系,则设置为 0

  如下图所示。

3.2 有向图关联矩阵示意图
  

  好啦,废话不多说,总结写作不易,如果你喜欢这篇文章或者对你有用,请动动你发财的小手手帮忙点个赞,当然 关注一波 那就更好了,好啦,就到这儿了,么么哒(*  ̄3)(ε ̄ *)。

上一篇:数据结构(廿一) – C语言版 – 图 - 图的基本概念
下一篇:数据结构(廿三) – C语言版 – 图 - 图的存储结构 – 邻接表、逆邻接表

猜你喜欢

转载自blog.csdn.net/zhemingbuhao/article/details/107413909