数据结构(16)--图的存储及实现

参考书籍:数据结构(C语言版)严蔚敏吴伟民编著清华大学出版社    

本文中的代码可从这里下载:https://github.com/qingyujean/data-structure

    图状结构是一种比树形结构更复杂的非线性结构。在树状结构中,结点间具有分支层次关系,每一层上的结点只能和上一层中的至多一个结点相关,但可能和下一层的多个结点相关。而在图状结构中,任意两个结点之间都可能相关,即结点之间的邻接关系可以是任意的。

1.邻接矩阵

1.1结构定义

    图和树一样,没有顺序映像的存储结构,但可以借助数组的数据类型表示元素之间的关系。所以邻接矩阵是用于描述图中顶点之间关系(即弧或边的权)的矩阵。   假设图中顶点数为n,则邻接矩阵An×n:

                1    若Vi和Vj之间有弧或边
    A[i][j]=
                0    反之
    
    网的邻接矩阵An×n:
                w (权值)          若Vi和Vj之间有弧或边
    A[i][j]=
                无穷大                   反之

注意:
1) 图中无邻接到自身的弧,因此邻接矩阵主对角线为全零。
2) 无向图的邻接矩阵必然是对称矩阵。
3) 无向图中,顶点的度是邻接矩阵对应行(或列)的非零元素之和;有向图中,对应行的非零元素之和是该顶点的出度;对应列的非零元素之和是该顶点的入度;则该顶点的度是对应行和对应列的非零元素之和。

1.2示例

有向网示例:

1.3代码实现

 

#include<stdio.h>
//#include<stdlib.h>
/*
图的表示方法
DG(有向图)或者DN(有向网):邻接矩阵、邻接表(逆邻接表--为求入度)、十字链表
UDG(无向图)或者UDN(无向网):邻接矩阵、邻接表、邻接多重表
*/

//1.数组表示法(邻接矩阵):将以有向网为例
#define INFINITY 32767//最大值:假定为无穷大
#define MAX_VERTEX_NUM 10//最大顶点数目
//typedef enum GraphKind {DG, DN, UDG, UDN};  //有向图:0,有向网:1,无向图:2,无向网:3

typedef int VRType;//顶点关系类型,对于无权图或网,用0或1表示相邻否;对于带权图或网,则为相应权值
typedef int VertexType;//顶点类型
typedef VRType AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct{
	VertexType vexs[MAX_VERTEX_NUM];//顶点向量
	AdjMatrix arcs;//邻接矩阵
	int vexnum, arcnum;//图的当前顶点数和弧数
	//GraphKind kind;//图的种类标志
}MGraph;//邻接矩阵表示的图
//若图G中存在顶点v,则返回v在图中的位置信息,否则返回其他信息
int locateVex(MGraph G, VertexType v){
	for(int i = 0; i < G.vexnum; i++){
		if(G.vexs[i] == v)
			return i;
	}
	return -1;//图中没有该顶点
}
//采用邻接矩阵表示法构造有向网G
void createDN(MGraph &G){
	printf("输入顶点数和弧数如:(5,3):");
	scanf("%d,%d", &G.vexnum, &G.arcnum);

	//构造顶点向量
	printf("输入%d个顶点(以空格隔开如:v1 v2 v3):", G.vexnum);
	getchar();//吃掉换行符
	for(int m = 0; m < G.vexnum; m++){
		scanf("v%d", &G.vexs[m]);
		getchar();//吃掉空格符
	}

	
	//初始化邻接矩阵
	int i=0, j=0;
	for(i = 0; i < G.vexnum; i++){
		for(j = 0; j < G.vexnum; j++)
			G.arcs[i][j] = INFINITY;
	}

	//构造邻接矩阵
	VertexType v1, v2;//分别是一条弧的弧尾和弧头(起点和终点)
	VRType w;//对于无权图或网,用0或1表示相邻否;对于带权图或网,则为相应权值	
	printf("\n每行输入一条弧依附的顶点(先弧尾后弧头)和权值(如:v1 v2 3):\n");
	fflush(stdin);//清除残余后,后面再读入时不会出错
	for(int k = 0; k < G.arcnum; k++){
		scanf("v%d v%d %d",&v1, &v2, &w);
		fflush(stdin);//清除残余后,后面再读入时不会出错
		i = locateVex(G, v1);
		j = locateVex(G, v2);
		G.arcs[i][j] = w;
	}
}
//打印邻接矩阵
void printDN(MGraph G){
	printf("\n打印有向网G的邻接矩阵:\n");
	for(int i = 0; i < G.vexnum; i++){
		for(int j = 0; j < G.vexnum; j++)
		{
			if(G.arcs[i][j] != INFINITY)
				printf("%9d ", G.arcs[i][j]);
			else
				printf("INFINITY  ");
		}
		printf("\n");
	}
	printf("\n");
}

 

