C语言入门——BFS图的广度优先遍历

本文详细介绍了图的邻接表表示法,并以C语言为例,展示了如何使用广度优先搜索(BFS)算法遍历图,包括邻接表的构建、队列操作和BFS的具体实现步骤。
摘要由CSDN通过智能技术生成

本博客为考试服务,研究邻接表表示法下BFS的实现。

图的定义以及邻接矩阵表示法,请参见C语言入门——DFS图的深度优先遍历-CSDN博客

涉及到的数据结构知识点:单链表、队列、图 

一、图的邻接表表示法

图不仅可以用邻接矩阵表示,还可以用邻接表表示。

对于一个节点来说,如果我们只需要关注“与它有边相连”的节点,我们可以使用邻接表,将所有与之有边的关系的节点存入到一个单链表之中。

比如,对于这个图

 按照上面的方法,可以得到下面的邻接表。

于是,图的边可以表示成一个List*的单链表数组。 

typedef struct LNode* List;
struct LNode{
    int element;
    List next;
}LNode;//单链表

typedef GraphNode* Graph;
struct GraphNode{
    List* Adjacency_List;//邻接表
    Elementtype* data;//每个节点的数据
    int Capacity;//节点的数量
}GraphNode;

二、广度优先遍历

广度优先遍历,就是在遇到一个节点时,访问所有的与其相邻的节点,这样一层一层“铺开”,用类似水波扩散的层序遍历的思想来访问所有的节点。这时,我们已经无法用递归来解决问题,因为“递归”本身就是一个具有“深度”属性的过程。

那怎么办?我们可以维护一个队列,首先,将起点入队。在队列未空的情况下,将队首元素弹出队列并访问队首元素,同时将没有入队过且与队首元素有边相连的元素入队,实现一次“扩散”。重复这个过程,直到队列空。这时,所有的元素都已经被访问,遍历结束。

和深度优先遍历一样,我们同样需要一个数组visited,记录节点有没有入队过。

bool visited[NodesSize];
memset(visited,0,NodesSize);

我们用这个图作为例子,研究广度优先的过程:我们不妨从A开始遍历。

首先,A入队,队列为{ A }

然后,A出队,B,D,E三个节点与A相连且没有入队过。将它们依次入队。队列变为{ E D B }

然后,队首元素B出队,A,D两个节点和B相连但都已经入过队。队列变为{E D}

然后,队首元素D出队,ABC三个节点与D相连,只有C没有入过队。入队C,队列变为{ C E }

接着,队首元素E出队,与其相连的节点A与E相连,但A已经入过队。队列变为{ C }

最后,C出队,与其相连节点D已经入过队,队列空,遍历结束。

至此,我们已经掌握广度优先思想的关键。

下面是BFS的C代码实现

