码迷,mamicode.com
首页 > 其他好文 > 详细

图的存储结构

时间:2014-10-10 00:31:51      阅读:443      评论:0      收藏:0      [点我收藏+]

标签:图的存储   十字链表   邻接矩阵   邻接表   边集数组   

图的存储结构(邻接矩阵)

 


 

图的存储结构

 

图的存储结构相比较线性表与树来说就复杂很多。

对于线性表来说,是一对一的关系,所以用数组或者链表均可简单存放。树结构是一对多的关系,所以我们要将数组和链表的特性结合在一起才能更好的存放。

那么我们的图,是多对多的情况,另外图上的任何一个顶点都可以被看作是第一个顶点,任一顶点的邻接点之间也不存在次序关系。

我们仔细观察以下几张图,然后深刻领悟一下:

bubuko.com,布布扣

bubuko.com,布布扣

 

因为任意两个顶点之间都可能存在联系,因此无法以数据元素在内存中的物理位置来表示元素之间的关系(内存物理位置是线性的,图的元素关系是平面的)。

如果用多重链表来描述倒是可以做到,但在几节课前的树章节我们已经讨论过,纯粹用多重链表导致的浪费是无法想像的(如果各个顶点的度数相差太大,就会造成巨大的浪费)。

所幸,前辈们已经帮想好了出路,我们接下来会谈图的五种不同的存储结构,大家做好准备哦~

 

邻接矩阵(无向图)

 

考虑到图是由顶点和边或弧两部分组成,合在一起比较困难,那就很自然地考虑到分为两个结构来分别存储。

顶点因为不区分大小、主次,所以用一个一维数组来存储是狠不错的选择。

 

而边或弧由于是顶点与顶点之间的关系,一维数组肯定就搞不定了,那我们不妨考虑用一个二维数组来存储。

于是我们的邻接矩阵方案就诞生了!

 

图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。

bubuko.com,布布扣

 

我们可以设置两个数组,顶点数组为vertex[4]={V0,V1,V2,V3},边数组arc[4][4]为对称矩阵(0表示不存在顶点间的边,1表示顶点间存在边)。

 

对称矩阵:所谓对称矩阵就是n阶矩阵的元满足a[i][j]=a[j][i](0<=i,j<=n)。即从矩阵的左上角到右下角的主对角线为轴,右上角的元与左下角相对应的元全都是相等的。

 

有了这个二维数组组成的对称矩阵,我们就可以很容易地知道图中的信息:

  1. 要判定任意两顶点是否有边无边就非常容易了;
  2. 要知道某个顶点的度,其实就是这个顶点Vi在邻接矩阵中第i行(或第i列)的元素之和;
  3. 求顶点Vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点咯。

 

邻接矩阵(有向图)

 

无向图的边构成了一个对称矩阵,貌似浪费了一半的空间,那如果是有向图来存放,会不会把资源都利用得很好呢?

bubuko.com,布布扣

 

可见顶点数组vertex[4]={V0,V1,V2,V3},弧数组arc[4][4]也是一个矩阵,但因为是有向图,所以这个矩阵并不对称,例如由V1到V0有弧,得到arc[1][0]=1,而V0到V1没有弧,因此arc[0][1]=0。

另外有向图是有讲究的,要考虑入度和出度,顶点V1的入度为1,正好是第V1列的各数之和,顶点V1的出度为2,正好是第V1行的各数之和。

 

邻接矩阵(网)

 

在图的术语中,我们提到了网这个概念,事实上也就是每条边上带有权的图就叫网。

bubuko.com,布布扣


以邻接矩阵创建有向图为例:
#include "stdlib.h"
#include "stdio.h"

#define MAX_VERTEX_NUM 10             /*最多顶点个数*/
#define INFINITY 32768             /*表示极大值,即∞*/
#define True 1
#define False 0
#define Error -1
#define Ok 1

typedef enum{DG, DN, UDG, UDN} GraphKind;  /*图的种类:DG表示有向图, DN表示有向网, UDG表示无向图, UDN表示无向网*/
typedef char VertexData;    /*假设顶点数据为字符型*/

typedef struct ArcNode
{
	int adj;   /*对于无权图,用1或0表示是否相邻;对带权图,则为权值类型*/

} ArcNode;

