图的存储结构

       图的存储结构相对于线性表和树来说更为复杂,因为图中的顶点具有相对概念,没有固定的位置。那我们怎么存储图的数据结构呢?我们知道,图是由(VE)来表示的,对于无向图来说,其中 = (v0v1, ... , vn),= { (vi,vj) (0 <=  i, j <=  n且i 不等于j)},对于有向图= { < vi,v> (0 <=  i, j <=  n且i 不等于j)}。V是顶点的集合,E是边的集合。所以我们只要把顶点和边的集合储存起来,那么该图的所有数据就能够存储起来了。

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

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

 

图1:无向图

 

 图2:邻接矩阵

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

 

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

  头文件是:GraphStruct.h

/*GraphStruct.h
* 图的邻接矩阵存储方式,结构由顶点数量、边数量、顶点集合和边集合组成。
* 其中顶点集合一维数组,根据顶点的数量动态分配数组大小。
* 边集合是二维数组,根据顶点的数量来动态分配数组大小,对于无向图来说,该邻接矩阵是对称矩阵。
* 邻接矩阵比较适用于稠密图
*/
typedef char vertexType;
typedef int edgeType;
typedef struct GraphMatrix{

    int vertexNumber;            // 顶点数量
    int edgeNumber;              // 边的数量
    vertexType *vertex;          // 顶点集合,动态数组
    edgeType** edge;             // 边集合,二维动态数组

} GraphMatrix;

void GraphMatrix_create(GraphMatrix *g);

  

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

  实现文件是:GraphStruct.c

#include <stdio.h>
#include <malloc.h>
#include"GraphStruct.h"

void GraphMatrix_create(GraphMatrix *g){

    printf("请分别输入图的顶点数量和边的数量,用空格隔开:");
    scanf("%d %d", &g->vertexNumber, &g->edgeNumber);  //将顶点数量和边的数量存储在结构体g中相应的变量
    g->vertex = (vertexType*)malloc(g->vertexNumber * sizeof(vertexType)); //为动态数组申请空间
    //二维动态数组申请空间
    g->edge = (edgeType**)malloc(g->vertexNumber * sizeof(edgeType));
    for (int i = 0; i < g->vertexNumber; i++){
        g->edge[i] = (edgeType*)malloc(g->vertexNumber * sizeof(edgeType));
    }
    //初始化邻接矩阵的所有元素
    for (int i = 0; i < g->vertexNumber; i++){
        for (int j = 0; j < g->vertexNumber; j++)
            g->edge[i][j] = 0;
    }

    //输入图的信息
    for (int k = 0; k < g->edgeNumber; k++){

        int i, j;
        printf("请输入边(vi,vj)的下标, i和j,用空格隔开:");
        scanf("%d%d", &i, &j);
        g->edge[i][j] = 1;    //(i,j)和(j,i)指的是一条边
        g->edge[j][i] = 1;
    }

    //输出图的信息
    printf("Your graph matrix is :\n");
    for (int i = 0; i < g->vertexNumber; i++){
        for (int j = 0; j < g->vertexNumber; j++){
            printf("%d\t", g->edge[i][j]);
        }
        printf("\n");
    }

  测试文件为:main.c

#include<stddef.h>
#include "GraphStruct.h"

int main(){
      GraphMatrix *gm;
      gm = (GraphMatrix *)malloc(sizeof(GraphMatrix));
      GraphMatrix_create(gm);
      return 0;
}

  运行结果为:

图3 运行结果

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

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

图4 邻接表 

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

 

 

图5 (i,j)的邻接表

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

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

  头文件是:GraphStruct.h

/*
 *图的另一种存储结构是邻接表

*/
typedef struct ListEdgeNode{
    int index;                    // 边的下标
    struct ListEdgeNode *next;            // 指向下一个节点的指针
}ListEdgeNode;

typedef struct ListVertexNode {
    vertexType vertex;            // 顶点
     ListEdgeNode *fistEdge;        // 指向第一条边
} ListVertexNode;

typedef struct GraphList{
    int vertexNumber;            // 顶点的数量
    int edgeNumber;                // 边的数量
    ListVertexNode *vertex;        // 顶点集合,动态数组
}GraphList;

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

#include<stddef.h>
#include "GraphStruct.h"

int main(){


    GraphList *gl;
    gl = (GraphList*)malloc(sizeof(GraphList));
    GraphList_create(gl);
    return 0;
}

  运行结果为:

 

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

 

本文转载自:https://www.cnblogs.com/zhuozengsi/p/4619616.html

 

posted @ 2020-10-16 17:01  looyee  阅读(376)  评论(0编辑  收藏  举报