( 从零开始的数据结构生活 )五、图


今天学习最后一节内容,这一节内容我只介绍图的表示、图的建立以及图的遍历,而一些图的经典算法,我准备在算法那一专栏进行解释,至于图节点的插入,删除等更高深的操作,需要在拥有以上的基础下,并且足够熟悉才可以
文章目录:

原创点:Harsha老哥似乎只有图的表示这一内容,并且没有代码,所以这一节图的表示的代码和其他内容都由秋秋亲自完成。


1.图的介绍


这里我只说明学习图的前提:你需要学习线性代数、离散数学、以及高等数学等知识;
而这一节的基本内容只有图的一小部分,你可以理解为图的基础;
至于图的进阶内容,我更希望你们取学习图论,图的高阶知识作为一本书。
(1)图有关术语:
邻接与关联、顶点的度、简单图、完全图、权值与网、子图、路径与回路、连通图、强连通图、生成树;这些内容需要读者有概念及基础,不然这节课会很吃力。
(2)图的基本运算:
CrerateGraph:创建图
DestoryGraph:销毁图
LocateVertex:查找顶点
FirstAdjVertex:查找某顶点的邻接顶点
InsertVertex:插入一个顶点
DeleteVertex:删除一个顶点
InsertArc:插入一条边
DeleteArc:删除一条边
TraverseGraph:遍历图


2. 图的表示


(1)边列表表示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bchPGuXs-1662384057540)(:/692d74d6b9b94402a17c50911d577f01)]


结构体的定义

struct Edge
{
	char* startV;
	char* endV;
	int weight;
};
char vertex_list[MAXSIZE];
struct Edge edge_list[MAXSIZE];

在C++中,你可以用 string* startV ;


(2) 邻接矩阵表示图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N8N9RyAa-1662384057542)(:/9e06481755244d3e88418159d5f1b08f)]
上图关于主对角线对称,当然这不符合有向图


结构体的定义:

#define MAXLEN 100	//图的最大顶点数
typedef struct vertex
{	int num; 	//用于描述顶点的序号信息,此处整数
		···		//剩下的信息暂时忽略
}Datatype;

struct Mgraph	
{
    int n,e;	//记录顶点数n和边的个数e    
    int edges[MAXLEN+1][MAXLEN+1];	//邻接矩阵,下表从1开始
    Datatype vexs[MAXLEN+1];	//储存顶点的数组,下标从1开始
};

对于稀疏图来说,邻接矩阵不够简洁,因为会有大量的零,但是这并不是重要的元素;
对于稠密图来说,邻接矩阵可能是很好的表达方式了。


创建图的邻接矩阵:

//创建一个n顶点,e条无向边的无向图G的邻接矩阵
void CreateGraph(struct Mgraph*  g,int n,int e)
{
	int i,j,k,v1,v2;
	g->n = n;
	g->e = e;
	for(i=1;i<g->n;i++)	//序号从1开始
		g->ves[i].num = i;
	for(i=1;i<=g->n;i++)	//初始化邻接矩阵
		for(i=1;j<=g->n;i++)
			g->edges[i][j] = 0;
	for(k=1;k<=g->e;k++)
	{
		scanf("%d%d",&v1,&v2);
		g->edges[v1][v2] = 1;	//无向图都要执行
		g->edges[v2][v1] = 1;	//有向图不执行该语句
	}
}

其时间复杂度为O(N^2).


(3)邻接表表示图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZiTpBHX-1662384057543)(:/4bd18a7f827c4af2bd98b492ec7f7538)]


结构体

#define MAXLEN 100
struct EdgeNode	//边链表节点类型
{
	int adjvex;
	struct EdgeNode* next;
};

struct VertexNode	//表头节点类型
{
	int vertex;
	struct EdgeNode* firstedge;
};

struct ALGraph	//图的邻接表类型
{
	struct VertexNode Adjlist[MAXLEN+1];
	int n,e;	
};

建立图的邻接表

//创建一个n顶点,e条无向边的无向图G的邻接表
void CreateAdjList(struct ALGraph* g,int n, int e)
{
	struct EdgeNode* ptr;
	int k,v1,v2;
	g->n = n;
	g->e = e;
	for(k=1;k<=g->n;k++)
	{
		g->adglist[k].vertex=k;	//假设各个顶点对应的序号为1~n
		g->adjlist[k].firstedge = NULL;	//边链表初始化
	}
	for(k=1;k<=g->e;k++)	//按边数循环e次
	{
		scanf("%d%d",&v1,&v2);	//输入一条边所对应的两个顶点
		ptr = (struct EdgeNode*)malloc(sizeof(struct EdgeNode));
		ptr->vertex = v2;	//写入数据
		ptr->next = g->adjlist[v1].firstedge;	//头插法插入v1对应的边链表
		g->adjlist[v].firstedge = ptr;
		// 有向图不执行以下语句
		ptr = (struct EdgeNode*)malloc(sizeof(struct EdgeNode));
		ptr->adgvex = v1;
		ptr->next = g->adglist[v2].firstedge;
		g->adglist[v2].firstedge = ptr;
	}
}

