Graph traversal (breadth-first traversal, BFS)

1. Concept

The graph traversal operation starts from a certain vertex in the graph, and visits all vertices in the graph once and only once

(1) In the graph, the starting vertex for traversal is the vertex with the smallest number

(2) If a starting point cannot reach all vertices, multiple calls are made to access all vertices

(3) In order to avoid the traversal from falling into an infinite loop due to loops , an array of visited flags visited[n] is attached (where it corresponds to all vertex subscripts, set it to 1 if it has been visited; set it to 0 if it has not been visited )

(4) The numbers of all nodes start from 0

2. Ideas

(1) Initialize the queue Q;

(2) Visit vertex v, and vertex v enters queue Q;

(3) As long as the queue is not empty, pop a value and assign it to v (outer loop);

(4) Loop through all adjacent points of v , and stack unvisited adjacent points (inner loop);

For example, the picture below: (The picture comes from the handouts of the related course "Data Structure" by Mr. Lazy Cat)

 pseudocode:

The function of popping and pushing operations is to save the nodes in sequence according to the level, and the node that is popped out first is also the node with the smallest number of layers, thus realizing the requirement of breadth-first traversal to access according to the number of layers

 3. Code implementation

 3.1 Function code

(1) Breadth-first traversal of adjacency matrix

void BFSTraverse(int arc[][MAX_VERTEX], DataType *vertex, int vertexNum, int v) {//广度优先遍历
	int visited[vertexNum];
	initvertex(vertexNum, visited);//初始化数组
	SqQueue Q;
	InitQueue(&Q);//初始化队列
	visit(vertex, v); //输出访问过的顶点信息
	visited[v] = 1;
	EnQueue(&Q, v); //将顶点v的下标入队
	while (QueueEmpty(Q) != 1) { //当队列非空时
		v = DeQueue(&Q); //将队头元素出队并送到v中
		for (int w = 0; w < vertexNum; w++) { //将该顶点的所有连接点都扫描一遍
			if (arc[v][w] == 1 && visited[w] == 0) {
				visit(vertex, w);
				visited[w] = 1;
				EnQueue(&Q, w); //将顶点w的下标入队
			}
		}
	}
}

(2) Breadth-first traversal of the adjacency list

void BFSTraverse(VertexNode *adjList, int vertexNum, int v) { //广度优先遍历
	int visited[vertexNum];
	initvertex(vertexNum, visited);
	SqQueue Q;
	InitQueue(&Q);//初始化队列
	ArcNode *p;
	visit(adjList, v); //输出访问过的顶点信息
	visited[v] = 1;
	EnQueue(&Q, v); //将顶点v的下标入队
	while (QueueEmpty(Q) != 1) { //当队列非空时
		v = DeQueue(&Q); //将队头元素出队并送到v中
		p = adjList[v].firstEdge; //工作指针p指向顶点v的边表
		while (p != NULL) {
			//遍历该顶点的边表
			if (visited[p->adjvex] == 0) {
				visit(adjList, p->adjvex);
				visited[p->adjvex] = 1;
				EnQueue(&Q, p->adjvex); //将顶点w的下标入队
			}
			p = p->next;
		}
	}
}

3.2 Complete code and test

The following code includes the construction of the graph, the depth-first traversal of the graph, and the function implementation of the breadth traversal of the graph mentioned above. If you need a detailed explanation of the first two parts, you can visit here:

Graph Construction: Graph Storage (Adjacency Matrix, Adjacency List, Cross Linked List)

Depth-first traversal of graphs: graph traversal (depth-first traversal, DFS)

(1) queue

Because the basic operation of the queue is required in the breadth-first traversal operation, the following queue (circular queue) is shared by the breadth-first traversal of the two graphs

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXQSIZE 100//最大队列长度
typedef int QElemType;

typedef struct {
	QElemType *data; //指向队列储存空间
	int front; //队首下标
	int rear;  //队尾下标
} SqQueue;

