图的存储结构

由于图的结构比较复杂,任意两个顶点之间都可能存在联系,因此图不可能用简单的顺序存储结构来表示。那么我们来看一下如何去表示一个复杂关系的图。

1.邻接矩阵

数组表示法:用两个数组分别存储数据元素(顶点)的信息和数据元素之间的关系。

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

有了这个矩阵我们能轻易看出图中的一些信息。

  1. 我们要判断两个顶点之间有无边就非常容易了。
  2. 我们要知道某个顶点的度,就是这个顶点在其这一列的元素之和。
  3. 求顶点的邻接点就是讲矩阵中这一行元素扫描一遍,arc[i][j]为1就是邻接点。

那么我们现在来看一下下图的有向图。

有向图讲究入度与出度顶点V1的入度为1,正好是V1列个数之和,出度为2,为V1行个数之和。

对于有向网和无向网,需要在邻接矩阵中加上关系的权值,如果两个顶点之间有邻接关系,就用权值代替原来的1,否则就用无穷代替0。如下图:

邻接矩阵的存储结构。

typedef char VerType;	//顶点类型
typedef int EdgeType;	//边上的权值类型
#define MAXVEX 100		//最大顶点数
#define INFI 65535		//代表无穷
typedef struct {
	VerType vexs[MAXVEX];	//顶点表
	EdgeType arc[MAXVEX][MAXVEX];	//邻接矩阵
	int numver, numedge;	//图中当前顶点数和边数
}Graph;

void createGraph(Graph *G)
{
	int i, j, k, w;
	printf("输入顶点数和边数\n");
	scanf("%d,%d", &G->numver, &G->numedge);
	for (i = 0; i < G->numver; i++)
		scanf(&G->vexs[i]);
	//邻接矩阵初始化
	for (i = 0; i < G->numver; i++)
		for (j = 0; j < G->numedge; j++)
			G->arc[i][j] = INFI;
	for (k = 0; k < G->numedge; k++)
	{
		printf("输入vi vj 上的下表i,下标j和权w:\n");
		scanf("%d,%d,%d", &i, &j, &w);
		G->arc[i][j] = w;
		G->arc[i][j] = G->arc[j][i];		//无向图,矩阵对称
	}
}

2.邻接表

邻接矩阵是不错的一种图存储结构, 但是我们也发现,对于边数相对顶点较少的图,这种结构是存在对存储空间的极大浪费的。比如说,如果我们要处理下图这样的稀疏有向图,邻接矩阵中除了arc[1][0]有权值外, 没有其他弧,其实这些存储空间都浪费掉了。

邻接表表示方法:数组和链表相结合的表示方法,通过头结点数组保存顶点信息,用单链表保存顶点之间的关系。这样我们可以轻易的知道每个节点的度。

对于有向图,我们要注意到有向图是有方向的,我们是以顶点为弧尾来存储边表的,这样很容易就可以得到每个顶点的出度。但也有时为了便于确定顶点的入度或以顶点为弧头的弧,我们可以建立-一个有向图的逆邻接表,即对每个顶点Vi都建立-一个链接为v为弧头的表。

对于带权值的网图,可以在边表节点定义中在增加一个weight的数据域,存储权值信息即可。

typedef char VertexType;				// 顶点类型应由用户定义
typedef int EdgeType;					//边上的权值类型应由用户定义 
#define MAXVEX 20

typedef struct EdgeNode					//边表结点
{
	int adjvex;							//邻接点域,存储该顶点对应的下标
	EdgeType weight;					//用于存储权值,对于非网图可以不需要
	struct EdgeNode *next;				//链域,指向下一个邻接点
} EdgeNode;

typedef struct VertexNode				//顶点表结点
{
	VertexType data;					//顶点域,存储顶点信息
	EdgeNode *firstedge;				//边表头指针
}vertexNode, AdjList[MAXVEX];

typedef struct
{
	AdjList adjList;
	int numvertexes,numEdges;			//图中当前顶点数和边数
} GraphAdjList;


