图的邻接矩阵存储及操作

第1关:图的邻接矩阵存储及求邻接点操作

任务描述
本关任务:要求从文件输入顶点和边数据,包括顶点信息、边、权值等,编写程序实现以下功能。
1)构造无向网G的邻接矩阵和顶点集,即图的存储结构为邻接矩阵。
2)输出无向网G的各顶点和邻接矩阵。
3)输出无向网G中顶点H的所有邻接顶点。

相关知识
图是表达“多对多”的关系的一种数据结构,由非空的有限顶点集合和有限边集合组成。将图的类型定义为枚举类型,在枚举值表中罗列:

enum GraphKind{DG,DN,UDG,UDN};   // 有向图,有向网,无向图,无向网

顶点集合
每个顶点数据类型为VertexType,一个图的顶点集合用数组表示,数组下标表示顶点位置。数组内容包含顶点的信息,图的顶点集合的数据类型定义如下:

#define    MAX_VERTEX_NUM    20        // 最大顶点个数
typedef char VertexType[16];           // 顶点类型
VertexType  vexs[MAX_VERTEX_NUM];      // 顶点向量
将顶点数据类型定义为长度为16的字符数组类型,可以将表示的城市名称存储在计算机中。

边集合
邻接矩阵是表示顶点之间相邻关系的矩阵。设G=(V,E)是具有n顶点的图,则G的邻接矩阵是具有如下性质的n阶方阵:

用邻接矩阵来表示一个具有n个顶点的图时,用邻接矩阵中的n×n个元素存储顶点间相邻关系,对于无权图,当邻接矩阵中的元素仅表示相应的边是否存在时,VRType可定义为值为0和1的枚举类型,0表示两个顶点之间没有边,即没有关系。对于带权图,则为权值,如果两个顶点之间没有边,则用一个很大很大的数代替∞。将顶点关系类型VRType定义为整型:

typedef int VRType;            //  顶点关系边的数据类型

顶点关系边的数据类型类型,对于无权图,用1或0表示相邻否;对于带权图,则为权值。

图的邻接矩阵表示法是图的一种顺序存储结构,优点是可以快速判断两个顶点之间是否存在边,可以快速添加边或者删除边,可以快速计算顶点度数、求邻接点的操作等;而其缺点是如果顶点之间的边比较少,会比较浪费空间。

邻接矩阵具有如下性质:
(1)图中各项点的序号确定后,图的邻接矩阵是唯一确定的;
(2)无向图和无向网的邻接矩阵是一个对称矩阵;
(3)无向图邻接矩阵中第i行(或第i列)的非0元素的个数即为第i个顶点的度;
(4)有向图邻接矩阵中第i行非0元素的个数为第i个顶点的出度,第i列非0元素的个数为第i个顶点的入度,第i个顶点的度为第i行与第j非0元素个数之和;
(5)无向图的边数等于邻接矩阵中非0元素个数之和的一半,有向图的弧数等于邻接矩阵中非0元素个数之和。

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

typedef int VRType;    // 顶点关系类型 
typedef char VertexType[20]; // 顶点类型 
// 图的数组(邻接矩阵)存储表示 
#define INFINITY INT_MAX // 用整型最大值代替∞ 
#define MAX_VERTEX_NUM 20 // 最大顶点个数 
typedef enum{DG,DN,UDG,UDN}GraphKind; // {有向图,有向网,无向图,无向网} 

typedef struct
{
	VRType adj; // 顶点关系类型。对无权图,用1(是)或0(否)表示相邻否;对带权图,则为权值 
}ArcCell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 二维数组 

typedef struct      // 图的数组(邻接矩阵)存储 
{
	VertexType vexs[MAX_VERTEX_NUM]; // 顶点向量 
	AdjMatrix arcs; // 邻接矩阵 
	int vexnum,arcnum; // 图的当前顶点数和弧数 
	GraphKind kind; // 图的种类标志 
}MGraph;  

void visit(VertexType i);

void CreateGraphF(MGraph &G);// 采用数组(邻接矩阵)表示法,由文件构造无向网G
void Display(MGraph G);    // 输出邻接矩阵存储表示的图G
int LocateVex(MGraph G,VertexType u);//若G中存在顶点u,则返回该顶点在图中位置;否则返回-1 
VertexType* GetVex(MGraph G,int v);    // v是G中某个顶点的序号,返回v的值
int FirstAdjVex(MGraph G,VertexType v);//v是图G中某个顶点,返回v的第一个邻接顶点的序号。若顶点在G中没有邻接顶点,则返回-1 
int NextAdjVex(MGraph G,VertexType v,VertexType w);//v是G中某个顶点,w是v的邻接顶点,返回v的(相对于w的)下一个邻接顶点的序号,若w是v的最后一个邻接顶点,则返回-1 
void DestroyGraph(MGraph &G); // 销毁图G 