对于稀疏图来说,邻接表确实是存储数据的一个优良算法,因为其时间复杂度要比邻接矩阵的要小,为:O(N+e).


3.图的遍历


图的遍历在最初代码实现的时候,总有这两个问题:重复经过和顶点遗漏;
针对第一个问题:设置一个数组,若值为零则未访问,值为一则已访问;
针对第二个问题,在遍历一遍后,对数组进行检查。
本节带来两个算法:图的深度优先遍历和广度优先遍历。
两个算法的最大区别:
深度优先建立在递归上,使用的是递归的思想;
广度优先建立在队列上,使用的是队列的思想;


(1)深度优先遍历
就是一条路走到黑,知道走不动了,遍历完了,选择下一个邻接节点进行重新遍历;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4RcxqJ2G-1662384057543)(:/a7499732037f4e54bf160b63d9b84666)]


<1> 邻接矩阵的深度优先遍历

int visited[MAXLEN+1]
void DFS(ALGraph* g,int i)	//算法
{
	int j;
	pritnf("%3d",g->vexs[i].num);//输出该信息
	visited[i] = 1;		//修改标志
	for(j=1;j<=g->n;j++)
		if((g->edges[i][j] == 1)&&!visited[j])	//等价于if ( visited[i]==0 )
			DFS(g,j)
}
void DFStraverse(MGraph* g)	//只需要传递指针,进行前期准备工作
{
	int i;
	for(i=1;i<=g->n;i++)
		visited[i]=0;		//初始化
	for(i=1;i<=g->n;i++)	//确保每一个都进行遍历
		if(!visited[i])		//visited函数进行排查,确保每一个节点都遍历
			DFS(g,i);		//递归
}

<2> 邻接表的深度优先遍历

int visited[MAXLEN+1]
void DFS(ALGraph* g,int i)	//算法
{
	EdgeNode* p;
	printf("%3d",g->adglist[i].vertex);	//输出该信息
	visited[i] = 1;		//修改标志
	for(p=g->Adjlist[i].firstedge;p!=NULL;p=p->next)
		if(!visited[p->adjvex])	//等价于if ( visited[i]==0 )
			DFS(g,p->adjvex);
}
void DFStraverse(ALGraph* g)	//只需要传递指针,进行前期准备工作
{
	int i;
	for(i=1;i<=g->n;i++)
		visited[i]=0;		//初始化
	for(i=1;i<=g->n;i++)	//确保每一个都进行遍历
		if(!visited[i])
			DFS(g,i);
}

(2)广度优先遍历
从上到下,从左到右,依次遍历。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P2XJteW4-1662384057544)(:/15da9ee8c09542ab888d685fab0d108a)]


<1> 邻接矩阵的广义优先遍历

void BFS(MGraph* g,int v)	//从顶点v开始
{
	int j;
	SeqQueue* q;		//开创一个队列
	InitQueue(q);		//队列初始化
	visited[v] = 1;		//标记,表示已访问
	EnQueue(q,v);		//入队
	while(!QueueEmpty q)
	{
		DeQueue(q,&v);		//出队
		for(j=1;j<=g->n;j++)
			if(g->edges[v][j]==1&&!visited[j])
			{
				visited[j]==1;
				EnQueue(q,j);	//入队
			}
	}
}

<2> 邻接表的广义优先遍历

void BFS(ALGraph* g,int v)	//从顶点v开始
{
	struct EdgeNode* p;
	SeqQueue* q;		//开创一个队列
	InitQueue(q);
	visited[v] = 1;		//标记,表示已访问
	EnQueue(q,v);
	while(!QueueEmpty q)
	{
		DeQueue(q,&v);
		p=g->adjlist[v].firstedge;
		while(p!=NULL)
		{
			if(!visited[p-adjvex])
			{
				visited[p-adjvex]==1;
				EnQueue(q,p-adjvex);
			}
			p = p->next;
		}
	}
}

  • 最后以上内容就是这么多,希望读者多加练习,会很容易了解数据结构的。
    Harsha Suryanarayana的熟肉
    同时也感谢up主fengmuzi2003的翻译。
  • 这就是本专栏的全部内容了,确实比预计的少了很多,希望读者可以理解秋秋。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值