1.4演示

 

 

/*测试:
6,10
v1 v2 v3 v4 v5 v6

v1,v2,5
v1,v4,7

v2,v3,4

v3,v1,8
v3,v6,9

v4,v3,5
v4,v6,6

v5,v4,5

v6,v1,3
v6,v5,1
*/
void main(){
	MGraph G;
	createDN(G);	
	printDN(G);
}

 

 

 

 

 

2.邻接表

 

2.1结构定义

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

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

                                                       

注意:
在无向图的邻接表中,
1)第i个链表中结点数目为顶点i的度;
2)所有链表中结点数目的一半为图中边数;
3)占用的存储单元数目为n+2e。
在有向图的邻接表中,
1)第i个链表中结点数目为顶点i的出度;
2)所有链表中结点数目为图中弧数;
3)占用的存储单元数目为n+e。

为求出每一个顶点的入度,必须另外建立有向图的逆邻接表。有向图的逆邻接表与邻接表类似,只是它是从入度考虑结点,而不是从出度考虑结点。

2.2示例

无向网示例:

          

2.3代码实现

 

#include<stdio.h>
#include<stdlib.h>
/*
图的表示方法
DG(有向图)或者DN(有向网):邻接矩阵、邻接表(逆邻接表--为求入度)、十字链表
UDG(无向图)或者UDN(无向网):邻接矩阵、邻接表、邻接多重表
*/
#define MAX_VERTEX_NUM 10//最大顶点数目
#define NULL 0
typedef int VRType;//对于带权图或网,则为相应权值
typedef int VertexType;//顶点类型
//typedef enum GraphKind {DG, DN, UDG, UDN};  //有向图:0,有向网:1,无向图:2,无向

typedef struct ArcNode{	
	int adjvex;//该弧所指向的顶点的在图中位置
	VRType w;//弧的相应权值
	struct ArcNode *nextarc;//指向下一条弧的指针
}ArcNode;//弧结点信息

typedef struct VNode{
	VertexType data;//顶点信息
	ArcNode *firstarc;//指向第一条依附该顶点的弧的指针
}VNode, AdjVexList[MAX_VERTEX_NUM];//顶点结点信息