#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<string.h>
typedef struct LNode* List;
struct LNode{
	int element;
	List next;
}LNode;//链表的结构体定义 :两个域:表的节点代号、指针域 
typedef struct Queue* PtrQ;
struct Queue{
	List front;
	List rear;
}Queue;//队列的结构体定义 
typedef struct GraphNode* Graph;
struct GraphNode{
	List* Adjacency_List;
	int* data;
	int Capacity;
}GraphNode;//图的结构体定义:三个域:邻接表、数据、节点个数(图的容量) 
void Add_to_end(List PtrL,int value){
	if(PtrL==NULL) return;
	if(PtrL->next==NULL){
        PtrL->next=(List)malloc(sizeof(LNode));
        PtrL->next->element=value;
        PtrL->next->next=NULL;
    }
    else{
        List tmp=PtrL->next;
        List newl=(List)malloc(sizeof(LNode));
        newl->element=value;
        newl->next=tmp;
        PtrL->next=newl;
    }
}//链表函数:在链表头插入元素
Graph NewGraph(int NodesSize){
	if(NodesSize<=0) return NULL;
	Graph G=(Graph)malloc(sizeof(GraphNode));
	G->Adjacency_List=(List*)malloc(sizeof(List)*NodesSize);
	for(int i=0;i<NodesSize;i++){
		G->Adjacency_List[i]=(List)malloc(sizeof(LNode));
		G->Adjacency_List[i]->element=-1;
		G->Adjacency_List[i]->next=NULL;
	}
	G->Capacity=NodesSize;
	G->data=(int*)malloc(sizeof(int)*NodesSize);
	for(int i=0;i<NodesSize;i++){
		scanf("%d",&G->data[i]);
	}
	int relationshipSize;
	scanf("%d",&relationshipSize);
	if(relationshipSize<0) return G;
	for(int i=0;i<relationshipSize;i++){
		int Node1,Node2;
		scanf("%d %d",&Node1,&Node2);
		if(Node1>=0&&Node2>=0&&Node1<NodesSize&&Node2<NodesSize&&Node1!=Node2){
			Add_to_end(G->Adjacency_List[Node1],Node2);
			Add_to_end(G->Adjacency_List[Node2],Node1);
		}
	}
    return G;
}//新建一个表,输入所有关系
PtrQ NewQueue(){
	PtrQ Q=(PtrQ)malloc(sizeof(Queue));
	Q->front=Q->rear=NULL;
	return Q;
}//新建队列
void EnQueue(PtrQ Q,int value,bool* visited){
	if(Q==NULL) return;
	visited[value]=true;
	if(Q->front==NULL){
		Q->front=(List)malloc(sizeof(LNode));
		Q->front->element=value;
		Q->front->next=NULL;
		Q->rear=Q->front;
		return;	
	}
	Q->rear->next=(List)malloc(sizeof(LNode));
	Q->rear->next->element=value;
	Q->rear->next->next=NULL;
	Q->rear=Q->rear->next;
	return;
}//入队
void DeQueue(PtrQ Q,Graph G,bool* visited){
	if(Q->front==NULL) return;
	int tmp=Q->front->element;
	printf("%d ",G->data[tmp]);
	if(Q->front==Q->rear){
		free(Q->front);
		Q->front=NULL;
        Q->rear=NULL;
	}
	else{
		List s=Q->front;
		Q->front=s->next;
		free(s);
		s=NULL;
	}//将队首元素弹出
	List L=G->Adjacency_List[tmp]->next;
	while(L!=NULL){
        if(visited[L->element]==false)
		EnQueue(Q,L->element,visited);
		L=L->next;
	}//与节点相连且没有入过队的节点入队
}
void BFS(Graph G,bool* visited,PtrQ Q,int place){
	if(place<0&&place>=G->Capacity) return;
	visited[place]=true;
	EnQueue(Q,place,visited);
    while(Q->front!=NULL){
	DeQueue(Q,G,visited);
    }
}//BFS广度优先
int main(){
	int NodesSize;
	scanf("%d",&NodesSize); 
	if(NodesSize<=0) return -0x7fffffff;//非法输入直接返回
	Graph G=NewGraph(NodesSize);
	PtrQ Q=NewQueue();//维护队列
	bool visited[NodesSize];
	memset(visited,0,NodesSize);//建立bool visited数组
	BFS(G,visited,Q,0);
    //记得在此还回申请的内存。
}

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是使用邻接表实现的无向广度优先遍历C语言代码: ```c #include <stdio.h> #include <stdlib.h> #define MAX_VERTEX_NUM 100 // 最大顶点数 // 邻接表中边结点的定义 typedef struct ArcNode{ int adjvex; // 该边指向的顶点位置 struct ArcNode *next; // 指向下一条边的指针 }ArcNode; // 邻接表中顶点结点的定义 typedef struct VNode{ int data; // 顶点值 ArcNode *firstarc; // 指向第一条依附该顶点的边的指针 }VNode, AdjList[MAX_VERTEX_NUM]; // 邻接表的定义 typedef struct{ AdjList vertices; // 邻接表中的顶点数组 int vexnum; // 顶点数 int arcnum; // 边数 }ALGraph; // 初始化邻接表 void InitGraph(ALGraph *G){ int i; printf("请输入顶点数和边数:\n"); scanf("%d %d", &G->vexnum, &G->arcnum); for(i = 0; i < G->vexnum; ++i){ printf("请输入第%d个顶点的值:", i+1); scanf("%d", &G->vertices[i].data); G->vertices[i].firstarc = NULL; } for(i = 0; i < G->arcnum; ++i){ int v1, v2; printf("请输入第%d条边的两个端点:", i+1); scanf("%d %d", &v1, &v2); // 添加v1->v2这条边 ArcNode *p = (ArcNode*)malloc(sizeof(ArcNode)); p->adjvex = v2-1; p->next = G->vertices[v1-1].firstarc; G->vertices[v1-1].firstarc = p; // 添加v2->v1这条边 p = (ArcNode*)malloc(sizeof(ArcNode)); p->adjvex = v1-1; p->next = G->vertices[v2-1].firstarc; G->vertices[v2-1].firstarc = p; } } // 广度优先遍历 void BFS(ALGraph *G, int v){ int visited[MAX_VERTEX_NUM] = {0}; // 标记数组,用于记录顶点是否被访问过 int queue[MAX_VERTEX_NUM], front = 0, rear = 0; // 辅助队列 printf("%d ", G->vertices[v].data); visited[v] = 1; queue[rear++] = v; while(front < rear){ int w = queue[front++]; ArcNode *p = G->vertices[w].firstarc; while(p != NULL){ int u = p->adjvex; if(!visited[u]){ printf("%d ", G->vertices[u].data); visited[u] = 1; queue[rear++] = u; } p = p->next; } } } int main(){ ALGraph G; InitGraph(&G); printf("广度优先遍历:"); BFS(&G, 0); return 0; } ``` 这里我们使用了一个辅助队列来实现广度优先遍历。从起始顶点开始,先将该顶点标记为已访问,然后将其加入队列中。接下来,从队首取出一个顶点,依次访问它的所有未被访问过的邻接点,并将这些邻接点标记为已访问并加入队列中。直到队列为空时,遍历结束。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值