图的四种存储方式(邻接矩阵、邻接表、十字链表、邻接多重表)

图的存储必须要完整、准确地反应顶点集和边集的信息。根据不同图的结构和算法,采用不同的存储方式将对程序的效率产生相当大的影响,因此所选的存储结构应适合于待求解的问题。下面介绍四种图的存储方式。

一、邻接矩阵法

1、概念:

邻接矩阵存储是指用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即各顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。
【在简单应用中,可直接用二维数组作为图的邻接矩阵(顶点信息等均可忽略)】

顶点数为n的图G=(V, E)的邻接矩阵A是n×n的,将G的顶点编号为v1,v2,…,vn,则:

对于带权图而言,若顶点vi和vj之间有边相连,则邻接矩阵中对应项存放着该边对应的权值,若顶点vi和vj不相连,则通常用0或∞来代表这两个顶点之间不存在边:

有向图、无向图和网对应的邻接矩阵示例如下图所示:

2、图的邻接矩阵存储结构定义:

#define MaxVertexNum 100//顶点数目的最大值
typedef char VertexType;//顶点对应的数据类型
typedef int EdgeType;//边对应的数据类型
typedef struct{
    VertexType vex[MaxVertexNum];//顶点表
    EdgeType edge[MaxVertexNum][MaxVertexNum];//邻接矩阵(边表)
    int vexnum, arcnum;//图当前的顶点数和边数
}MGraph;

【注】邻接矩阵表示法的空间复杂度为O(n2),其中n为图的顶点数|V|。

3、图的邻接矩阵存储表示法的特点:

  • 对于有n个顶点的无向图,有A[i][i]=0,其中1<=i<=n。
  • 无向图的邻接矩阵一定是一个对称矩阵(且唯一),即A[i][j]=A[j][i](1<=i<=n,1<=j<=n),因此,在实际存储邻接矩阵时只需存储上(或下)三角矩阵的元素(压缩存储)。
  • 对于无向图,邻接矩阵的第 i 行(或第 i 列)非零元素(或非∞元素)的个数正好是顶点 i 的度TD(vi)。
  • 对于有向图,邻接矩阵的第 i 行非零元素(或非∞元素)的个数正好是顶点 i 的出度OD(vi);邻接矩阵的第 i 列非零元素(或非∞元素)的个数正好是顶点 i 的入度ID(vi)。
  • 用邻接矩阵存储图,很容易确定图中任意两个顶点之间是否有边相连,但是,要确定图中有多少条边,则必须按行、按列对每个元素进行检测,所花费的时间代价很大。
  • 稠密图(即边数较多的图)适合采用邻接矩阵的存储方式。
  • (了解即可)设图G的邻接矩阵为A,Am的元素Am [i][j]等于由顶点i到顶点j的长度为m的路径的数目。

4、例题:

C

D

D

B、D

C【2013年统考真题】

二、邻接表法

1、概念:

当一个图为稀疏图时,使用邻接矩阵法会浪费大量的存储空间,而图的邻接表法结合了顺序存储和链式存储的方法,大大减少了这种不必要的浪费。

邻接链表是指对图G中的每个顶点vi建立一个单链表,第i个单链表中的结点表示依附于顶点vi的边(对于有向图则是以顶点vi为尾的弧),这个单链表就称为顶点vi边表(对于有向图则称为出边表)。边表的头指针和顶点的数据信息采用顺序存储,称为顶点表,所以在邻接表中存在两种结点:顶点表结点和边表结点,如下图所示:

顶点表结点由两个域组成:

  • 顶点域(data)存储顶点vi的相关信息;
  • 边表头指针域(firstarc)指向第一条边的边表结点。

边表结点至少由两个域组成:

  • 邻接表域(adjvex)存储与头结点顶点vi邻接的顶点编号;
  • 指针域(nextarc)指向下一条边的边表结点。

无向图、有向图对应的邻接表示例如下图所示:


2、图的邻接表存储结构定义:

#define MaxVertexNum 100//图中顶点数目的最大值
typedef struct ArcNode{//边表结点
    int adjvex;//该弧所指向的顶点的位置
    struct ArcNode *nextarc;//指向下一条弧的指针
    //InfoType info;//网的边权值
}ArcNode;
typedef struct VNode{//顶点表结点
    VertexType data;//顶点信息
    ArcNode *firstarc;//指向第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
typedef struct{
    AdjList vertices;//邻接表
    int vexnum, arcnum;//图的顶点数和弧数
}ALGraph;//ALGraph是以邻接表存储的图类型

【注】若G为无向图,则图的邻接表存储方法所需的存储空间为O(|V|+2|E|),若G为有向图,则所需的存储空间为O(|V|+|E|)。

3、图的邻接表存储表示法的特点:

  • 对于稀疏图(即边数较少的图),采用邻接表表示将极大地节省存储空间。
  • 在邻接表中,给定一个顶点,能很容易地找出它的所有邻边,因为只需要读取它的邻接表。在邻接矩阵中,相同的操作则需要扫描一行,花费的时间为O(n)。但是,若要确定给定的两个顶点间是否存在边,则在邻接矩阵中可以立刻查到,而在邻接表中则需要在相应结点对应的边表中查找另一结点,效率较低。
  • 在无向表的邻接表中,求某个顶点的度只需计算其邻接表中的边表结点个数。在有向图的邻接表中,求某个顶点的出度只需计算其邻接表中的边表结点个数;但求某个顶点x的入度则需遍历全部的邻接表,统计邻接点(adjvex)域为x的边表结点个数。
  • 图的邻接表表示并不唯一,因为在每个顶点对应的边表中,各边结点的链接次序可以是任意的,它取决于建立邻接表的算法及边的输入次序。

4、例题:

B、A、D、C、B、D

三、十字链表法

1、概念:

十字链表是有向图的一种链式存储结构。在十字链表中,有向图的每条弧用一个结点(弧结点)来表示,每个顶点也用一个结点(顶点结点)来表示。如下图所示:

弧结点中有5个域:

  • tailvex域指示弧尾顶点的编号;
  • headvex域指示弧头顶点的编号;
  • 头链域hlink指向弧头相同的下一个弧结点;
  • 尾链域tlink指向弧尾相同的下一个弧结点;
  • info域存放该弧的相关信息。

这样,弧头相同的弧在同一个链表上,弧尾相同的弧也在同一个链表上。

顶点结点中有3个域:

  • data域存放该顶点的数据信息,如顶点名称;
  • firstin域指向以该顶点为弧头的第一个弧结点;
  • firstout域指向以该顶点为弧尾的第一个弧结点。

2、有向图的十字链表表示示例:

注意:顶点结点之间是顺序存储的,弧结点省略了info域。

在十字链表中,既容易找到vi为尾的弧,也容易找到vi为头的弧,因而容易求得顶点的出度和入度。图的十字链表表示是不唯一的,但一个十字链表表示唯一确定一个图。下面来详细分析一下上面的示例:

十字链表适用于有向图,它实际上是邻接表和逆邻接表的结合。邻接表的单链表指向的是顶点的出边表,与之相反,逆邻接表的单链表指向的是顶点的入边表。

我们先来看顶点的出边表部分:(进行水平方向的连接)
1)v1指向v2、v3

2)v3指向v1、v4
v4指向v1、v2、v3

再来看顶点的入边表部分:(进行垂直方向的连接)
3)v1被v3、v4指向:

4)v2被v1、v4指向
v3被v1、v4指向
v4被v3指向:

再举一个例子:(自己练习一下吧)

3、例题:

B

四、邻接多重表法

1、概念:

邻接多重表是无向图的一种链式存储结构。在邻接表中,容易求得顶点和边的各种信息,但在邻接表中求两个顶点之间是否存在边而对边执行删除等操作时,需要分别在两个顶点的边表中遍历,效率较低。与十字链表相似,在邻接多重表中,每个顶点、每条边用一个结点表示,其结构如下所示:


边结点中有5个域:

  • ivex域指示该边依附的其中一个顶点的编号;
  • jvex域指示该边依附的另一个顶点的编号;
  • link域指向下一条依附于顶点ivex的边;
  • jink域指向下一条依附于顶点jvex的边;
  • info域存放该边的相关信息。


顶点结点中有2个域:

  • data域存放该顶点的相关信息;
  • firstedge域指向第一条依附于该顶点的边。

在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,因为每条边依附于两个顶点,所以每个边结点同时链接在两个链表中。对无向图而言,其邻接多重表和邻接表的差别仅在于:同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。

2、无向图的邻接多重表表示示例:

注意:顶点结点之间是顺序存储的,弧结点省略了info域,邻接多重表的各种基本操作的实现和邻接表类似。下面来详细分析一下上面的示例:

1)图中顶点b和顶点c所连的边(3条)最多,因此先画顶点b的边表:
b-a(1-0)、b-c(1-2)、b-e(1-4)

2)除去三条边后,顶点c和顶点d所连的边(2条)最多,由于间隔一个顶点画出的图更好看,因此画顶点d的边表:
d-a(3-0)、d-c(3-2)

3)最后只剩e-c(4-2)这条边了,把它加入顶点e的边表中:

4)连线,规则是与编号相同的就串联起来,比如顶点a的编号为0,那么找到边表中有含0的结点,依次串联起来即可:

可以看出邻接多重表的表示方式不唯一。

再举两个例子:(自己练习一下吧)


3、例题:

A

4、图的四种存储方式的总结:

  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,邻接矩阵可以用十字链表来表示。十字链表是一种用于表示稀疏矩阵的数据结构,它可以更有效地存储和访问矩阵中的非零元素。邻接矩阵虽然可以表示稠密,但是对于稀疏来说,它会浪费大量的空间。因此,使用十字链表可以更好地支持稀疏存储和操作。 在十字链表中,每个非零元素对应一个节点,节点中记录了该元素的值、行列下标以及上下左右四个指针。具体来说,对于一个n*n的邻接矩阵,我们可以定义两个头节点row_head和col_head,分别表示行链表和列链表的头结点。对于每个非零元素matrix[i][j],我们可以创建一个节点node,同时将node分别插入到row_head[i]和col_head[j]所表示的链表中。在节点node中,我们可以记录matrix[i][j]的值以及上下左右四个指针,用于链接其他的非零元素。 以下是一个简单的示例代码,用于将一个邻接矩阵转换为十字链表的形式: ```c #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 100 typedef struct node { int row; int col; int value; struct node *up, *down, *left, *right; } Node; Node *create_node(int row, int col, int value) { Node *node = (Node *)malloc(sizeof(Node)); node->row = row; node->col = col; node->value = value; node->up = node->down = node->left = node->right = NULL; return node; } void free_node(Node *node) { free(node); } void print_cross_list(Node *row_head[], int n) { for (int i = 0; i < n; i++) { for (Node *p = row_head[i]->right; p != row_head[i]; p = p->right) { printf("(%d,%d,%d) ", p->row, p->col, p->value); } printf("\n"); } } void adjacency_matrix_to_cross_list(int **matrix, int n, Node *row_head[], Node *col_head[]) { for (int i = 0; i < n; i++) { row_head[i] = create_node(i, -1, 0); col_head[i] = create_node(-1, i, 0); row_head[i]->right = row_head[i]; col_head[i]->down = col_head[i]; Node *last = row_head[i]; for (int j = 0; j < n; j++) { if (matrix[i][j] != 0) { Node *node = create_node(i, j, matrix[i][j]); node->left = last; node->right = row_head[i]; last->right = node; row_head[i]->left = node; last = node; node->up = col_head[j]; node->down = col_head[j]->down; col_head[j]->down = node; node->down->up = node; } } } } int main() { int n = 5; int matrix[MAX_SIZE][MAX_SIZE] = {{0, 1, 0, 2, 0}, {0, 0, 0, 0, 1}, {0, 0, 0, 0, 0}, {0, 0, 1, 0, 0}, {0, 0, 0, 0, 0}}; Node *row_head[MAX_SIZE], *col_head[MAX_SIZE]; adjacency_matrix_to_cross_list(matrix, n, row_head, col_head); print_cross_list(row_head, n); return 0; } ``` 以上代码中,`Node`结构体表示十字链表中的一个节点,`create_node`函数用于创建一个节点,`free_node`函数用于释放节点的内存,`print_cross_list`函数用于打印十字链表的内容。在`main`函数中,我们首先定义一个大小为5的邻接矩阵,并将其转换为十字链表的形式,然后打印结果。在`adjacency_matrix_to_cross_list`函数中,我们首先创建两个头节点row_head和col_head,分别表示行链表和列链表的头结点。然后,对于每个非零元素matrix[i][j],我们创建一个节点node,并将node分别插入到row_head[i]和col_head[j]所表示的链表中。在节点node中,我们记录matrix[i][j]的值以及上下左右四个指针,用于链接其他的非零元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值