C语言实现图的广度优先搜索
简介
图是一种复杂的数据结构,在计算机科学和许多其他领域都有广泛应用。广度优先搜索(Breadth-First Search,BFS)是一种用于遍历或搜索图的算法。它从给定的起始顶点开始,逐层地探索图中的顶点,直到找到目标顶点或遍历完整个图。在 C 语言中实现图的广度优先搜索,需要我们结合图的存储结构和 BFS 算法的逻辑,本文将详细介绍相关内容。
目录
- 基础概念
- 图的存储结构
- 广度优先搜索算法原理
- 使用方法
- 初始化图
- 实现广度优先搜索
- 常见实践
- 最短路径问题
- 连通分量检测
- 最佳实践
- 优化内存使用
- 提高算法效率
- 代码示例
- 小结
- 参考资料
基础概念
图的存储结构
在 C 语言中,常见的图存储结构有邻接矩阵和邻接表。
- 邻接矩阵:是一个二维数组
adjMatrix[n][n],其中n是图中顶点的数量。如果顶点i和顶点j之间有边,则adjMatrix[i][j] = 1(对于无向图,adjMatrix[j][i] = 1也成立),否则为0。邻接矩阵的优点是实现简单,缺点是对于稀疏图会浪费大量内存。 - 邻接表:由一个数组和多个链表组成。数组的每个元素存储一个顶点,每个顶点对应一个链表,链表中存储与该顶点相邻的顶点。邻接表适合存储稀疏图,节省内存。
广度优先搜索算法原理
BFS 从起始顶点开始,将其标记为已访问,并将其加入队列。然后,不断从队列中取出顶点,访问其所有未访问的邻接顶点,将这些邻接顶点标记为已访问并加入队列,直到队列为空。这样可以保证按照层次依次访问图中的顶点。
使用方法
初始化图
以邻接表为例,定义图的结构体和初始化函数。
#include <stdio.h>
#include <stdlib.h>
// 定义邻接表节点结构体
typedef struct AdjListNode {
int dest;
struct AdjListNode* next;
} AdjListNode;
// 定义邻接表结构体
typedef struct AdjList {
AdjListNode* head;
} AdjList;
// 定义图结构体
typedef struct Graph {
int V;
AdjList* array;
} Graph;
// 创建新的邻接表节点
AdjListNode* createAdjListNode(int dest) {
AdjListNode* newNode = (AdjListNode*)malloc(sizeof(AdjListNode));
newNode->dest = dest;
newNode->next = NULL;
return newNode;
}
// 创建图
Graph* createGraph(int V) {
Graph* graph = (Graph*)malloc(sizeof(Graph));
graph->V = V;
graph->array = (AdjList*)malloc(V * sizeof(AdjList));
for (int i = 0; i < V; ++i) {
graph->array[i].head = NULL;
}
return graph;
}
// 添加边
void addEdge(Graph* graph, int src, int dest) {
AdjListNode* newNode = createAdjListNode(dest);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;
// 对于无向图,还需要添加反向边
newNode = createAdjListNode(src);
newNode->next = graph->array[dest].head;
graph->array[dest].head = newNode;
}
实现广度优先搜索
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
// 队列节点结构体
typedef struct QueueNode {
int data;
struct QueueNode* next;
} QueueNode;
// 队列结构体
typedef struct Queue {
QueueNode* front;
QueueNode* rear;
} Queue;
// 创建新的队列节点
QueueNode* createQueueNode(int data) {
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 创建队列
Queue* createQueue() {
Queue* queue = (Queue*)malloc(sizeof(Queue));
queue->front = queue->rear = NULL;
return queue;
}
// 入队
void enqueue(Queue* queue, int data) {
QueueNode* newNode = createQueueNode(data);
if (queue->rear == NULL) {
queue->front = queue->rear = newNode;
return;
}
queue->rear->next = newNode;
queue->rear = newNode;
}
// 出队
int dequeue(Queue* queue) {
if (queue->front == NULL)
return -1;
QueueNode* temp = queue->front;
int data = temp->data;
queue->front = queue->front->next;
if (queue->front == NULL)
queue->rear = NULL;
free(temp);
return data;
}
// 检查队列是否为空
bool isQueueEmpty(Queue* queue) {
return queue->front == NULL;
}
// 广度优先搜索
void BFS(Graph* graph, int start) {
bool* visited = (bool*)malloc(graph->V * sizeof(bool));
for (int i = 0; i < graph->V; ++i) {
visited[i] = false;
}
Queue* queue = createQueue();
visited[start] = true;
enqueue(queue, start);
while (!isQueueEmpty(queue)) {
int currentVertex = dequeue(queue);
printf("%d ", currentVertex);
AdjListNode* adjNode = graph->array[currentVertex].head;
while (adjNode!= NULL) {
int adjVertex = adjNode->dest;
if (!visited[adjVertex]) {
visited[adjVertex] = true;
enqueue(queue, adjVertex);
}
adjNode = adjNode->next;
}
}
free(visited);
free(queue);
}
常见实践
最短路径问题
在无权图中,BFS 可以用来计算从起始顶点到其他顶点的最短路径。从起始顶点开始进行 BFS,记录每个顶点被访问的层次,即可得到最短路径长度。
连通分量检测
通过对图中的每个未访问顶点进行 BFS,可以检测图中的连通分量。每次从一个未访问顶点开始 BFS,能够遍历到的所有顶点构成一个连通分量。
最佳实践
优化内存使用
在实现图的存储和 BFS 过程中,合理使用内存。例如,对于稀疏图使用邻接表存储结构,避免使用邻接矩阵造成的内存浪费。同时,及时释放不再使用的内存,如在 BFS 结束后释放访问标记数组和队列。
提高算法效率
可以使用位运算来优化访问标记数组的操作,提高效率。另外,对于大规模图,可以考虑并行化 BFS 算法,利用多核处理器的优势。
代码示例
完整的代码示例如下:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 定义邻接表节点结构体
typedef struct AdjListNode {
int dest;
struct AdjListNode* next;
} AdjListNode;
// 定义邻接表结构体
typedef struct AdjList {
AdjListNode* head;
} AdjList;
// 定义图结构体
typedef struct Graph {
int V;
AdjList* array;
} Graph;
// 创建新的邻接表节点
AdjListNode* createAdjListNode(int dest) {
AdjListNode* newNode = (AdjListNode*)malloc(sizeof(AdjListNode));
newNode->dest = dest;
newNode->next = NULL;
return newNode;
}
// 创建图
Graph* createGraph(int V) {
Graph* graph = (Graph*)malloc(sizeof(Graph));
graph->V = V;
graph->array = (AdjList*)malloc(V * sizeof(AdjList));
for (int i = 0; i < V; ++i) {
graph->array[i].head = NULL;
}
return graph;
}
// 添加边
void addEdge(Graph* graph, int src, int dest) {
AdjListNode* newNode = createAdjListNode(dest);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;
// 对于无向图,还需要添加反向边
newNode = createAdjListNode(src);
newNode->next = graph->array[dest].head;
graph->array[dest].head = newNode;
}
// 队列节点结构体
typedef struct QueueNode {
int data;
struct QueueNode* next;
} QueueNode;
// 队列结构体
typedef struct Queue {
QueueNode* front;
QueueNode* rear;
} Queue;
// 创建新的队列节点
QueueNode* createQueueNode(int data) {
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 创建队列
Queue* createQueue() {
Queue* queue = (Queue*)malloc(sizeof(Queue));
queue->front = queue->rear = NULL;
return queue;
}
// 入队
void enqueue(Queue* queue, int data) {
QueueNode* newNode = createQueueNode(data);
if (queue->rear == NULL) {
queue->front = queue->rear = newNode;
return;
}
queue->rear->next = newNode;
queue->rear = newNode;
}
// 出队
int dequeue(Queue* queue) {
if (queue->front == NULL)
return -1;
QueueNode* temp = queue->front;
int data = temp->data;
queue->front = queue->front->next;
if (queue->front == NULL)
queue->rear = NULL;
free(temp);
return data;
}
// 检查队列是否为空
bool isQueueEmpty(Queue* queue) {
return queue->front == NULL;
}
// 广度优先搜索
void BFS(Graph* graph, int start) {
bool* visited = (bool*)malloc(graph->V * sizeof(bool));
for (int i = 0; i < graph->V; ++i) {
visited[i] = false;
}
Queue* queue = createQueue();
visited[start] = true;
enqueue(queue, start);
while (!isQueueEmpty(queue)) {
int currentVertex = dequeue(queue);
printf("%d ", currentVertex);
AdjListNode* adjNode = graph->array[currentVertex].head;
while (adjNode!= NULL) {
int adjVertex = adjNode->dest;
if (!visited[adjVertex]) {
visited[adjVertex] = true;
enqueue(queue, adjVertex);
}
adjNode = adjNode->next;
}
}
free(visited);
free(queue);
}
int main() {
int V = 5;
Graph* graph = createGraph(V);
addEdge(graph, 0, 1);
addEdge(graph, 0, 2);
addEdge(graph, 1, 2);
addEdge(graph, 2, 0);
addEdge(graph, 2, 3);
addEdge(graph, 3, 3);
addEdge(graph, 4, 4);
printf("BFS starting from vertex 2:\n");
BFS(graph, 2);
return 0;
}
小结
本文详细介绍了使用 C 语言实现图的广度优先搜索的相关知识,包括图的存储结构、BFS 算法原理、使用方法、常见实践和最佳实践,并给出了完整的代码示例。掌握这些内容,读者可以在实际应用中灵活运用 BFS 算法解决各种与图相关的问题。
参考资料
- 《数据结构(C 语言版)》 - 严蔚敏
- 《算法导论》 - Thomas H. Cormen 等
希望这篇博客能帮助读者深入理解并高效使用 C 语言实现图的广度优先搜索。如果有任何疑问或建议,欢迎在评论区留言。