void CreateALGraph(GraphAdjList *G)
{
	int i, j, k;
	EdgeNode *e;
	printf("输入顶点数和边数:\n");
	scanf("%d, %d", &G->numvertexes, &G->numEdges);		//输入顶点数和边数
	for (i = 0; i < G->numvertexes; i++)				//读入顶点信息,建立顶点表
	{
		scanf(&G->adjList[i].data);						//输入顶点信息
		G->adjList[i].firstedge = NULL;					//将边表置为空表
	}
	for (k = 0; k < G->numEdges; k++)					//建立边表
	{
		printf("输入边(vi,vj)上的顶点序号:\n");
		scanf("&d,d", &i, &j);							//输入边(VI,Vj)上的顶点序号
		e = (EdgeNode *)malloc(sizeof(EdgeNode));		//向内存申请空间
		/*生成边表结点*/
		e->adjvex = j;									//邻接序号为j 
		e->next = G->adjList[i].firstedge;				//将e指针指向当前顶点指向的结点
		G->adjList[i].firstedge = e;					//将当前顶点的指针指向e 
		e = (EdgeNode *)malloc(sizeof(EdgeNode));		//向内存申请空间
		/*生成边表结点*/
		e->adjvex = i;//邻接序号为i
		e->next = G->adjList[j].firstedge;				//将e指针指向当前顶点指向的结点
		G->adjList[j].firstedge = e;					//将当前顶点的指针指向e 
	}
}

3.十字链表

那么对于有向图来说,邻接表是有缺陷的。关心了出度问题,想了解入度就必须要遍历整个图才能知道,反之,逆邻接表解决了入度却不了解出度的情况。有没有可能把邻接表与逆邻接表结合起来呢?答案是肯定的,就是把它们整合在一起。这就是我们现在要讲的有向图的一种存储方法: 十字链表(Orthogonal List)。
我们重新定义顶点表节点结构:

其中firstin表示入边表头指针,指向该顶点的入边表中第一一个结点,firstout 表示出边表头指针,指向该顶点的出边表中的第一个结点。

重新定义边表节点结构:

其中tailvex是指弧起点在顶点表的下标,headvex是指弧终点在顶点表中的下标,headlink是指入边表指针域,指向终点相同的下一条边,taillink 是指边表指针域,指向起点相同的下一条边。如果是网,还可以再增加一一个 weight域来存储权值。

我们重点需要来解释虚线箭头的含义,它其实就是此图的逆邻接表的表示。对于vo来说,它有两个顶点V1和v2的入边。因此Vo的firstin 指向顶点V1的边表结点中headvex为0的结点,如图7-4-10右图中的①。接着由入边结点的headlink指向下一个入边顶点Vv2,如图中的②。对于顶点V1,它有一一个入边顶点V2,所以它的firstin指向顶点V2的边表结点中headvex为1的结点,如图中的③。顶点V2和V3也是同样有一个入边顶点,如图中④和⑤。十字链表的好处就是因为把邻接表和逆邻接表整合在了一起, 这样既容易找到以vi为尾的弧,也容易找到以V1为头的弧,因而容易求得顶点的出度和入度。而且它除了结构复杂一点外,其实创建图算法的时间复杂度是和邻接表相同的,因此,在有向图的应用中,十字链表是非常好的数据结构模型。

4.邻接多重表

重新定义边表节点结构:

其中ivex和jvex是与某条边依附的两个顶点在顶点表中下标。ilink 指向依附顶点ivex的下一条边,jlink 指向依附顶点jvex的下一-条边。这就是邻接多重表结构。
我们来看结构示意图的绘制过程,理解了它是如何连线的,也就理解邻接多重表构造原理了。如图所示,左图告诉我们它有4个顶点和5条边,显然,我们就应该先将4个顶点和5条边的边表结点画出来。由于是无向图,所以ivex是0、jvex是1还是反过来都是无所谓的,不过为了绘图方便,都将ivex值设置得与一旁的顶点下标相同。

我们开始连线,如图。首先连线的①②③④就是将顶点的firstedge 指向一条边,顶点下标要与ivex的值相同,这很好理解。接着,由于顶点vo的(vo,V1) 边的邻边有(Vo,V3) 和(Vo,V2)。 因此⑤⑥的连线就是满足指向下一条依附于顶点vo 的边的目标,注意ilink指向的结点的jvex - 定要和它本身的ivex 的值相同。同样的道理,连线⑦就是指(V1,Vo) 这条边,它是相当于顶点V1指向(V1,v2) 边后的下一条。V2有三条边依附,所以在③之后就有了⑧⑨。连线四的就是顶点V3在连线④之后的下一条边。 左图一共有5条边,所以右图有10条连线,完全符合预期。

  • 7
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值