int InitQueue(SqQueue *Q) {//初始化队列函数,初始化成功返回1,失败返回0
	Q->data = (QElemType *)malloc(MAXQSIZE * sizeof(QElemType));
	if (Q->data != NULL) {
		Q->front = Q->rear = 0;
		return 1;
	}
	return 0;
}

void DestroyQueue(SqQueue *Q) { //销毁队列函数
	if (Q->data != NULL)
		free(Q->data);
}

void ClearQueue(SqQueue *Q) {
	//这里其实只需要把指针重新归位就好了,其他的数据会被后来的数据覆盖掉
	Q->rear = Q->front;
}

int QueueEmpty(SqQueue Q) { //是否为空,为空返回1,非空返回0
	if (Q.front == Q.rear)
		return 1;
	else
		return 0;
}

int QueueFull(SqQueue Q) { //队列是否为满,已满返回1,未满返回0
	if ((Q.rear + 1) % MAXQSIZE == Q.front)
		return 1;
	else
		return 0;
}

int QueueLength(SqQueue Q) {//返回队列长度
	int count = 0;
	while (Q.front != Q.rear) {
		if (Q.front >= MAXQSIZE)
			Q.front -= MAXQSIZE;
		Q.front++;
		count++;
	}
	return count;
}

QElemType GetHead(SqQueue Q) { //若队列非空,返回队列首的值
	int e;
	if (QueueEmpty(Q) != 1) {
		e = Q.data[Q.front + 1];
		return e;
	} else
		printf("队列为空!\n");
}

void EnQueue(SqQueue *Q, QElemType e) { //将e元素插入队列
	if (QueueFull(*Q) == 1) {
		printf("队列已满,无法添加!\n");
		return;
	} else {
		Q->rear = (Q->rear + 1) % MAXQSIZE; //通过这个语句实现循环
		Q->data[Q->rear] = e;
	}
}

QElemType DeQueue(SqQueue *Q) { //删除队头元素,并返回其值
	QElemType e;
	if (QueueEmpty(*Q) != 1) {
		e = Q->data[Q->front + 1];
		Q->front = (Q->front + 1) % MAXQSIZE;
		//这里不用对移动后的front位置上的元素做处理,就当作没有,因为如果后续有也会被覆盖
		return e;
	} else {
		printf("队列为空!\n");
	}
}

(2) Adjacency matrix

1) graph_adjacency matrix.h

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "循环队列.h"
#define MAX_VERTEX 10//最大的顶点个数
typedef int DataType;

//以下是无向图的定义
void MGraph(DataType *vertex, int arc[][MAX_VERTEX], int vertexNum, int arcNum) { //初始化构造图(邻接矩阵法)
	printf("请逐个输入顶点的内容:");
	DataType x;
	DataType vi, vj; //构建邻接矩阵时,一条边的两个结点编号
	for (int i = 0; i < vertexNum; i++) { //顶点数组赋值
		scanf("%d", &x);
		vertex[i] = x;
	}
	for (int i = 0; i < vertexNum; i++) //初始化邻接矩阵
		for (int j = 0; j < vertexNum; j++)
			arc[i][j] = 0;
	int count = 1;
	for (int i = 0; i < arcNum; i++) { //依次输入每一条边
		printf("请输入第%d条边依附的两个顶点的编号:", count++);
		scanf("%d %d", &vi, &vj); //输入该边依附的顶点的编号
		arc[vi][vj] = 1; //置有边标志
		arc[vj][vi] = 1;
	}

}

void printMGraph(DataType *vertex, int arc[][MAX_VERTEX], int vertexNum) { //输出
	printf("vertex:");
	for (int i = 0; i < vertexNum; i++) {
		printf("%d ", vertex[i]);
	}
	printf("\n");
	printf("arc:\n");
	for (int i = 0; i < vertexNum; i++) {
		for (int j = 0; j < vertexNum; j++) {
			if (j == vertexNum - 1)
				printf("%d\n", arc[i][j]);
			else
				printf("%d ", arc[i][j]);
		}
	}

}

int isLinked(int arc[][MAX_VERTEX], int i, int j) { //两顶点i,j是否有边相连,1是相连,0是不相连
	if (arc[i][j] == 1)
		return 1;
	else
		return 0;
}