typedef struct{
	AdjVexList vexs;//顶点向量
	int vexnum, arcnum;//图的当前顶点数和弧数
	//GraphKind kind;//图的种类标志
}ALGraph;//邻接表表示的图
//若图G中存在顶点v,则返回v在图中的位置信息,否则返回其他信息
int locateVex(ALGraph G, VertexType v){
	for(int i = 0; i < G.vexnum; i++){
		if(G.vexs[i].data == v)
			return i;
	}
	return -1;//图中没有该顶点
}
//采用邻接表表示法构造无向网G
void createUDN(ALGraph &G){
	printf("输入顶点数和弧数如:(5,3):");
	scanf("%d,%d", &G.vexnum, &G.arcnum);

	//构造顶点向量,并初始化
	printf("输入%d个顶点(以空格隔开如:v1 v2 v3):", G.vexnum);
	getchar();//吃掉换行符
	for(int m = 0; m < G.vexnum; m++){
		scanf("v%d", &G.vexs[m].data);
		G.vexs[m].firstarc = NULL;//初始化为空指针////////////////重要!!!
		getchar();//吃掉空格符
	}

	//构造邻接表
	VertexType v1, v2;//分别是一条弧的弧尾和弧头(起点和终点)
	VRType w;//对于无权图或网,用0或1表示相邻否;对于带权图或网,则为相应权值	
	printf("\n每行输入一条弧依附的顶点(先弧尾后弧头)和权值(如:v1 v2 3):\n");
	fflush(stdin);//清除残余后,后面再读入时不会出错
	int i = 0, j = 0;
	for(int k = 0; k < G.arcnum; k++){
		scanf("v%d v%d %d",&v1, &v2, &w);
		fflush(stdin);//清除残余后,后面再读入时不会出错
		i = locateVex(G, v1);//弧起点
		j = locateVex(G, v2);//弧终点
		
		//采用“头插法”在各个顶点的弧链头部插入弧结点
		ArcNode *p1 = (ArcNode *)malloc(sizeof(ArcNode));//构造一个弧结点,作为弧vivj的弧头(终点)
		p1->adjvex = j;
		p1->w = w;
		p1->nextarc = G.vexs[i].firstarc;
		G.vexs[i].firstarc = p1;
		ArcNode *p2 = (ArcNode *)malloc(sizeof(ArcNode));//构造一个弧结点,作为弧vivj的弧尾(起点)
		p2->adjvex = i;
		p2->w = w;
		p2->nextarc = G.vexs[j].firstarc;
		G.vexs[j].firstarc = p2;
	}
}
//打印邻接表
void printAdjList(ALGraph G){
	printf("\n");
	for(int i = 0; i < G.vexnum; i++){
		printf("依附顶点v%d的弧为:", G.vexs[i].data);
		ArcNode *p = G.vexs[i].firstarc;
		while(p){
			printf("v%dv%d(weight:%d) ", G.vexs[i].data, G.vexs[p->adjvex].data, p->w);
			p = p->nextarc;
		}
		printf("\n");
	}
	printf("\n");
}

 

2.4演示

 

 

/*测试:
4,4
v1 v2 v3 v4

v1 v2 3
v1 v3 6
v1 v4 4
v2 v4 9
*/
void main(){
	ALGraph G;
	createUDN(G);
	printAdjList(G);
}

 

 

 

 

 

3.邻接多重表

 

3.1结构定义

 

    在无向图的邻接表中,每条边(Vi,Vj)由两个结点表示,一个结点在第 i 个链表中,另一个结点在第 j 个链表中,当需要对边进行操作时,就需找到表示同一条边的两个结点,这给操作带来不便,在这种情况下采用邻接多重表较方便。
    邻接多重表中结点分为:边结点和顶点结点 :         

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

                         

3.2示例

无向图示例:

3.3代码实现

 

#include<stdio.h>
#include<stdlib.h>
/*
图的表示方法
DG(有向图)或者DN(有向网):邻接矩阵、邻接表(逆邻接表--为求入度)、十字链表
UDG(无向图)或者UDN(无向网):邻接矩阵、邻接表、邻接多重表
*/
#define MAX_VERTEX_NUM 10//最大顶点数目
#define NULL 0
//typedef int VRType;//对于带权图或网,则为相应权值
typedef int VertexType;//顶点类型
//typedef enum GraphKind {DG, DN, UDG, UDN};  //有向图:0,有向网:1,无向图:2,无向

typedef struct ArcNode{	
	int ivex, jvex;//该边所依附的2个顶点的在图中位置
	struct ArcNode *ivexNextarc, *jvexNextarc;//分别指向该边所依附的两个顶点下一条边
}ArcNode;//弧结点信息

