数据结构基础:P6.5-图(一)--->小白专场:如何建立图-C语言实现

本系列文章为浙江大学陈越、何钦铭数据结构学习笔记,前面的系列文章链接如下
数据结构基础:P1-基本概念
数据结构基础:P2.1-线性结构—>线性表
数据结构基础:P2.2-线性结构—>堆栈
数据结构基础:P2.3-线性结构—>队列
数据结构基础:P2.4-线性结构—>应用实例:多项式加法运算
数据结构基础:P2.5-线性结构—>应用实例:多项式乘法与加法运算-C实现
数据结构基础:P3.1-树(一)—>树与树的表示
数据结构基础:P3.2-树(一)—>二叉树及存储结构
数据结构基础:P3.3-树(一)—>二叉树的遍历
数据结构基础:P3.4-树(一)—>小白专场:树的同构-C语言实现
数据结构基础:P4.1-树(二)—>二叉搜索树
数据结构基础:P4.2-树(二)—>二叉平衡树
数据结构基础:P4.3-树(二)—>小白专场:是否同一棵二叉搜索树-C实现
数据结构基础:P4.4-树(二)—>线性结构之习题选讲:逆转链表
数据结构基础:P5.1-树(三)—>堆
数据结构基础:P5.2-树(三)—>哈夫曼树与哈夫曼编码
数据结构基础:P5.3-树(三)—>集合及运算
数据结构基础:P5.4-树(三)—>入门专场:堆中的路径
数据结构基础:P5.5-树(三)—>入门专场:File Transfer
数据结构基础:P6.1-图(一)—>什么是图
数据结构基础:P6.2-图(一)—>图的遍历
数据结构基础:P6.3-图(一)—>应用实例:拯救007
数据结构基础:P6.4-图(一)—>应用实例:六度空间


一、邻接矩阵表示的图结点的结构

关于图的所有算法的实现都有一个前提,就是首先你得有一个图。我们前面讲到,图主要有两种表示方法,一种是邻接矩阵来表示图,一种是用邻接表来表示图。那我们先从简单的开始来考虑一下,用邻接矩阵表示的图在程序里面怎么写出来。

1、用邻接矩阵表示图非常简单,主要就是一个矩阵G[i][j],在程序里对应一个二维的数组。对于无权图,如果结点 V i {{\rm{V}}_{\rm{i}}} Vi V j \rm{V_j} Vj有一条边的话,那么对应第[i][j]这个元素就等于1,如果没有边的话就等于0。
在这里插入图片描述
2、这个数组的大小MaxVertaxNum由图里所有顶点的数量来决定的。这个二维数组的类型定义为WeightType,这是一个抽象的写法,大多数情况下他就是个整型的,这个根据具体的情况自己到前面去定义。

WeightType G[MaxVertexNum][MaxVertexNum];

3、还有两个很重要变量要定义,一个是它的顶点数,一个是它的边数。

int Nv; /* 顶点数 */
int Ne; /* 边数 */

4、有时候图中每个结点都有特定的意义,因此需要一个数组存放顶点数据。另外,在大型项目中都将图封装成一个结构。最后将MGraph定义成指向图的指针,因为函数传指针较方便。

typedef struct GNode *PtrToGNode;
struct GNode {
	WeightType G[MaxVertexNum][MaxVertexNum];
	int Nv; /* 顶点数 */
	int Ne; /* 边数 */
	DataType Data[MaxVertexNum]; /* 存顶点的数据 */
};
typedef PtrToGNode MGraph; /* 以邻接矩阵存储的图类型 */

二、邻接矩阵表示的图-初始化

建立一个图的过程实际上是由两步组成的,第一步就是初始化一个有多个顶点但是没有边的图。我们知道一个图里面可以一条边都没有,但是不能一个顶点都没有。那么在给定了顶点的个数以后我们就可以实现这样一个函数,叫CreateGraph,初始化一个有VertexNum个顶点但没有边的图。

typedef int Vertex; /* 用顶点下标表示顶点,为整型 */
MGraph CreateGraph( int VertexNum )
{ 
	//把这个 v 和 w 定义为是顶点类型
	Vertex V, W;
	MGraph Graph;
	Graph = (MGraph)malloc(sizeof(struct GNode));  //分配内存,创建一个图
	Graph->Nv = VertexNum;  //顶点数
	Graph->Ne = 0;    //边为0
	/* 注意:这里默认顶点编号从0开始,到(Graph->Nv - 1) */
	for (V=0; V<Graph->Nv; V++)  //初始化为0,代表两个点之间没关系
		for (W=0; W<Graph->Nv; W++) 
			Graph->G[V][W] = 0; /* 或INFINITY */
	return Graph; 
}

三、邻接矩阵表示的图-插入边

接下来要做的动作就是每次向这个图里面插入一条边。那么现在我们的问题又来了,就是首先你得有一条边。这个边的类型可以模仿刚才那个图结点的定义,里面包含了连接的两个顶点与权重。在插入边时,直接对相应位置的元素赋值即可。

typedef struct ENode *PtrToENode;
struct ENode {
	Vertex V1, V2; /* 有向边<V1, V2> */
	WeightType Weight; /* 权重 */
};
typedef PtrToENode Edge

void InsertEdge( MGraph Graph, Edge E )
{
	/* 插入边 <V1, V2> */
	Graph->G[E->V1][E->V2] = E->Weight; 
	/* 若是无向图,还要插入边<V2, V1> */
	Graph->G[E->V2][E->V1] = E->Weight;
}

四、邻接矩阵表示的图-建立图

在实现完了前面两个函数之后,我们就准备好完整地建立一个用邻接矩阵表示的图了。