int nodeDepth(int arc[][MAX_VERTEX], int index, int vertexNum) { //任意一顶点的度
	//无向图任意遍历行\列求和
	int count = 0;
	for (int i = 0; i < vertexNum; i++) {
		if (arc[index][i] == 1)
			count++;
	}
	return count;
}

void initvertex(int vertexNum, int *visited) { //初始化visited函数
	for (int i = 0; i < vertexNum; i++)
		visited[i] = 0;
}

void visit(DataType *vertex, int v) { //输出访问过的顶点信息
	printf("%d ", vertex[v]);

}

void DFSTraverse(int arc[][MAX_VERTEX], DataType *vertex, int *visited, int vertexNum,//深度优先遍历
                 int v) { //v是遍历的起始位置的编号
	static int flag = 0;
	if (flag == 0) {//第一次需要初始化
		initvertex(vertexNum, visited);
		flag = 1;
	}
	visit(vertex, v); //输出访问过的顶点信息
	visited[v] = 1;
	for (int i = 0; i < vertexNum; i++) { //因为是邻接矩阵,所以编号的顺序本来就是由小到大的.所以不用遍历找
		if (arc[v][i] != 0 && visited[i] == 0)
			DFSTraverse(arc, vertex, visited, vertexNum, i); //二维数组传参,调用时实参直接写数组名
	}
}

void BFSTraverse(int arc[][MAX_VERTEX], DataType *vertex, int vertexNum, int v) {//广度优先遍历
	int visited[vertexNum];
	initvertex(vertexNum, visited);//初始化数组
	SqQueue Q;
	InitQueue(&Q);//初始化队列
	visit(vertex, v); //输出访问过的顶点信息
	visited[v] = 1;
	EnQueue(&Q, v); //将顶点v的下标入队
	while (QueueEmpty(Q) != 1) { //当队列非空时
		v = DeQueue(&Q); //将队头元素出队并送到v中
		for (int w = 0; w < vertexNum; w++) { //将该顶点的所有连接点都扫描一遍
			if (arc[v][w] == 1 && visited[w] == 0) {
				visit(vertex, w);
				visited[w] = 1;
				EnQueue(&Q, w); //将顶点w的下标入队
			}
		}
	}
}

2) Graph test_adjacency matrix.c

#include "图_邻接矩阵.h"

main() {
	DataType vertex[MAX_VERTEX];//储存所有的顶点
	int arc[MAX_VERTEX][MAX_VERTEX];//邻接矩阵,结点间的连通关系
	int vertexNum, arcNum; //结点个数,边的个数
	printf("输入顶点个数:");
	scanf("%d", &vertexNum);
	printf("输入边个数:");
	scanf("%d", &arcNum);
	MGraph(vertex, arc, vertexNum, arcNum);
	printMGraph(vertex, arc, vertexNum);
	printf("测试判断两顶点是否相连,请输入两个顶点下标:");
	int i, j;
	scanf("%d %d", &i, &j);
	if (isLinked(arc, i, j) == 1)
		printf("相连!\n");
	else
		printf("不相连!\n");
	printf("测试求顶点的度,请输入一个顶点下标:");
	int index;
	scanf("%d", &index);
	printf("该顶点的度为:%d\n", nodeDepth(arc, index, vertexNum));
	printf("-----------------测试邻接矩阵的深度优先遍历-----------------\n");
	printf("\n");
	int visited[vertexNum];//判断结点是否访问过,访问过设置1,未访问过为0
	int v;
	printf("请输入深度优先遍历的第一个结点编号:");
	scanf("%d", &v);
	printf("深度优先遍历序列:");
	DFSTraverse(arc, vertex, visited, vertexNum, v);
	printf("\n");
	printf("\n");
	printf("-----------------测试邻接矩阵的广度优先遍历-----------------\n");
	printf("\n");
	printf("请输入广度优先遍历的第一个结点编号:");
	scanf("%d", &v);
	printf("广度优先遍历序列:");
	BFSTraverse(arc, vertex, vertexNum, v);
	printf("\n");
}

3) Test output

