图的建立——图的两种存储结构

【前言】

图状结构是一种比树形结构更复杂的非线性结构。

在树状结构中,结点间具有分支层次关系,每一层的结点可以和下一层的多个结点相关,但只能和上一层中的一个结点相关。

而在图状结构中,任意两个结点之间都可能相关,即结点之间的邻接关系可以是任意的。

一、邻接矩阵

图和树一样,没有顺序映像的存储结构,但可以借助数组表示元素之间的关系。

邻接矩阵是用于描述图中顶点之间关系(即弧或边的权)的矩阵。

若图G中有n个顶点,则邻接矩阵是一个n×n的方阵,定义为:

若图G是一个有n个顶点的带权图(即网),有n个顶点,则邻接矩阵可定义为:

图的邻接矩阵存储方式是用两个数组来表示图:

  • 一个一维数组vex:存储图中顶点信息;
  • 一个二维数组arc:存储图中顶点之间关系(即弧或边)的信息。
//邻接矩阵表示法 
//有向网 
#include<iostream>
#include<cstdio>
using namespace std;
#define INFINITY 32767		//最大值,假定为无穷大 
const int maxn = 10;

typedef int VertexType;		//顶点类型
typedef int VRType;			//顶点关系类型,对于无权图,用0或1表示相邻否;对于带权图,则为相应权值
 
struct Graph{				//邻接矩阵表示的图结构 
	VertexType vex[maxn];	//存储顶点 
	int arc[maxn][maxn];	//邻接矩阵 
	int vexnum,arcnum;		//图的当前顶点数和弧数 
};

int locateVex(Graph g,VertexType v)	//若图中存在v,则返回v在图中的位置信息 
{
	for(int i=0;i<g.vexnum;i++){
		if(v == g.vex[i]){
			return i;
		}
	}
	return -1;				//图中无该顶点 
}

void createGraph(Graph &g)  //构建有向网g 
{
	cout<<"请输入顶点数和边数:";
	cin>>g.vexnum>>g.arcnum;
	
	//构造顶点向量 
	cout<<"请依次输入各顶点:\n"; 
	for(int i=0;i<g.vexnum;i++){
		scanf("%d",&g.vex[i]);
	}
	
	//初始化邻接矩阵 
	for(int i=0;i<g.vexnum;i++){
		for(int j=0;j<g.vexnum;j++){
			g.arc[i][j] = INFINITY;
		}
	}
	
	//构造邻接矩阵 
	VertexType u,v;		//分别是一条弧的弧尾(起点)和弧头(终点) 
	VRType w;			//对于无权图,用0或1表示相邻否;对于带权图,则为相应权值
	printf("每一行输入一条弧依附的顶点(先弧尾,再弧头)和权值(如:u v w):\n");
	for(int i=0;i<g.arcnum;i++){
		cin>>u>>v>>w;
		int v1_index = locateVex(g,u);
		int v2_index = locateVex(g,v);
		g.arc[v1_index][v2_index] = w;  
	}	
}

void print(Graph g)
{
	cout<<"打印有向网g的邻接矩阵:\n";
	for(int i=0;i<g.vexnum;i++){
		for(int j=0;j<g.vexnum;j++){
			if(g.arc[i][j] != INFINITY)
				printf("%5d",g.arc[i][j]);
			else{
				printf("   -1");	//表示两点之间不直接相连 
			}
		}
		printf("\n");
	}
	printf("\n");
}

int main()
{
	Graph g;
	createGraph(g);
	print(g);
	return 0;
}

若现在有一个有向网及它的邻接矩阵如下图所示:

那么,执行上面程序,我们得到:

 

二、邻接表

对于图来说,邻接矩阵是不错的一种图存储结构,但是我们也发现,对于边数相对顶点较少的图,这种结构是存在对存储空间的极大浪费的。

因此我们考虑另外一种存储结构方式:邻接表,即数组与链表相结合的存储方法。

图的邻接表存储方式是用一个数组和一个单链表来表示图:

  • 图中顶点用一个一维数组存储,另外,对于顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息。
  • 图中每个顶点vi所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储。无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。

例如,下图就是一个无向图的邻接表的结构。

 

从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点(即此顶点的第一个邻接点)。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。

若是有向图,邻接表的结构是类似的,如下图。

以顶点作为弧尾来存储边表容易得到每个顶点的出度,而以顶点为弧头的表容易得到顶点的入度,即逆邻接表。

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