int main()
{
	MGraph g;
	VertexType v1,v2;
	CreateGraphF(g);      // 利用数据文件创建邻接矩阵表示的图	
	Display(g);           // 输出图  
	int i,j,k,n;
	//printf("请输入顶点的值: ");
	scanf("%s",v1);
	//printf("输出图G中顶点%s的所有邻接顶点: ",v1);
	k=FirstAdjVex(g,v1);
	while(k!=-1)
	{ 
		strcpy(v2, g.vexs[k] );
		visit(v2);
		k=NextAdjVex(g,v1,v2);
	}
	printf("\n");    
	return 0;
}

void visit(VertexType i)
{
	printf("%s ",i);
}


void CreateGraphF(MGraph &G)
{
	// 采用数组(邻接矩阵)表示法,由文件构造无向网G
	/********** Begin **********/
	int i,j,k,w;
	char filename[13];
	VertexType va,vb;
	FILE * graphlist;
	scanf("%d",&G.kind);
	scanf("%s",filename);
	graphlist=fopen(filename,"r");

	fscanf(graphlist,"%d",&G.vexnum);
	fscanf(graphlist,"%d",&G.arcnum);
	for(i=0;i<G.vexnum;i++)
		fscanf(graphlist,"%s",G.vexs[i]);
	for(i=0;i<G.vexnum;++i)
		for(j=0;j<G.vexnum;++j){
			if(G.kind%2)
				G.arcs[i][j].adj=INFINITY;
			else
				G.arcs[i][j].adj=0;
		}
	for(k=0;k<G.arcnum;++k){
		if(G.kind%2)
			fscanf(graphlist,"%s%s%d",va,vb,&w);
		else
			fscanf(graphlist,"%s%s",va,vb);
		i=LocateVex(G,va);
		j=LocateVex(G,vb);
		if(G.kind==0)
			G.arcs[i][j].adj=1;
		else if(G.kind==1)
			G.arcs[i][j].adj=w;
		else if(G.kind==2)
			G.arcs[i][j].adj=G.arcs[j][i].adj=1;
		else
			G.arcs[i][j].adj=G.arcs[j][i].adj=w;
	}
	fclose(graphlist);
	/********** End **********/
}


void Display(MGraph G)
{
	// 输出邻接矩阵存储表示的图G
	/********** Begin **********/
	int i,j;
	switch(G.kind){
		case DG:printf("有向图\n");break;
		case DN:printf("有向网\n");break;
		case UDG:printf("无向图\n");break;
		case UDN:printf("无向网\n");
	}
    printf("%d个顶点%d条边。顶点依次是: ",G.vexnum,G.arcnum);
	for(i=0;i<G.vexnum;++i)
		printf("%s ",G.vexs[i]);
	printf("\n图的邻接矩阵:\n");
	for(i=0;i<G.vexnum;i++){
		for(j=0;j<G.vexnum;j++)
			if(G.kind%2){
				if(G.arcs[i][j].adj==INFINITY)
					printf("%s\t","∞");
				else
					printf("%d\t",G.arcs[i][j].adj);
			}else
				printf("%d\t",G.arcs[i][j].adj);
		printf("\n");
	}
	/********** End **********/
}


int LocateVex(MGraph G,VertexType u)
{
	//初始条件:图G存在,u和G中顶点有相同特征
	// 操作结果:若G中存在顶点u,则返回该顶点在图中位置;否则返回-1 
	/********** Begin **********/
    int i;
	for(i=0;i<G.vexnum;++i)
		if(strcmp(u,G.vexs[i])==0) return i;
	return -1;  
	/********** Begin **********/
}

VertexType* GetVex(MGraph G,int v)
{ 
	// 初始条件:图G存在,v是G中某个顶点的序号。操作结果:返回v的值
	/********** Begin **********/
	if(v>=G.vexnum || v<0)
		exit(0);
	return &(G.vexs[v]); 
	/********** End **********/
}



int FirstAdjVex(MGraph G,VertexType v)
{
 	// 初始条件:图G存在,v是G中某个顶点 
	// 操作结果:返回v的第一个邻接顶点的序号。若顶点在G中没有邻接顶点,则返回-1 
	/********** Begin **********/
	int i,j,k;
	if(G.kind%2)
		j=INFINITY;
	else
		j=0;
	k=LocateVex(G,v);
	for(i=0;i<G.vexnum;i++)
		if(G.arcs[k][i].adj!=j)
			return i;
	return -1;
	/********** End **********/
}

int NextAdjVex(MGraph G,VertexType v,VertexType w)
{
	// 初始条件:图G存在,v是G中某个顶点,w是v的邻接顶点 
	// 操作结果:返回v的(相对于w的)下一个邻接顶点的序号,若w是v的最后一个邻接顶点,则返回-1 
	/********** Begin **********/
	int i,j,k1,k2;
	if(G.kind%2)
		j=INFINITY;
	else
		j=0;
	k1=LocateVex(G,v);

	k2=LocateVex(G,w);

	for(i=k2+1;i<G.vexnum;i++)
		if(G.arcs[k1][i].adj!=j)
			return i;
	return -1;
	/********** End **********/
}