Test case: (undirected graph)

Output: (Pay attention to distinguish the content and number of the node, the node data starts from 1, and the node number starts from 0)

(2) Adjacency list

1) Figure_adjacency list.h

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "循环队列.h"
#define MAX_VERTEX 10//最大的顶点个数
typedef char DataType;

/*
当DataType为char时要特别注意输入时空格和换行符,分别利用getchar()
和fflush(stdin);来清空内存区
*/
typedef struct ArcNode { /*边表结点*/
	int adjvex;//边表数据域,即下标
	struct ArcNode *next; //指针域
} ArcNode;

typedef struct VertexNode { /*顶点表结点*/
	DataType vertex;//顶点的数据
	ArcNode *firstEdge; //指向储存该顶点所连接所有结点的边表
} VertexNode;
void initVertex(DataType *, int);

//以下是有向图的定义
void ALGraph(DataType *vertex, VertexNode *adjList, int vertexNum, int arcNum) { //初始化构造图(邻接表法)
	int vi, vj;
	int count = 0;
	initVertex(vertex, vertexNum);
	for (int i = 0; i < vertexNum; i++) { //初始化顶点表
		adjList[i].vertex = vertex[i];
		adjList[i].firstEdge = NULL;
	}
	for (int i = 0; i < arcNum; i++) {
		//输入边的信息储存在边表中
		printf("请输入第%d条边依附的两个顶点的编号(方向->):", count++);
		fflush(stdin);//清除输入缓冲区(否则这里就会直接跳过scanf)
		scanf("%d %d", &vi, &vj); //输入该边依附的顶点的编号
		ArcNode *s;
		s = (ArcNode *)malloc(sizeof(ArcNode));
		if (s != NULL) {
			s->adjvex = vj;
			s->next = adjList[vi].firstEdge; //头插法建立链表
			adjList[vi].firstEdge = s;
		} else
			printf("init error!\n");
	}
}

void initVertex(DataType *vertex, int vertexNum) {//输入函数
	printf("请逐个输入顶点的内容:");
	DataType x;
	for (int i = 0; i < vertexNum; i++) { //顶点数组赋值
		getchar();//吸收空格
		scanf("%c", &x);
		vertex[i] = x;
	}
}

void printALGraph(VertexNode *adjList, int vertexNum) {
	printf("vertex  firstEdge\n");
	ArcNode *p ;
	for (int i = 0; i < vertexNum; i++) {
		printf("%3c -->", adjList[i].vertex);
		p = adjList[i].firstEdge;
		while (p != NULL) {
			printf("%d -->", p->adjvex);
			p = p->next;
		}
		printf("NULL\n");
		printf("\n");
	}
}

int isLinked(VertexNode *adjList, int i, int j) { //两顶点i,j是否有边相连,1是相连,0是不相连
	ArcNode *p = adjList[i].firstEdge ;
	while (p != NULL) {
		if (p->adjvex == j)
			return 1;
		else
			p = p->next;
	}
	p = adjList[j].firstEdge;
	while (p != NULL) {
		if (p->adjvex == i)
			return 1;
		else
			p = p->next;
	}
	return 0;
}

int nodeDepth(VertexNode *adjList, int index, int vertexNum) { //任意一顶点的度
	int count = 0;
	ArcNode *p = adjList[index].firstEdge;
	while (p != NULL) {
		count++;
		p = p->next;
	}
	return count;
}

void freeArcNode(VertexNode *adjList, int vertexNum) {
	ArcNode *p;
	ArcNode *temp;
	for (int i = 0; i < vertexNum; i++) {
		p = adjList[i].firstEdge ;
		while (p != NULL) {
			temp = p;
			p = p->next;
			free(temp);
		}
	}
}

void initvertex(int vertexNum, int *visited) { //初始化visited函数
	for (int i = 0; i < vertexNum; i++)
		visited[i] = 0;
}

void visit(VertexNode *adjList, int v) { //输出访问过的顶点信息
	printf("%c ", adjList[v].vertex);//记得改变输出时要改变数据类型
}

