【C语言数据结构】图的创建和遍历(数形结合,简单详细!)

一.前言: 

学习这一节之前,我们需要弄懂一些基础的概念,不然会在学后面的时候有点迷糊。

1.顶点:

线性表中我们把数据元素叫元素,在树中叫结点,在图中叫顶点(Vertex)。

2.邻接矩阵:(无向图)

一种用于表示图的数据结构。它是一个二维数组,其中的元素表示图中顶点之间的连接关系。对于一个有n个顶点的图,邻接矩阵是一个大小为n×n的二维数组。要是图中有两个顶点(假设是第1个和第2个顶点)相连(存在一条边),那么他们对应在邻接矩阵中的第1行第2列第2行第1列位置为1,其他没有相连的用0表示。

如下图表示:(下面都用以下图举栗子)

我们把有相连边的顶点拿出来:AB,AC,AD,BC,BD,DE,BE(不分先后)

因为顶点顺序为ABCDE,然后我们可以作出它的邻接矩阵:

邻接矩阵ABCDE
A01110
B10111
C11000
D11001
E01010

 观察发现无向图邻接矩阵是关于主对角线对称的,这使得后面可以通过邻接矩阵的对称性了解和运用图的一些性质。

»»具体图的基本概念还不熟悉的同学可以看这位大佬的文章,了解了直接往下看代码举例

数据结构与算法(C语言) | 图的基本定义及存储结构icon-default.png?t=N7T8https://blog.csdn.net/qq_35924276/article/details/81781022

二.代码部分:

//无向图图的创建
#include<stdio.h>
#include<stdlib.h>
#define MAX_SIZE 5
typedef struct graph
{
	char* vexs;//存储图中所有顶点的标签或名称
	int** arcs;//用来表示一个邻接矩阵(二维数组)
	int vexNum;//顶点数
	int arcNum;//边数
}Graph;

//以下所有队列的操作都是为BFS遍历做的准备,不可少
typedef struct queue
{
	int front;//标记队首元素的位置 
	int rear;//标记队尾元素的下一个空位置
	int data[MAX_SIZE];
}Queue;