typedef struct VNode{
	VertexType data;//顶点信息
	ArcNode *firstedge;//指向第一条依附该顶点的弧的指针
}VNode, AdjMuitiVexList[MAX_VERTEX_NUM];//顶点结点信息

typedef struct{
	AdjMuitiVexList vexs;//顶点向量
	int vexnum, arcnum;//图的当前顶点数和弧数
	//GraphKind kind;//图的种类标志
}AMLGraph;//邻接表表示的图
//若图G中存在顶点v,则返回v在图中的位置信息,否则返回其他信息
int locateVex(AMLGraph G, VertexType v){
	for(int i = 0; i < G.vexnum; i++){
		if(G.vexs[i].data == v)
			return i;
	}
	return -1;//图中没有该顶点
}
//采用邻接多重表表示法构造无向图G
void createUDG(AMLGraph &G){
	printf("输入顶点数和弧数如:(5,3):");
	scanf("%d,%d", &G.vexnum, &G.arcnum);

	//构造顶点向量,并初始化
	printf("输入%d个顶点(以空格隔开如:v1 v2 v3):", G.vexnum);
	getchar();//吃掉换行符
	for(int m = 0; m < G.vexnum; m++){
		scanf("v%d", &G.vexs[m].data);
		G.vexs[m].firstedge = NULL;//初始化为空指针////////////////重要!!!
		getchar();//吃掉空格符
	}

	//构造邻接多重表
	VertexType v1, v2;//分别是一条的两个顶点
	printf("\n每行输入一条边依附的顶点(如:v1v2):\n");
	fflush(stdin);//清除残余后,后面再读入时不会出错
	int i = 0, j = 0;
	for(int k = 0; k < G.arcnum; k++){
		scanf("v%dv%d",&v1, &v2);
		fflush(stdin);//清除残余后,后面再读入时不会出错
		i = locateVex(G, v1);//边依附的两个顶点的在图中的位置
		j = locateVex(G, v2);
		
		//采用“头插法”在各个顶点的边链头部插入边结点
		ArcNode *p = (ArcNode *)malloc(sizeof(ArcNode));//构造一个边结点,它依附于vivj两个顶点
		p->ivex = i;
		p->ivexNextarc = G.vexs[i].firstedge;
		G.vexs[i].firstedge = p;
		p->jvex = j;
		p->jvexNextarc = G.vexs[j].firstedge;
		G.vexs[j].firstedge = p;
	}
}
//打印邻接多重表
void printAdjMultiList(AMLGraph G){
	printf("\n");
	for(int i = 0; i < G.vexnum; i++){
		printf("依附顶点v%d的边为:", G.vexs[i].data);
		ArcNode *p = G.vexs[i].firstedge;

		while(p){
			if(p->ivex == i){
				printf("v%dv%d ", G.vexs[i].data, G.vexs[p->jvex].data);
				p = p->ivexNextarc;
			}else if(p->jvex == i){
				printf("v%dv%d ", G.vexs[i].data, G.vexs[p->ivex].data);
				p = p->jvexNextarc;
			}
		}
		printf("\n");
	}
	printf("\n");
}

 

 

 

3.4演示

 

 

/*
测试:
4,3
v1 v2 v3 v4

  v1v2
  v1v3
  v2v4
*/
void main(){
	AMLGraph G;
	createUDG(G);
	printAdjMultiList(G);
}

 

 

 

 

 

4.十字链表

4.1结构定义

十字链表是有向图的另一种链式存储结构。可以理解成有向图的邻接表和逆邻接表的结合,在十字链表中,有两种结点结构:         

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

          

 

4.2示例

有向图示例:

4.3代码实现

 

#include<stdio.h>
#include<stdlib.h>
/*
图的表示方法
DG(有向图)或者DN(有向网):邻接矩阵、邻接表(逆邻接表--为求入度)、十字链表
UDG(无向图)或者UDN(无向网):邻接矩阵、邻接表、邻接多重表
*/
#define MAX_VERTEX_NUM 10//最大顶点数目
#define NULL 0
//typedef int VRType;//对于带权图或网,则为相应权值
typedef int VertexType;//顶点类型
//typedef enum GraphKind {DG, DN, UDG, UDN};  //有向图:0,有向网:1,无向图:2,无向