void DFSTraverse(VertexNode *adjList, int *visited, int vertexNum, int v) {//深度优先遍历
	static int flag = 0;
	if (flag == 0) {//第一次需要初始化
		initvertex(vertexNum, visited);
		flag = 1;
	}
	visit(adjList, v); //输出访问过的顶点信息
	visited[v] = 1;
	ArcNode *p = adjList[v].firstEdge;
	while (p != NULL) {
		if (visited[p->adjvex] == 0)
			DFSTraverse(adjList, visited, vertexNum, p->adjvex);
		p = p->next;
	}
}

void ranklist(VertexNode *adjList, int vertexNum) { //将边表进行升序的排序,便于遍历操作
	ArcNode *p, *q;
	int temp;
	for (int i = 0; i < vertexNum; i++) {
		p = adjList[i].firstEdge;
		q = p;
		while (p != NULL) {
			while (q != NULL) {
				if (p->adjvex > q->adjvex) {
					temp = p->adjvex;
					p->adjvex = q->adjvex;
					q->adjvex = temp;
				}
				q = q->next;
			}
			p = p->next;
		}
	}
}

void BFSTraverse(VertexNode *adjList, int vertexNum, int v) { //广度优先遍历
	int visited[vertexNum];
	initvertex(vertexNum, visited);
	SqQueue Q;
	InitQueue(&Q);//初始化队列
	ArcNode *p;
	visit(adjList, v); //输出访问过的顶点信息
	visited[v] = 1;
	EnQueue(&Q, v); //将顶点v的下标入队
	while (QueueEmpty(Q) != 1) { //当队列非空时
		v = DeQueue(&Q); //将队头元素出队并送到v中
		p = adjList[v].firstEdge; //工作指针p指向顶点v的边表
		while (p != NULL) {
			//遍历该顶点的边表
			if (visited[p->adjvex] == 0) {
				visit(adjList, p->adjvex);
				visited[p->adjvex] = 1;
				EnQueue(&Q, p->adjvex); //将顶点w的下标入队
			}
			p = p->next;
		}
	}
}

2) Graph test_adjacency list.c

#include "图_邻接表.h"

int main() {
	DataType vertex[MAX_VERTEX];//储存所有的顶点
	int vertexNum, arcNum; //结点个数,边的个数
	printf("输入顶点个数:");
	scanf("%d", &vertexNum);
	printf("输入边个数:");
	scanf("%d", &arcNum);
	VertexNode adjList[vertexNum];//顶点表
	ALGraph(vertex, adjList, vertexNum, arcNum);
	ranklist(adjList, vertexNum);
	printALGraph(adjList, vertexNum);
	printf("测试判断两顶点是否相连,请输入两个顶点下标:");
	int i, j;
	scanf("%d %d", &i, &j);
	if (isLinked(adjList, i, j) == 1)
		printf("相连!\n");
	else
		printf("不相连!\n");
	printf("测试求顶点的度,请输入一个顶点下标:");
	int index;
	scanf("%d", &index);
	printf("该顶点的度为:%d\n", nodeDepth(adjList, index, vertexNum));
	printf("-----------------测试邻接表的深度优先遍历-----------------\n");
	printf("\n");
	int visited[vertexNum];//判断结点是否访问过,访问过设置1,未访问过为0
	int v;
	printf("请输入深度优先遍历的第一个结点编号:");
	scanf("%d", &v);
	printf("深度优先遍历序列:");
	DFSTraverse(adjList, visited, vertexNum, v);
	printf("\n");
	printf("\n");
	printf("-----------------测试邻接表的广度优先遍历-----------------\n");
	printf("\n");
	printf("请输入广度优先遍历的第一个结点编号:");
	scanf("%d", &v);
	printf("广度优先遍历序列:");
	BFSTraverse(adjList, vertexNum, v);
	freeArcNode(adjList, vertexNum);
}

3) Test output

Test case: (directed graph)

Output: (When the node data is char type, pay attention to the reading of spaces and line breaks when inputting)

Beginner Xiaobai, welcome to correct me if I make mistakes!

Guess you like

Origin blog.csdn.net/m0_63223213/article/details/126666911