typedef struct
{
	VertexData vexs[MAX_VERTEX_NUM];                        /*顶点向量*/
	ArcNode arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];   /*邻接矩阵*/
	int vexnum,arcnum;          /*图的顶点数和弧数*/
	GraphKind kind;                 /*图的种类标志*/
}AdjMatrix;      /*(Adjacency Matrix Graph)*/

int LocateVertex(AdjMatrix *G,VertexData v)    /*求顶点位置函数*/
{
	int j=Error,k;
	for(k=0;k<G->vexnum;k++)
		if(G->vexs[k]==v)
		{ 
			j=k; 
			break; 
		}
	return(j);
}
int CreateDN(AdjMatrix *G)  /*创建一个有向网*/
{ 
	int i,j,k,weight; 
	VertexData v1,v2;
	printf("输入图的顶点数和弧数\n");
	fflush(stdin);
    scanf("%d,%d",&G->arcnum,&G->vexnum); /*输入图的顶点数和弧数*/
    for(i=0;i<G->vexnum;i++)       /*初始化邻接矩阵*/
		for(j=0;j<G->vexnum;j++)
			G->arcs[i][j].adj=INFINITY;
    for(i=0;i<G->vexnum;i++)
	{
        printf("输入图的顶点\n");
		fflush(stdin);
		scanf("%c",&G->vexs[i]);  /* 输入图的顶点*/
	}
	for(k=0;k<G->arcnum;k++)
	{ 
		printf("输入一条弧的两个顶点及权值\n");
		fflush(stdin);
		scanf("%c,%c,%d",&v1,&v2,&weight);/*输入一条弧的两个顶点及权值*/
	  	i=LocateVertex(G,v1);
	    j=LocateVertex(G,v2); 
	    G->arcs[i][j].adj=weight;  /*建立弧*/
	} 
	return(Ok);
}

void main()
{
	AdjMatrix G;
	CreateDN(&G);
}

无向与有向的关键区别在于初始化,无向为节省空间,可以只初始化下三角或上三角,空间复杂度n(n-1)/2.

邻接表(无向图)

 

邻接矩阵看上去是个不错的选择,首先是容易理解,第二是索引和编排都很舒服~

但是我们也发现,对于边数相对顶点较少的图,这种结构无疑是存在对存储空间的极大浪费。

bubuko.com,布布扣

邻接表(有向图)

 

因此我们可以考虑另外一种存储结构方式,例如把数组与链表结合一起来存储,这种方式在图结构也适用,我们称为邻接表(AdjacencyList)。

 

邻接表的处理方法是这样:

  1. 图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过数组可以较容易地读取顶点信息,更加方便。
  2. 图中每个顶点Vi的所有邻接点构成一个线性表,由于邻接点的个数不确定,所以我们选择用单链表来存储。

 

bubuko.com,布布扣

邻接表(无向图)

 

邻接表(有向图)

 

若是有向图,邻接表结构也是类似的,我们先来看下把顶点当弧尾建立的邻接表,这样很容易就可以得到每个顶点的出度:

bubuko.com,布布扣

 

但也有时为了便于确定顶点的入度或以顶点为弧头的弧,我们可以建立一个有向图的逆邻接表:

bubuko.com,布布扣

 

此时我们很容易就可以算出某个顶点的入度或出度是多少,判断两顶点是否存在弧也很容易实现。

 

邻接表(网)

 

对于带权值的网图,可以在边表结点定义中再增加一个数据域来存储权值即可:

bubuko.com,布布扣

邻接表(网)


十字链表

 

邻接表固然优秀,但也有不足,例如对有向图的处理上,有时候需要再建立一个逆邻接表~

那我们思考了:有没有可能把邻接表和逆邻接表结合起来呢?

 

答案是肯定的,这就是我们现在要谈的十字链表(Orthogonal List)

为此我们重新定义顶点表结点结构:

bubuko.com,布布扣

十字链表

 

接着重新定义边表结点结构:

bubuko.com,布布扣

十字链表

bubuko.com,布布扣

十字链表

 

十字链表的好处就是因为把邻接表和逆邻接表整合在了一起,这样既容易找到以Vi为尾的弧,也容易找到以Vi为头的弧,因而容易求得顶点的出度和入度。

十字链表除了结构复杂一点外,其实创建图算法的时间复杂度是和邻接表相同的,因此,在有向图的应用中,十字链表也是非常好的数据结构模型。

 以有向图为例:

/*图的十字链表结构形式化定义如下:*/
#include <malloc.h>
#include <stdio.h>
#define MAX_VERTEX_NUM 10              /*最多顶点个数*/
#define INFINITY 32768             /*表示极大值,即∞*/
#define True 1
#define False 0

#define Error -1
#define Ok 1
typedef enum{DG, DN, UDG, UDN} GraphKind;  /*图的种类*/
typedef char VertexData;    /*假设顶点数据为字符型*/


typedef struct ArcNode 
{
	int tailvex,headvex;  
	struct ArcNode *hlink,*tlink;    
}ArcNode;

typedef struct VertexNode
{
	VertexData data;    /*顶点信息*/
	ArcNode *firstin,*firstout;   
}VertexNode;

typedef struct
{
	VertexNode vertex[MAX_VERTEX_NUM];   
	int vexnum,arcnum;   /*图的顶点数和弧数*/
	GraphKind kind;              /*图的种类*/
}OrthList;      /*图的十字链表表示法(Orthogonal List)*/

int LocateVertex(OrthList *G,VertexData v)    /*求顶点位置函数*/
{
	int j=Error,k;
	for(k=0;k<G->vexnum;k++)
		if(G->vertex[k].data==v)
		{ 
			j=k; 
			break; 
		}
	return(j);
}
/*建立一个有向图的十字链表的算法如下:*/

void  CrtOrthList(OrthList *g)
/*从终端输入n个顶点的信息和e条弧的信息,以建立一个有向图的十字链表*/
{
	int n,e;
	int i,j,k;
	char vt,vh;

	ArcNode *p;
	printf("从键盘输入图的顶点个数和弧的个数");
	fflush(stdin);
	scanf("%d,%d",&n,&e);  /*从键盘输入图的顶点个数和弧的个数*/
	g->vexnum = n;
	g->arcnum = e;
	for(i=0;i<n;i++) 
	{
		printf("输入图的顶点");
		fflush(stdin);
		scanf("%c",&g->vertex[i].data);
		g->vertex[i].firstin=NULL;g->vertex[i].firstout=NULL;
	}
	for(k=0;k<e;k++) 
	{
		printf("输入一条弧的两个顶点");
		fflush(stdin);
		scanf("%c,%c",&vt,&vh);
		i=LocateVertex(g,vt);
		j = LocateVertex(g,vh);
		p=(ArcNode*)malloc(sizeof(ArcNode));
		p->tailvex=i;
		p->headvex=j;
		p->tlink=g->vertex[i].firstout;
		g->vertex[i].firstout=p;
		p->hlink=g->vertex[j].firstin;
		g->vertex[j].firstin=p;
    }
}/* CrtOrthList */

void main()
{
	OrthList g;
	CrtOrthList(&g);
}


邻接多重表

 

讲了有向图的优化存储结构,对于无向图的邻接表,有没有问题呢?

如果我们在无向图的应用中,关注的重点是顶点的话,那么邻接表是不错的选择,但如果我们更关注的是边的操作,比如对已经访问过的边做标记,或者删除某一条边等操作,邻接表就显得不那么方便了。

 

到底有多烦?小甲鱼用图片告诉你:

若要删除(V0,V2)这条边,就需要对邻接表结构中边表的两个结点进行删除操作。

bubuko.com,布布扣

邻接多重表

 

因此,我们也仿照十字链表的方式,对边表结构进行改装,重新定义的边表结构如下:

bubuko.com,布布扣

 

其中iVex和jVex是与某条边依附的两个顶点在顶点表中的下标。iLink指向依附顶点iVex的下一条边,jLink指向依附顶点jVex的下一条边。

也就是说在邻接多重表里边,边表存放的是一条边,而不是一个顶点。(注意这句话)

 

不急,马上进入No pic you say a J8!环节~

bubuko.com,布布扣

邻接多重表

 

边集数组

 

边集数组是由两个一维数组构成,一个是存储顶点的信息,另一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标(begin)、终点下标(end)和权(weight)组成。

bubuko.com,布布扣

边集数组


本文部分内容来自鱼C工作室,详细请见:http://blog.fishc.com/

关于邻接表与邻接多重表有时间会再补上,如有问题,请跟帖指出!!

图的存储结构

标签:图的存储   十字链表   邻接矩阵   邻接表   边集数组   

原文地址:http://blog.csdn.net/u014492609/article/details/39937657

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!