void DestroyGraph(MGraph &G)
{ // 初始条件:图G存在。操作结果:销毁图G 
	/********** Begin **********/
	int i,j,k=0;
	if(G.kind%2) 
		k=INFINITY; 
	G.vexnum=0; 
	G.arcnum=0; 
	/********** End **********/
}

第2关:图的深度优先遍历

任务描述
本关任务:以邻接矩阵存储图,要求编写程序实现图的深度优先遍历。

相关知识
图的深度优先遍历类似于树的先序遍历, 是树的先序遍历的推广,其基本思想如下:

从某个顶点v出发,访问此顶点。
访问一个与v邻接的顶点u之后,再从u出发,访问与u邻接且未被访问的顶点w,依此类推。
当到达一个所有邻接顶点都被访问的顶点时,则又从最后被访问过的顶点开始,依次退回到最近被访问的尚有邻接顶点的末被访问过的顶点,从该顶点出发,重复步骤 2 和 3 ,直到所有被访问过的顶点的邻接顶点都被访问过为止。
在程序里完成遍历需要在函数体外定义全局访问标志数组,记录顶点是否被访问过,初始时,所有元素均为0,表示所有顶点未被访问过:

int  visited[MAX_VERTEX_NUM] = {0}; 

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

#include"MGraph.h"

void DFS(MGraph G,int v);// 从第v个顶点出发递归地深度优先遍历图G 
void DFSTraverse(MGraph G);// 图G存在,从第1个顶点起,深度优先遍历图G,并对每个顶点调用函数visit一次且仅一次 

int visited[MAX_VERTEX_NUM]; // 访问标志数组(全局量) 

int main()
{
   MGraph g;
	VertexType v1,v2;
	CreateGraphF(g); /* 利用数据文件创建无向图*/
	Display(g); /* 输出无向图*/  
	printf("深度优先遍历序列:\n"); 
	DFSTraverse(g);
	return 0;
}



void DFS(MGraph G,int v)
{
	// 从第v个顶点出发递归地深度优先遍历图G 
	/********** Begin **********/
	int w;
	visited[v]=1;
	visit(G.vexs[v]);
	for(w=FirstAdjVex(G,G.vexs[v]);w>=0;w=NextAdjVex(G,G.vexs[v],G.vexs[w]))
		if(!visited[w])
			DFS(G,w);
	/********** End **********/
}

void DFSTraverse(MGraph G)
{   //图G存在,从第1个顶点起,深度优先遍历图G,并对每个顶点调用函数visit一次且仅一次 
	/********** Begin **********/
	int v;
	for(v=0;v<G.vexnum;v++)
		visited[v]=0;
	for(v=0;v<G.vexnum;v++)
		if(!visited[v])
			DFS(G,v);
	printf("\n");
	/********** End **********/
}

第3关:图的广度优先遍历

任务描述
本关任务:以邻接矩阵存储图,要求编写程序实现图的广度优先遍历。

相关知识
广度优先遍历类似于树的按层次遍历的过程。

假设从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过和邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,直至图中所有已被访问的顶点的邻接点都被访问到。

若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

换句话说,广度优先遍历图的过程中以v为起始点,由近至远,依次访问和v有路径相通且路径长度为1,2,…的顶点。

本关卡提供顺序队列sqqueue的相关操作和功能,顺序队列数据类型定义及相关操作函数接口定义如下,在进行图的广度优先遍历的过程中,可以根据需要调用以下操作:

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

#include"MGraph.h"
#include"sqqueue.h" 


void BFSTraverse(MGraph G);// 图G存在,从第1个顶点起,按广度优先非递归遍历图G,并对每个顶点调用函数visit一次且仅一次 

int visited[MAX_VERTEX_NUM]; // 访问标志数组(全局量) 

int main()
{
    MGraph g;
	VertexType v1,v2;
	CreateGraphF(g);    // 利用数据文件创建图
	Display(g);         // 输出图  
	printf("广度优先遍历序列:\n"); 
	BFSTraverse(g);
	return 0;
}



void BFSTraverse(MGraph G)
{ 	// 图G存在,从第1个顶点起,按广度优先非递归遍历图G,并对每个顶点调用函数visit一次且仅一次 
	/********** Begin **********/
	int v,u,w;
	SqQueue Q;
	for(v=0;v<G.vexnum;v++)
		visited[v]=0;
	InitQueue(Q);
	for(v=0;v<G.vexnum;v++)
		if(!visited[v]){
			visited[v]=1;
			visit(G.vexs[v]);
			EnQueue(Q,v);
			while(!QueueEmpty(Q)){
				DeQueue(Q,u);
				for(w=FirstAdjVex(G,G.vexs[u]);w>=0;w=NextAdjVex(G,G.vexs[u],G.vexs[w]))
					if(!visited[w]){
						visited[w]=1;
						visit(G.vexs[w]);
						EnQueue(Q,w);
					}
			}
		}
		printf("\n");
	/********** End **********/
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小狗碎碎念

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值