数据结构之图(一)图的存储结构

  图的存储结构相对于线性表和树来说更为复杂,因为图中的顶点具有相对概念,没有固定的位置。那我们怎么存储图的数据结构呢?我们知道,图是由(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。下图是一个无向图和对应的临街矩阵:

  图1:无向图

图2:邻接矩阵

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

  以下用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 }

  运行结果为:

图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

 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 }

  运行结果为:

  

  

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

时间: 2024-10-12 09:23:01

数据结构之图(一)图的存储结构的相关文章

数据结构之图(术语、存储结构、遍历)

1.相关术语 顶点(Vertex).弧(Arc).弧头(初始点).弧尾(终结点).边(Edge).有向图(Directed graph).无向图(Undigraph).完全图(Completed grapg).有向完全图.稀疏图(Sparse graph).稠密图(Dense graph).权(weigh).网(network).无向网.有向网.子图(Subgraph).邻接点(Adjacent).度(Degree).入度(Indegree).出度(Outdegree).路径(path).简单路

7-3-有向图的十字链表存储结构-图-第7章-《数据结构》课本源码-严蔚敏吴伟民版

课本源码部分 第7章  图 - 有向图的十字链表存储结构 ——<数据结构>-严蔚敏.吴伟民版        源码使用说明  链接??? <数据结构-C语言版>(严蔚敏,吴伟民版)课本源码+习题集解析使用说明        课本源码合辑  链接??? <数据结构>课本源码合辑        习题集全解析  链接??? <数据结构题集>习题解析合辑        本源码引入的文件  链接? Status.h.Scanf.c.LinkQueue.c        

数据结构--图的定义和存储结构

图的定义 图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成.注意:在图结构中,不允许没有顶点,在定义中,如果V是顶点的集合,则强调了顶点集合V的有穷非空. 在图中,若不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图. 图的存储结构 邻接矩阵 考虑到图是由顶点和边或者弧两部分组成的.合在一起比较困难,那就自然地考虑到分两个结构来分别存储.顶点不分大小.主次,所以用一个一位数组来存储是很不错的选择.而边或者弧是顶点与顶点之间的关系,一维搞不定,那就考虑用一个二维数组来存

数据结构(二十八)图的五种存储结构

由于图的结构比较复杂,任意两个顶点之间都可能存在联系,因此无法以数据元素在内存中的物理位置来表示元素之间的关系,也就是说,图不可能用简单的顺序存储结构来表示. 多重链表的方式,即以一个数据域和多个指针域组成的结点表示图中的一个结点,尽管可以实现图结构,但是如果各个顶点的度数相差很大,按度数最大的顶点设计结点结构会造成很多存储单元的浪费,而若按每个顶点自己的度数设计不同的结点结构,又带来操作的不变. 图的类型主要有4种:无向图.有向图.无向网和有向网. 图的五种常见的存储结构:领接矩阵.领接表.十

图的定义和存储结构

学习目的及应用:导航 .GPS.网络规划.路径规划 交通流可以用一个图来模型化,每一条街道交叉口表示一个顶点,而每一条街道就是一条边.边的值可能是代表限制速度,或者是容量(车道的数目)等等.此时我们可能需要找出一条最短路,或用该信息找出最可能产生交通瓶颈的位置, 图的定义: 是由顶点的有穷非空集合和顶点之间边的集合组成一种数据结构 表示方法: graph = ( V,E ) V = { x | x 属于 某个数据对象} 是顶点的有穷非空集合 E = { (x, y) | (x, y) 属于 V

图的理解:存储结构与邻接矩阵

存储结构 要存储一个图,我们知道图既有结点,又有边,对于有权图来说,每条边上还带有权值.常用的图的存储结构主要有以下二种: 邻接矩阵 邻接表 邻接矩阵 我们知道,要表示结点,我们可以用一个一维数组来表示,然而对于结点和结点之间的关系,则无法简单地用一维数组来表示了,我们可以用二维数组来表示,也就是一个矩阵形式的表示方法. 我们假设A是这个二维数组,那么A中的一个元素aij不仅体现出了结点vi和结点vj的关系,而且aij的值正可以表示权值的大小. 以下是一个无向图的邻接矩阵表示示例: 从上图我们可

数据结构--线性表的链式存储结构

一 线性表的链式存储结构 A.链式存储的定义为了表示每个数据元素与直接后继元素之间的逻辑关系:数据元素除了存储本身的信息外,还需要存储其直接后继的信息图示B链式存储逻辑结构基于链式存储结构的线性表中,每个结点都包含数据域和指针域1.数据域:存储数据元素本身2.指针域:存储相邻结点的地址图示C链表中的基本概念1.头结点--链表中的辅助结点,包含指向第一个数据元素的指针(方便插入和删除)2.数据结点--链表中代表数据元素的结点,表现形式为:(数据元素,地址)3.尾节点--链表中的最后一个数据结点,包

数据结构:队列的链式存储结构

链队列的实现方法: 队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,简称为链队列.为了操作上的方便,我们将队头指针指向链队列的头节点,而队尾指针指向终端节点.空队列时,front和rear都指向头节点. 注意:这里的实现是有头结点的,在队列的初始化函数中要为头结点开辟空间. 链队列的实现代码: #include <iostream> #include <stdlib.h> using namespace std; /**********************

数据结构之树的基本运算和存储结构

树的运算: 寻找某节点 插入.删除某节点 遍历树中每个节点 先根遍历 后根遍历 层次遍历 树的存储结构: 1.双亲存储结构 typedef struct { ElemType data; int parent; }PTree[Maxsize]; 2.孩子链存储结构 typedef struct node { ElemType data; struct node *sons[MaxSons]; }TSonNode; 3.孩子兄弟链存储结构 typedef struct tnode { ElemTy

大话数据结构之栈的链式存储结构

#include<iostream> //#include<time.h> //#include <stdlib.h> using namespace std; #define OK 1 #define TRUE 1 #define FALSE 0 #define ERROR 0 typedef int status;//返回的状态值 typedef int elemtype;//节点里数据的类型 //每一个节点的数据结构 typedef struct stacknod