typedef struct ArcNode{	
	int tailvex, headvex;//该弧的弧尾(起点)和弧头(终点)所指向的顶点的在图中位置
	struct ArcNode *tailNextarc, *headNextArc;//分别为指向弧尾(起点)相同的弧的下一条弧的指针、弧头(终点)相同的弧的下一条弧的指针
}ArcNode;//弧结点信息

typedef struct VNode{
	VertexType data;//顶点信息
	ArcNode *firstIn;//指向第一条以该顶点为弧尾(起点)的指针
	ArcNode *firstOut;//指向第一条以该顶点的弧头(终点)的指针
}VNode, VexList[MAX_VERTEX_NUM];//顶点结点信息

typedef struct{
	VexList vexs;//顶点向量
	int vexnum, arcnum;//图的当前顶点数和弧数
	//GraphKind kind;//图的种类标志
}OLGraph;//邻接表表示的图
//若图G中存在顶点v,则返回v在图中的位置信息,否则返回其他信息
int locateVex(OLGraph G, VertexType v){
	for(int i = 0; i < G.vexnum; i++){
		if(G.vexs[i].data == v)
			return i;
	}
	return -1;//图中没有该顶点
}
//采用十字链表表示法构造有向图G
void createDG(OLGraph &G){
	printf("输入顶点数和弧数如:(5,3):");
	scanf("%d,%d", &G.vexnum, &G.arcnum);

	//构造顶点向量,并初始化
	printf("输入%d个顶点(以空格隔开如:v1 v2 v3):", G.vexnum);
	getchar();//吃掉换行符
	for(int m = 0; m < G.vexnum; m++){
		scanf("v%d", &G.vexs[m].data);
		G.vexs[m].firstIn = NULL;//初始化为空指针////////////////重要!!!
		G.vexs[m].firstOut = NULL;
		getchar();//吃掉空格符
	}

	//构造十字链表
	VertexType v1, v2;//分别是一条弧的弧尾和弧头(起点和终点)
	printf("\n每行输入一条弧依附的顶点(先弧尾后弧头)(如:v1v2):\n");
	fflush(stdin);//清除残余后,后面再读入时不会出错
	int i = 0, j = 0;
	for(int k = 0; k < G.arcnum; k++){
		scanf("v%dv%d",&v1, &v2);
		fflush(stdin);//清除残余后,后面再读入时不会出错
		i = locateVex(G, v1);//弧起点
		j = locateVex(G, v2);//弧终点
		
		//采用“头插法”在各个顶点的弧链头部插入弧结点
		ArcNode *p = (ArcNode *)malloc(sizeof(ArcNode));//构造一个弧结点,作为弧vivj的弧头(终点)
		p->tailvex = i;
		p->tailNextarc = G.vexs[i].firstOut;
		G.vexs[i].firstOut = p;
		p->headvex = j;
		p->headNextArc = G.vexs[j].firstIn;
		G.vexs[j].firstIn = p;
	}
}
//打印十字链表
void printOrthogonalList(OLGraph G){
	printf("\n");
	for(int i = 0; i < G.vexnum; i++){
		printf("以顶点v%d为弧尾的弧有为:", G.vexs[i].data);
		ArcNode *p = G.vexs[i].firstOut;
		while(p){
			printf("v%dv%d ", G.vexs[i].data, G.vexs[p->headvex].data);
			p = p->tailNextarc;
		}
		printf("\n");
	}
	printf("\n");
}

 

4.4演示

 

 

/*测试:
4,7
v1 v2 v3 v4

v1v2
v1v3

v3v1
v3v4

v4v1
v4v2
v4v3
*/
void main(){
	OLGraph G;
	createDG(G);
	printOrthogonalList(G);
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章