简要地介绍了图的两种存储结构,邻接矩阵和邻接表,以及他们用C语言的实现代码。

(VE)来表示的,对于无向图来说,其中 = (v0v1, ... , vn),= { (vi,vj) (0 <=  i, j <=  n且i 不等于j)},对于有向图= { < vi,vj > (0 <=  i, j <=  n且i 不等于j)}。V是顶点的集合,E是边的集合。所以我们只要把顶点和边的集合储存起来,那么该图的所有数据就能够存储起来了。

  本文只介绍两种比较常见和重要的图的存储结构:邻接矩阵和邻接表。

M,如果M(i,j)=1,则说明顶点i和顶点j之间存在一条边,对于无向图来说,M (j ,i) = M (i, j),所以其邻接矩阵是一个对称矩阵;对于有向图来说,则未必是一个对称矩阵。邻接矩阵的对角线元素都为0。下图是一个无向图和对应的临街矩阵:

图数据结构python 图数据结构存储_邻接矩阵

  图1:无向图

图数据结构python 图数据结构存储_邻接表_02

图2:邻接矩阵

需要注意的是,当边上有权值的时候,称之为网图,则邻接矩阵中的元素不再仅是0和1了,邻接矩阵M中的元素定义为:

图数据结构python 图数据结构存储_邻接矩阵_03


以下用C语言创建一个无向图的邻接矩阵:

  头文件是:GraphStruct.h

1 /*GraphStruct.h
 2 * 图的邻接矩阵存储方式,结构由顶点数量、边数量、顶点集合和边集合组成。
 3 * 其中顶点集合一维数组,根据顶点的数量动态分配数组大小。
 4 * 边集合是二维数组,根据顶点的数量来动态分配数组大小,对于无向图来说,该邻接矩阵是对称矩阵。
 5 * 邻接矩阵比较适用于稠密图
 6 */
 7 typedef char vertexType;
 8 typedef int edgeType;
 9 typedef struct GraphMatrix{
10 
11     int vertexNumber;            // 顶点数量
12     int edgeNumber;                // 边的数量
13     vertexType *vertex;            // 顶点集合,动态数组
14     edgeType** edge;            // 边集合,二维动态数组
15 
16 } GraphMatrix;
17 
18 void GraphMatrix_create(GraphMatrix *g);

  该头文件包含了邻接矩阵的数据结构,结构体的成员变量包括顶点数量、边的数量、顶点集合和边的集合。为了节省空间,将顶点集合和边集设为动态数组,根据顶点数量来分配空间。

 实现文件是:GraphStruct.c