以无向网为例,可有如下邻接表:

类似树的孩子链表。即对图中的每个顶点vi建立一个单链表,表中结点表示依附于该顶点vi的边或弧。

顶点结点(弧链表表头结点), 弧结点

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
#define INFINITY 32767		//最大值,假定为无穷大 
const int maxn = 10;		//最大顶点数

typedef int VertexType;		//顶点类型
typedef int VRType;			//边上的权值类型,对于带权图或网,则为相应权值 

typedef struct ArcNode{			//边表结点,亦指弧节点信息 
	int adjvex;					//邻接点域,存储该顶点对应的下标,亦指该弧所指向的顶点的在图中位置 
	VRType w;					//用于存储权值,对于非网图可以不需要
	struct ArcNode *nextarc;	//链域,指向下一个邻接点;指向下一条弧的指针 
}ArcNode; 
typedef struct VNode{			//顶点表结点,亦指顶点节点信息 
	VertexType data;			//顶点域,存储顶点信息
	ArcNode *firstarc;			//边表头指针,指向第一条依附该顶点的弧的指针 
}VNode;
//VNode AdjVexList[maxn]; 
struct Graph{			//邻接表表示的图
	VNode vex[maxn];		//顶点向量 
	int vexnum,arcnum;	//图中当前顶点数和边数
};
 
int locateVex(Graph g,VertexType v)	//若图中存在v,则返回v在图中的位置信息 
{
	for(int i=0;i<g.vexnum;i++){
		if(v == g.vex[i].data){
			return i;
		}
	}
	return -1;				//图中无该顶点 
}

void createGraph(Graph &g) 						//构建无向网g 
{
	cout<<"请输入顶点数和边数(空格分隔):";
	cin>>g.vexnum>>g.arcnum;
	
	//构造顶点向量,并初始化
	cout<<"请依次输入各顶点:\n"; 
	for(int i=0;i<g.vexnum;i++){
		scanf("%d",&g.vex[i].data);
		g.vex[i].firstarc = NULL;				//将边表置为空表,初始化为空指针 
	}
	
	//构造邻接表,亦指建立边表 
	VertexType u,v;			//分别是一条弧的弧尾和弧头(起点和终点)
	VRType w;				//对于无权图或网,用0或1表示相邻否;对于带权图或网,则为相应权值
	printf("每一行输入一条弧依附的顶点(先弧尾,再弧头)和权值(如:u v w):\n");
	for(int i=0;i<g.arcnum;i++){
		cin>>u>>v>>w;
		int v1_index = locateVex(g,u);	//弧起点
		int v2_index = locateVex(g,v);	//弧终点
		
		//采用“头插法”在各个顶点的弧链头部插入弧结点 
		ArcNode *p1 = (ArcNode *)malloc(sizeof(ArcNode));	//构造一个弧结点,作为弧vivj的弧头(终点)
		p1->adjvex = v2_index;		//邻接序号为v2_index
		p1->w = w;
		/* 将p1的指针指向当前顶点上指向的结点 */
		p1->nextarc = g.vex[v1_index].firstarc;
		g.vex[v1_index].firstarc = p1; 						//将当前顶点的指针指向p1
		ArcNode *p2 = (ArcNode *)malloc(sizeof(ArcNode));	//构造一个弧结点,作为弧vivj的弧尾(起点) 
		p2->adjvex = v1_index;
		p2->w = w;
		p2->nextarc = g.vex[v2_index].firstarc;
		g.vex[v2_index].firstarc = p2;
		
	}	
}

//打印邻接表 
void print(Graph g)
{
	cout<<"\n";
	for(int i=0;i<g.vexnum;i++){
		printf("依赖顶点%d的弧为:",g.vex[i].data);
		ArcNode *p = g.vex[i].firstarc;
		while(p){
			printf("%d---%d(weight:%d) ",g.vex[i].data,g.vex[p->adjvex].data,p->w);
			p = p->nextarc;
		}
		printf("\n");
	}
	printf("\n");
}

int main()
{
	Graph g;
	createGraph(g);
	print(g);
	return 0;
	
}

若现有一个无向网及其邻接表如下图所示,

执行上面程序,有:

 

三、其他

十字链表点击

邻接多重表及十字链表点击

 

posted @ 2017-07-31 10:21  GGBeng  阅读(2568)  评论(0编辑  收藏  举报