MGraph BuildGraph()
{ 
	MGraph Graph; //声明一个图
	Edge E;       //声明一条边
	Vertex V;     //声明顶点数据
	int Nv, i;    //Nv为定点数,iwei控制循环地遍历
	scanf("%d", &Nv);
	Graph = CreateGraph(Nv);   //创建一个有Nv个结点的图
	scanf("%d", &(Graph->Ne)); //读入边数,如果不为0,则开始创建便
	if ( Graph->Ne != 0 ) {
		E = (Edge)malloc(sizeof(struct ENode)); 
		for (i=0; i<Graph->Ne; i++) {
			//读入一条边的信息
			scanf("%d %d %d", &E->V1, &E->V2, &E->Weight); 
			//插入边
			InsertEdge( Graph, E );
		}
	} 
	/* 如果顶点有数据的话,读入数据 */
	for (V=0; V<Graph->Nv; V++) 
		scanf(" %c", &(Graph->Data[V]));
	return Graph;
} 

看到这里我们便想:真的有必要这么麻烦吗?如果是在考试的状态下,你只有非常短的时间去建立一个图的话,其实只要这么一小段代码就可以了。

/*
直接把这个邻接矩阵以及顶点数和边数都声明成全局变量
*/
int G[MAXN][MAXN], Nv, Ne;
void BuildGraph()
{ 
	int i, j, v1, v2, w;
	scanf("%d", &Nv);
	/* CreateGraph */
	for (i=0; i<Nv; i++)
		for (j=0; j<Nv; j++)
			G[i][j] = 0; /* 或INFINITY */
	scanf("%d", &Ne);
	for (i=0; i<Ne; i++) {
		scanf("%d %d %d", &v1, &v2, &w);
	/* InsertEdge */
		G[v1][v2] = w;
		G[v2][v1] = w;
	}
} 

五、邻接表表示的图结点的结构

当我们在使用邻接表定义图的结点的时候,其实大部分内容都跟前面是一样的。就是它也得有顶点数、边数,然后把这个邻接表方式存储的图类型定义为指向这个结点的指针。在前面,结点中含有邻接矩阵,而这里就是邻接表。

typedef struct GNode *PtrToGNode;
struct GNode { 
	int Nv; /* 顶点数 */
	int Ne; /* 边数 */
	AdjList G; /* 邻接表 */
};

这个邻接表其实是个数组,数字大小就是我们的顶点数。数组中每一个元素都是一个结点,我们取个名字叫VNode。结点里头首先要包含一个指针,表示指向第一个结点的边。有的情况下每一个顶点还要存一堆乱七八糟的东西的话,那么我们再定义一个Data

typedef struct Vnode{
	PtrToAdjVNode FirstEdge;
	DataType Data; /* 存顶点的数据 */
} AdjList[MaxVertexNum]; 
/* AdjList是邻接表类型 */

接下来我们再来看这个链表里头代表每一条边的PtrToAdjVNode。虽然说它代表的是一条边,但是它不需要存两个顶点,因为它必定是放在某一个顶点的链表里,所以我们只需要存它的邻接点。

typedef struct AdjVNode *PtrToAdjVNode; 
struct AdjVNode {
	Vertex AdjV; /* 邻接点下标 */
	WeightType Weight; /* 边权重 */
	PtrToAdjVNode Next; 
};

六、邻接表表示的图-建立图

1、初始化一个有VertexNum个顶点但没有边的图
建立LGraph过程跟建立MGraph的过程从原理上来讲是一样的,都是先初始化一个包含了所有的顶点但是一条边都没有的一个图,然后再一条边一条边地插到这个图里面,只不过细节略微有点区别。对于LGraph来说,没有边代表每一个顶点跟着那个链表都是空的,对于这个Graph里的邻接表的每一个顶点V来说,它的指向第一条边的指针FirstEdge都是空的。

typedef int Vertex; /* 用顶点下标表示顶点,为整型 */
LGraph CreateGraph( int VertexNum )
{ 
	Vertex V, W;
	LGraph Graph;
	Graph = (LGraph)malloc(sizeof(struct GNode)); 
	Graph->Nv = VertexNum;
	Graph->Ne = 0;
	/* 注意:这里默认顶点编号从0开始,到(Graph->Nv - 1) */
	for ( V=0; V<Graph->Nv; V++ )
	Graph->G[V].FirstEdge = NULL;
	return Graph; 
}

2、 向LGraph中插入边
插入边的过程就是把一个顶点插到一个链表里面去的过程。如下图所示,现在我要插入V1到V2这条边,从实现的角度讲显然是插在这个链表的头部是最简单的。先建立一个新的结点V2,然后让其Next指向G[V1]的FirstEdge,最后让G[V1]的FirstEdge指向V2
在这里插入图片描述
对应代码如下:

void InsertEdge( LGraph Graph, Edge E )
{ 
	PtrToAdjVNode NewNode;
	/***************** 插入边 <V1, V2> ****************/
	/* 为V2建立新的邻接点 */
	NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
	NewNode->AdjV = E->V2;
	NewNode->Weight = E->Weight;
	/* 将V2插入V1的表头 */
	NewNode->Next = Graph->G[E->V1].FirstEdge;
	Graph->G[E->V1].FirstEdge = NewNode;
	/********** 若是无向图,还要插入边 <V2, V1> **********/
	/* 为V1建立新的邻接点 */
	NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
	NewNode->AdjV = E->V1;
	NewNode->Weight = E->Weight;
	/* 将V1插入V2的表头 */
	NewNode->Next = Graph->G[E->V2].FirstEdge;
	Graph->G[E->V2].FirstEdge = NewNode;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

知初与修一

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

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

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

打赏作者

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

抵扣说明:

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

余额充值