Queue* initQueue()//分配队列空间
{
	Queue* Q=(Queue*)malloc(sizeof(Queue));
	Q->front=Q->rear=0;//处在同一位置时为空
	return Q;
}
int isFull(Queue* Q)//判断队列是否已满
{
	if((Q->rear+1)%MAX_SIZE==Q->front)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int isEmpty(Queue* Q)//判断是否为空
{
	if(Q->front==Q->rear)//在同一位置时空
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int enQueue(Queue* Q,int data)//入队(尾插)
{
	if(isFull(Q))
	{
		return 0;
	}
	else
	{
		Q->data[Q->rear]=data;
		Q->rear=(Q->rear+1)%MAX_SIZE;
		return 1;
	}
}
int deQueue(Queue* Q)//出队(头出)
{
	if(isEmpty(Q))
	{
		return -1;
	}
	else
	{
		int data=Q->data[Q->front];
		Q->front=(Q->front+1)%MAX_SIZE;//数据往后移一位,相当于后面一个元素往前移了
		return data;
	}
}

Graph* initGraph(int vexNum)//为图结构体分配空间
{
	Graph* G=(Graph*)malloc(sizeof(Graph));
	G->vexs=(char*)malloc(sizeof(char)*vexNum);
	G->arcs=(int**)malloc(sizeof(int*)*vexNum);
	for(int i=0;i<vexNum;i++)
	{
		G->arcs[i]=(int*)malloc(sizeof(int)*vexNum);
	}
	G->vexNum=vexNum;//初始化顶点数
	G->arcNum=0;//初始化边数
	return G;
}
void creatGraph(Graph* G,char* vexs,int* arcs)//创建一个图
{
	for(int i=0;i<G->vexNum;i++)
	{
		G->vexs[i]=vexs[i];
		for(int j=0;j<G->vexNum;j++)
		{
			//一种指针算术运算,通过指针运算可以模拟成二维数组。假设 arcs 的大小为 m 行 n 列,那么第 i 行、第 j 列的元素可以通过 *(arcs + i * n + j) 来访问。
            G->arcs[i][j]=*(arcs+i*(G->vexNum)+j);
			if(G->arcs[i][j]!=0)//更新边数,1有边,0无边
			{
				G->arcNum++;
			}
			
		}
	}
	G->arcNum/=2;//因为是无向图,这里的边数算了两次,只算一次即可
}
void DFS(Graph* G,int* flag,int index)//深度优先遍历
{
	printf("%c\t",G->vexs[index]);
	flag[index]=1;//已经访问过顶点标记为1,之后不会再访问
	for(int i=0;i<G->vexNum;i++)
	{
		if(G->arcs[index][i]==1&&!flag[i])
		{
			DFS(G,flag,i);
		}
	}
}
void BFS(Graph* G,int* flag,int index)//广度优先遍历(队列)
{
	Queue* Q=initQueue();
	printf("%c\t",G->vexs[index]);
	flag[index]=1;
	enQueue(Q,index);
	while(!isEmpty(Q))//队不空时 
	{
		int i=deQueue(Q);
		for(int j=0;j<G->vexNum;j++)//就将邻接矩阵每一行有1的顶点先输出,输出完了再转到刚刚有1的顶点的那一行,再看此行有没有1的顶点,接着输出
		{
			if(G->arcs[i][j]==1&&!flag[j])
			{
				printf("%c\t",G->vexs[j]);
				flag[j]=1;
				enQueue(Q,j);
			}
		}
	}
}
int main()
{
	Graph* G=initGraph(5);
	int* flag=(int*)malloc(sizeof(int)*G->vexNum);//为每一个顶点都分配一块用来判断是非已输出的空间
	for(int i=0;i<G->vexNum;i++)//首先赋值为0,表示未访问
	{
		flag[i]=0;
	}
	int arcs[5][5]=
	{
		0,1,1,1,0,
		1,0,1,1,1,
		1,1,0,0,0,
		1,1,0,0,1,
		0,1,0,1,0
	};// 有边是1,无边是0
	creatGraph(G,"ABCDE",(int*)arcs);
	DFS(G,flag,0);
	printf("\n");
	for(int i=0;i<G->vexNum;i++)//首先赋值为0,表示未访问
	{
		flag[i]=0;
	}
	BFS(G,flag,0);
	printf("\n");
	return 0;
}

1.DFS遍历函数解释:

解释:DFS叫深度优先遍历,这种遍历方式主要思路是从图中一个未访问的顶点V开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底,不断递归重复此过程,直到所有的顶点都遍历完成。它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。还是没有理解的同学可以看下面的遍历的分解过程。

函数体:

void DFS(Graph* G,int* flag,int index)//深度优先遍历
{
	printf("%c\t",G->vexs[index]);
	flag[index]=1;//已经访问过顶点标记为1,之后不会再访问
	for(int i=0;i<G->vexNum;i++)
	{
		if(G->arcs[index][i]==1&&!flag[i])
		{
			DFS(G,flag,i);
		}
	}
}

 遍历过程如下:

<1> 此时由A到B,发现A有到B的边,则遍历到B处,B可以输出

<1>ABCDE
A0110
B10111
C11000
D11001
E01010

<2>由B继续往后遍历,B有到C的边,则C可以输出

<2>ABCDE
A01110
B1011
C11000
D11001
E01010

<3>到C顶点处,没有下一个未访问的顶点了,正常来说按顺序是到D的,结果C到D没有边,则递归返回到B顶点,由B继续走未走过的边遍历未访问的顶点,于是到了D顶点,D输出

<3>ABCDE
A01110
B1011
C11000
D11001
E01010

<4>D继续往后走,发现到E有路了,于是E输出

<4>ABCDE
A01110
B10111
C11000
D1100
E01010

总结:通过以上的图像分步遍历示意,同学们应该知道规律了。找下一个相邻顶点时看行,有没有边看行列相交处是否为1,有边是1,无边为0。

 1.BFS遍历函数解释:

BFS叫广度优先遍历,它从图的起始节点开始,首先访问所有与起始节点直接相连的节点,然后再访问与这些节点直接相连的所有未被访问过的节点,这个过程会持续到所有的节点都被访问过。这种遍历方式类似于波纹在水面上扩散的过程。在BFS广度遍历中,通常使用队列来存储待访问的节点。还是没有理解的同学可以看下面的遍历的分解过程。

函数体:

void BFS(Graph* G,int* flag,int index)//广度优先遍历(队列)
{
	Queue* Q=initQueue();
	printf("%c\t",G->vexs[index]);//开始:那一个顶点出来(A)
	flag[index]=1;//标记A列无顶点输出了,也就是A已经输出
	enQueue(Q,index);//开始:下标0入队
	while(!isEmpty(Q))//队不空
	{
		int i=deQueue(Q);//开始:0出队,i=0,所以到A行
		for(int j=0;j<G->vexNum;j++)//就将邻接矩阵每一行有1的顶点先输出,输出完了再转到刚刚有1的顶点的那一行,再看此行有没有1的顶点,接着输出
		{
			if(G->arcs[i][j]==1&&!flag[j])
			{
				printf("%c\t",G->vexs[j]);
				flag[j]=1;
				enQueue(Q,j);
			}
		}
	}
}

遍历过程如下: 

<1>先输出顶点A,此时flag[0]=1,表示第一列(j=0)已经有顶点A输出,这里保证每个顶点都输出的话,就要做这个标记,之后就不会再来这一列(j=0)找顶点了

i\j

→(j)

01234
↓(i)<1>ABCDE
0A

0  flag[0]=1

1110
1B10111
2C11000
3D11001
4E01010

<2>输出顶点A后,继续在A行里面找1,B,C,D都有1,所以顶点都输出,并将其顶点对应的下标j值存入队列中,之后会访问这些j下标的行。另外还flag[1]=flag[2]=flag[3]=1,标志着BCD列都没有顶点输出了。

i\j

→(j)

01234
↓(i)<2>ABCDE
0A

0  flag[0]=1

1

flag[1]=1

1

flag[2]=1

1

flag[3]=1

0

1B10111
2C11000
3D11001
4E01010

<3>之后出队的是1,i=1,就来到了B行,B中ABCD列顶点都标记输出过了,E列有1,未标记,表示有边,输出E,之后j=4这个下标入队。到C行之后每一行就没有顶点了输出,因为列全标记了,遍历才结束。

i\j

→(j)

01234
↓(i)<3>ABCDE
0A

0  flag[0]=1

1

flag[1]=1

1

flag[2]=1

1

flag[3]=1

0

1B1011

1

flag[4]=1

2C11000
3D11001
4E01010

 总结:flag是用来标记列的,可以这样认为:一列代表一个顶点,要是全部的列标记了就可以证明遍历结束了。另外,行只是用来看是否有到达下一个顶点的条件(有1就有边),这里行的执行顺序是按入队出队顺序决定的。

 NO.33 【已修改代码注释漏洞】

如果这篇文章有帮助到你,希望给个免费的点赞支持,感谢🌹

  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

木鳶戾天

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

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

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

打赏作者

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

抵扣说明:

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

余额充值