1 #include <stdio.h>
 2 #include <malloc.h>
 3 #include"GraphStruct.h"
 4 
 5 void GraphMatrix_create(GraphMatrix *g){
 6 
 7     printf("请分别输入图的顶点数量和边的数量,用空格隔开:");
 8     scanf("%d %d", &g->vertexNumber, &g->edgeNumber);  //将顶点数量和边的数量存储在结构体g中相应的变量
 9     g->vertex = (vertexType*)malloc(g->vertexNumber * sizeof(vertexType)); //为动态数组申请空间
10     //二维动态数组申请空间
11     g->edge = (edgeType**)malloc(g->vertexNumber * sizeof(edgeType));        
12     for (int i = 0; i < g->vertexNumber; i++){
13         g->edge[i] = (edgeType*)malloc(g->vertexNumber * sizeof(edgeType));
14     }
15     //初始化邻接矩阵的所有元素
16     for (int i = 0; i < g->vertexNumber; i++){
17         for (int j = 0; j < g->vertexNumber; j++)
18             g->edge[i][j] = 0;
19     }
20     
21     //输入图的信息
22     for (int k = 0; k < g->edgeNumber; k++){
23 
24         int i, j;
25         printf("请输入边(vi,vj)的下标, i和j,用空格隔开:");
26         scanf("%d%d", &i, &j);
27         g->edge[i][j] = 1;    //(i,j)和(j,i)指的是一条边
28         g->edge[j][i] = 1;
29     }
30     
31     //输出图的信息
32     printf("Your graph matrix is :\n");
33     for (int i = 0; i < g->vertexNumber; i++){
34         for (int j = 0; j < g->vertexNumber; j++){
35             printf("%d\t", g->edge[i][j]);
36         }
37         printf("\n");
38     }
39

  测试文件为:main.c

 

1 #include<stddef.h>
 2 #include "GraphStruct.h"
 3 
 4 int main(){
 5     
 6     GraphMatrix *gm;
 7     gm = (GraphMatrix *)malloc(sizeof(GraphMatrix));
 8     GraphMatrix_create(gm);
 9    10 
11     return 0;
12 }

 

 

 

  运行结果为:

图数据结构python 图数据结构存储_邻接矩阵_04

 

图3 运行结果

  对于有向图,网图的代码,只要将上面的代码稍微修改就行了,本文末尾附上代码的下载地址。

  对于顶点数很多但是边数很少的图来说,用邻接矩阵显得略为“奢侈”,因为矩阵元素为1的很少,即其中的有用信息很少,但却占了很大的空间。所以下面我们来看看邻接表,以图1的无向图为例,我们先不讲理论的知识,先把图1的邻接表画出来,如图4。

图数据结构python 图数据结构存储_图数据结构python_05

 

 

图4 邻接表 

  作为顶点0,它的邻接顶点有1,3,4,形成的边有(0,1),(0,3)和(0,4),所以顶点0将其指出来了;对于顶点1,它的邻接顶点有0,2,4,所以顶点1将其指出来了,以此类推。他们的边没有先后顺序之分。对于边(i,j),邻接表如下:

图数据结构python 图数据结构存储_图数据结构python_06

图5 (i,j)的邻接表

  左边的节点称为顶点节点,其结构体包含顶点元素和指向第一条边的指针;右边的为边节点,结构体包含边的顶点对应的下标,和指向下一个边节点的指针。对于有权值的网图,只需要在边节点增加一个权值的成员变量即可。

  实现邻接表存储结构的代码如下:

  头文件是:GraphStruct.h

1 /*
 2  *图的另一种存储结构是邻接表
 3 
 4 */
 5 typedef struct ListEdgeNode{
 6     int index;                    // 边的下标
 7     struct ListEdgeNode *next;            // 指向下一个节点的指针
 8 }ListEdgeNode;
 9 
10 typedef struct ListVertexNode {
11     vertexType vertex;            // 顶点
12      ListEdgeNode *fistEdge;        // 指向第一条边
13 } ListVertexNode;
14 
15 typedef struct GraphList{
16     int vertexNumber;            // 顶点的数量
17     int edgeNumber;                // 边的数量
18     ListVertexNode *vertex;        // 顶点集合,动态数组
19 }GraphList;
20 
21 void GraphList_create(GraphList *g);

  该文件定义的结构体如上所述,GraphList是链接表的结构体,包含了顶点数,边数和顶点集,其中顶点集根据顶点个数分配内存空间。

  实现文件是:GraphStruct.c

void GraphList_create(GraphList *g){
    printf("请分别输入图的顶点数量和边的数量,用空格隔开:");
    scanf("%d %d", &g->vertexNumber, &g->edgeNumber);        //将顶点数量和边的数量存储在结构体g中相应的变量
    //为动态数组申请空间
    g->vertex = (ListVertexNode*)malloc(g->vertexNumber * sizeof(ListVertexNode));
    //初始化顶点指的第一条边
    for (int i = 0; i < g->edgeNumber; i++){
        g->vertex[i].fistEdge = NULL;
    }

    //输入图的信息
    ListEdgeNode *listEdgeNode;
    for (int k = 0; k < g->edgeNumber; k++){
        int i, j;
        printf("请输入边(vi,vj)的下标, i和j,用空格隔开:");
        scanf("%d%d", &i, &j);
        //始终将插入的节点放在顶点所指的地一条边
        listEdgeNode = (ListEdgeNode *)malloc(sizeof(ListEdgeNode));
        listEdgeNode->index = j;
        listEdgeNode->next = g->vertex[i].fistEdge;
        g->vertex[i].fistEdge = listEdgeNode;

        listEdgeNode = (ListEdgeNode*)malloc(sizeof(ListEdgeNode));
        listEdgeNode->index = i;
        listEdgeNode->next = g->vertex[j].fistEdge;
        g->vertex[j].fistEdge = listEdgeNode;

    }

    //输出图的信息
    ListEdgeNode * len = NULL;
    for (int i = 0; i < g->vertexNumber; i++){
        
        if (g->vertex[i].fistEdge != NULL)
            len = g->vertex[i].fistEdge;
        while (len!= NULL){
            printf("%d --- %d\t", i,len->index);
            len = len->next;
        }
        printf("\n");
    }
}

  测试文件是:main.c

1 #include<stddef.h>
 2 #include "GraphStruct.h"
 3 
 4 int main(){
 5     
 6     
 7     GraphList *gl;
 8     gl = (GraphList*)malloc(sizeof(GraphList));
 9     GraphList_create(gl);
10     return 0;
11 }

  运行结果为:

图数据结构python 图数据结构存储_动态数组_07

 

  

  

  邻接矩阵适合于点少边多的图,而对于边少的图,可以考虑用邻接表。但是对于有向图,邻接表的表示有多种,有些更为复杂,在这里不一一阐述,有兴趣的可以跟本人交流。

   源代码下载:http://pan.baidu.com/s/1hqeYahQ