图的邻接矩阵表示法&邻接表表示法

图表示是一种多对多的关系的数据结构。因为线性表表示的是一种一对一的关系的数据结构,树表示的是一种一对多的数据结构,所以图把线性表和树都包含在内。图由一个非空的有限顶点集合和一个有限边集合组成。当我们描述图的时候,一定要包含以下两个元素:

1、一组顶点:例如用Vertex表示顶点的集合。

2、一组边:用Edge表示边的集合,一条边是一组顶点对。

      •无向边 :(ij)∈EdgeijVertex。即双向的,即可从i走到j,也可以从j走到i

•有向边 :< ij >EdgeijVertex。即单向的(单行线),只可从i走到j或从j走到i

•图不考虑重边和自己连自己的情况。

初始化一个图是,可以一条边也没有,但是不能一个顶点也没有。

那么怎么用代码表示一个图?

第一种方法是邻接矩阵表示法:即用矩阵,一个二维数组来表示一个图。

#include <stdio.h>
#include <stdlib.h>

#define MaxVertex 100  /*最大顶点数*/
#define INFINITY 65535 /*双字节无符号整数的最大值*/
typedef int Vertex; /*二维数组的下标表示顶点,为整型*/
typedef int WeightType; /*边的权值的类型*/ 
typedef char DataType; /*顶点存储的数据类型为字符型*/

/*图的数据结构*/
typedef struct GraphNode *PtrlGraphNode;
struct GraphNode {
	int numv; /*顶点数*/ 
	int nume; /*边数*/ 
	WeightType wt[MaxVertex][MaxVertex]; /*邻接矩阵*/
	DataType Data[MaxVertex]; /*存放顶点的数据*/
	/*很多情况下,顶点无数据,此时Data[]可以不用*/
};
typedef PtrlGraphNode Graph;/*用邻接矩阵存储的图*/

用一个二维数组来表示图中一组顶点对的关系,还有一个一维数组来存放顶点的数据。

/*边的数据结构*/
typedef struct EdgeNode *PtrlEdgeNode;
struct EdgeNode {
	Vertex V1, V2; /*有向边<V1, V2>*//*无向边(V1, V2)*/
	WeightType weight; /*权重*/
};
typedef PtrlEdgeNode Edge;

边的数据结构,一条边由一组顶点对和权重表示(即顶点对的关系)。

表示图的各个数据结构都建立好后,就开始建立一个图,思路是先创建一个有全部顶点但没有边的图,在逐条插入边。

/*建立一个有VertexNum个顶点但没有边的空图*/
Graph CreatGraph(int VertexNum) 
{
	Vertex i,j;
	Graph MyGraph;
	
	MyGraph=(Graph)malloc(sizeof(struct GraphNode));/*申请空间建立图*/
	MyGraph->numv=VertexNum;
	MyGraph->nume=0; //初始化边数为0; 
	
	/*对二维数组矩阵初始化*/
	for (i=0;i<MyGraph->numv;i++) {
		for (j=0;j<MyGraph->numv;j++) {
			MyGraph->wt[i][j]=INFINITY;
		}
	}
	return MyGraph;
}

CreatGraph函数中输入一个值来创建有Vertexnum个结点的图,然后初始化二维数组中的元素为无穷(或0)。

/*插入边*/
void InsertEdge(Graph MyGraph, Edge MyEdge) 
{
	/*插入边<V1, V2>*/
	MyGraph->wt[MyEdge->V1][MyEdge->V2]=MyEdge->weight;
	
	/*如果是无向图,还要加上插入边<V2, V1>*/
	MyGraph->wt[MyEdge->V2][MyEdge->V1]=MyEdge->weight;
}

然后插入边,把边的V1V2,也就是两个顶点(对应二维数组的两个下标),以及权值插入到对应二维数组的位置。

Graph BuildGraph() 
{
	Graph MyGraph; /*建图*/
	Edge MyEdge; /*建边*/ 
	Vertex v;
	int VertexNum,i;
	
	printf("请输入顶点个数");
	scanf("%d", &VertexNum); 
	MyGraph=CreatGraph(VertexNum);/*建立一个有numv个顶点,没有边的图*/
	
	printf("请输入边数:");
	scanf("%d", &MyGraph->nume);   /* 读入边数 */
	MyEdge=(Edge)malloc(sizeof(struct EdgeNode));/*建立边结点*/
	/*插入边:格式为:"起点->终点->权重"。插入到邻接矩阵*/ 
	for (i=0;i<MyGraph->nume;i++) {
		scanf("%d %d %d",&MyEdge->V1, &MyEdge->V2, &MyEdge->weight);
		InsertEdge(MyGraph, MyEdge);
	}
	
	/*如果顶点有数据,就读入数据*/
	for (i=0;i<MyGraph->numv;i++) {
		scanf("%c", &MyGraph->Data[i]);
	}
	
	return MyGraph;
}

Graph[ n ][ n ]二维数组中,用n个顶点从0n-1编号。如果一组顶点对<  ij  >是图Graph中的边的话,就令Graph[ i ][ j ]=1,否则等于0

用邻接矩阵表示法表示出来的矩阵图,有以下特点:

1、矩阵上的对角线全都是0。因为图中的顶点对不会出现自己连自己的情况,所以对于矩阵图中Graph[ n ][ n ]一定是等于0

2、邻接矩阵表示法表示出来的矩阵无向图,一定是沿对角线对称的,也就是说一定是一个对称矩阵。这是因为,假设图中结点12是有一条边的,所以21之间也是有一条边的,所以Graph[ 1 ][ 2 ]Graph[ 2 ][ 1 ]一定是相等都等于1的。


用矩阵的方式,好处是容易检查某条边(顶点对)是否存在,容易找出一个顶点的邻接点,方便计算顶点的度。不过矩阵表示的不好在于,对于稀疏图来说,邻接矩阵的方式会浪费空间,因为稀疏图即边很少,也就是二维数组里很多元素都是0。矩阵在稀疏图的情况下浪费空间也浪费时间,比如我想统计稀疏图里一共有多少条边,把二维数组遍历一遍,统计元素1有多少个,问题是1很少,所以造成很多对0的遍历,是浪费时间的。

那么如何解决邻接矩阵浪费空间和时间的问题?我们知道线性表,线性表顺序存储需要实现分配好内存,这样有可能造成空间浪费或空间不够用,于是就有了链表的方法,同样图的表示方法也可以用链表的方式,就是邻接表表示法。

邻接表表示法,即用一个链表的集合,一个一维指针数组,依然用数组下标表示顶点。所以数组的大小就是图中顶点的个数。

#include <stdio.h>
#include <stdlib.h>

#define MaxVertex 100  /*最大顶点数*/
typedef int Vertex; /*顶点的下标表示顶点*/ 
typedef int WeightType; /*边的权值类型为整型*/
typedef char DataType; /*顶点存储的数据的类型*/

/*边的数据结构*/
typedef struct EdgeNode *PtrlEdgeNode;
struct EdgeNode {
	Vertex V1, V2;  /* 向边<V1, V2>*/
	WeightType weight; /*权重*/
};
typedef PtrlEdgeNode Edge;

/*邻接点的数据结构*/ 
typedef struct PointNode *PtrlPointNode;
struct PointNode {
	Vertex index; /*邻接点的下标*/
	WeightType weight; /*边的权重*/
	PtrlPointNode Next; /*指向下一个邻接点的指针*/
};

/*顶点表头结点的数据结构*/
typedef struct HeadNode {
	PtrlPointNode HeadEdge; /*边的表头结点指针*/
	DataType Data; /*顶点的数据*/
} PointList[MaxVertex]; /*邻接表类型*/

/*图结点的数据结构*/ 
typedef struct GraphNode *PtrlGraphNode;
struct GraphNode {
	int numv; /*顶点数*/
	int nume; /*边数*/
	PointList PL; /*邻接表*/
};
typedef PtrlGraphNode  ListGraphNode; /*邻接表方式存储图*/

2629行,顶点的表头就是一个结构体指针数组。指针类型是第1822行的邻接点的数据结构。

/*创建一个有Vertexnum个顶点但没有边的图*/
ListGraphNode CreatGraph(int Vertexnum) 
{
	Vertex i;
	ListGraphNode MyGraph;
	
	MyGraph=(ListGraphNode)malloc(sizeof(struct GraphNode));
	MyGraph->nume=0;
	MyGraph->numv=Vertexnum;
	
	for (i=0;i<MyGraph->numv;i++) {
		MyGraph->PL[i].HeadEdge=NULL; //初始化数组里各个头结点 
	}
	return MyGraph;
}
/*插入边*/
void InsertEdge(ListGraphNode MyGraph, Edge E) 
{
	PtrlPointNode Node; /*邻接点*/
	/*插入边<V1, V2>*/
	/*为 V2建立新的邻接点*/ 
	Node=(PtrlPointNode)malloc(sizeof(struct PointNode));
	Node->index=E->V2; /*将V2插入V1的表头*/
	Node->weight=E->weight;
	Node->Next=MyGraph->PL[E->V1].HeadEdge;
	MyGraph->PL[E->V1].HeadEdge=Node;
	
	/*如果是无向图,还要插入边<V2, V1>*/
	Node=(PtrlPointNode)malloc(sizeof(struct PointNode));
	Node->index=E->V1; /*将V2插入V1的表头*/
	Node->weight=E->weight;
	Node->Next=MyGraph->PL[E->V2].HeadEdge;
	MyGraph->PL[E->V2].HeadEdge=Node;
}
/*创建图*/
ListGraphNode BuildGraph()
{
	ListGraphNode MyGraph;
	Edge E;
	Vertex i,j,numv;
	
	printf("请输入要读入的顶点个数:");
	scanf("%d", &numv);
	MyGraph=CreatGraph(numv);
	
	printf("请输入要读入的边数:");
	scanf("%d", &MyGraph->nume);
	if (MyGraph->nume!=0) {
		E=(Edge)malloc(sizeof(struct EdgeNode)); /*建立边结点*/
		/*读入边:格式为"起点 终点 权重"*/
		for (i=0;i<MyGraph->numv;i++) {
			scanf("%d %d %d", &E->V1, &E->V2, &E->weight);
			/*插入边到图里*/
			InsertEdge(MyGraph, E);
		}
	}
	
	/*如果顶点有数据的话,读入数据*/
	for (i=0;i<MyGraph->numv;i++) {
		scanf("%c", &MyGraph->PL[i].Data);
	}
	return MyGraph;
}

但是对于邻接表表示法来说,一条边必定是被存了两次的,例如顶点对< 5, 9 >,在指针数组5的链表里必定有9,在指针数组9的链表里也必定有5。所以用邻接表的话,表示的图要是稀疏图才算省空间,越稀疏越好。而对于完全图来说,用邻接矩阵的方式更方便后面的操作。

猜你喜欢

转载自blog.csdn.net/justinzengtm/article/details/80805254