1. 数组实现队列:
class ArrayQueue {
private:
int* arr; // 存储队列元素的数组
int capacity; // 队列容量
int front; // 队头指针
int rear; // 队尾指针
int size; // 当前队列大小
public:
ArrayQueue(int cap) {
capacity = cap;
arr = new int[capacity];
front = 0;
rear = -1;
size = 0;
}
bool isEmpty() {
return size == 0;
}
bool isFull() {
return size == capacity;
}
void enqueue(int item) {
if (isFull()) return;
rear = (rear + 1) % capacity;
arr[rear] = item;
size++;
}
int dequeue() {
if (isEmpty()) return -1;
int item = arr[front];
front = (front + 1) % capacity;
size--;
return item;
}
int getFront() {
if (isEmpty()) return -1;
return arr[front];
}
};
2. 链表实现队列:
struct Node {
int data;
Node* next;
Node(int d) : data(d), next(nullptr) {}
};
class LinkedListQueue {
private:
Node* front; // 队头指针
Node* rear; // 队尾指针
int size; // 队列大小
public:
LinkedListQueue() {
front = rear = nullptr;
size = 0;
}
bool isEmpty() {
return front == nullptr;
}
void enqueue(int item) {
Node* temp = new Node(item);
if (isEmpty()) {
front = rear = temp;
} else {
rear->next = temp;
rear = temp;
}
size++;
}
int dequeue() {
if (isEmpty()) return -1;
Node* temp = front;
int item = temp->data;
front = front->next;
if (front == nullptr) rear = nullptr;
delete temp;
size--;
return item;
}
int getFront() {
if (isEmpty()) return -1;
return front->data;
}
};
循环队列的概念:
循环队列的实现:
class CircularQueue {
private:
int* arr;
int capacity;
int front;
int rear;
int size;
public:
CircularQueue(int cap) {
capacity = cap;
arr = new int[capacity];
front = rear = -1;
size = 0;
}
bool isEmpty() {
return front == -1;
}
bool isFull() {
return (rear + 1) % capacity == front;
}
void enqueue(int item) {
if (isFull()) return;
if (isEmpty())
front = 0;
rear = (rear + 1) % capacity;
arr[rear] = item;
size++;
}
int dequeue() {
if (isEmpty()) return -1;
int item = arr[front];
if (front == rear) {
// 队列中只有一个元素
front = rear = -1;
} else {
front = (front + 1) % capacity;
}
size--;
return item;
}
int getFront() {
if (isEmpty()) return -1;
return arr[front];
}
};
BFS算法步骤:
BFS图示:
A
/ \
B C
/ \ \
D E F
BFS遍历顺序:A → B → C → D → E → F
BFS执行过程示例:
BFS特点:
BFS适用场景:
DFS特点:
DFS适用场景:
BFS vs DFS比较:
| 特性 | BFS | DFS |
|---|---|---|
| 实现方式 | 队列 | 栈/递归 |
| 搜索策略 | 逐层扩展 | 尽可能深入 |
| 空间复杂度 | O(b^d) | O(d) |
| 最短路径 | 保证 | 不保证 |
| 完整性 | 完备 | 不完备(可能陷入无限深度) |
| 适用场景 | 最短路径、层次分析 | 所有路径、回溯问题 |
BFS的时间复杂度:
空间复杂度分析:
图的表示方式
图的结构特征
优化方法
迷宫最短路
二叉树层序遍历
无权图最短路
BFS基本框架:
// 图的BFS模板
void bfs(vector<vector<int>>& graph, int start) {
int n = graph.size();
vector<bool> visited(n, false);
queue<int> q;
// 起点入队并标记
q.push(start);
visited[start] = true;
while (!q.empty()) {
int curr = q.front();
q.pop();
// 处理当前节点
process(curr);
// 遍历邻居
for (int next : graph[curr]) {
if (!visited[next]) {
q.push(next);
visited[next] = true;
}
}
}
}
带层次信息的BFS:
// 记录层次的BFS模板
void bfsWithLevel(vector<vector<int>>& graph,
int start) {
int n = graph.size();
vector<bool> visited(n, false);
vector<int> level(n, -1);
queue<int> q;
q.push(start);
visited[start] = true;
level[start] = 0;
while (!q.empty()) {
int curr = q.front();
q.pop();
for (int next : graph[curr]) {
if (!visited[next]) {
q.push(next);
visited[next] = true;
level[next] = level[curr] + 1;
}
}
}
}
// 创建图(邻接表表示)
vector<vector<int>> graph = {
{1, 2}, // 0号顶点的邻居
{0, 3, 4}, // 1号顶点的邻居
{0, 5}, // 2号顶点的邻居
{1}, // 3号顶点的邻居
{1}, // 4号顶点的邻居
{2} // 5号顶点的邻居
};
// 从0号顶点开始BFS
bfs(graph, 0);
访问标记的重要性:
常见的访问标记方式:
vector<bool> visited(n, false);
// 标记访问
visited[node] = true;
// 检查是否访问过
if (!visited[neighbor]) { /* ... */ }
集合:
set<int> visited;
// 标记访问
visited.insert(node);
// 检查是否访问过
if (visited.find(neighbor) == visited.end()) { /* ... */ }
哈希表:
unordered_set<int> visited;
// 标记访问
visited.insert(node);
// 检查是否访问过
if (!visited.count(neighbor)) { /* ... */ }
二维网格:
vector<vector<bool>> visited(rows, vector<bool>(cols, false));
// 标记访问
visited[x][y] = true;
// 检查是否访问过
if (!visited[nx][ny]) { /* ... */ }
记录前驱节点:
vector<int> bfsWithPath(vector<vector<int>>& graph,
int start, int end) {
int n = graph.size();
vector<bool> visited(n, false);
vector<int> parent(n, -1); // 记录前驱节点
queue<int> q;
q.push(start);
visited[start] = true;
while (!q.empty()) {
int curr = q.front();
q.pop();
if (curr == end) break; // 找到终点
for (int next : graph[curr]) {
if (!visited[next]) {
q.push(next);
visited[next] = true;
parent[next] = curr; // 记录前驱
}
}
}
// 重建路径
vector<int> path;
if (parent[end] == -1) return path; // 无路径
for (int at = end; at != -1; at = parent[at]) {
path.push_back(at);
}
reverse(path.begin(), path.end());
return path;
}
记录完整状态路径:
struct State {
int x, y;
vector<pair<int, int>> path; // 记录路径
State(int _x, int _y) : x(_x), y(_y) {
path.push_back({_x, _y});
}
State(int _x, int _y, const vector<pair<int, int>>& _path)
: x(_x), y(_y), path(_path) {
path.push_back({_x, _y});
}
};
vector<pair<int, int>> findPath(vector<vector<int>>& grid,
pair<int, int> start,
pair<int, int> end) {
int rows = grid.size(), cols = grid[0].size();
vector<vector<bool>> visited(rows, vector<bool>(cols, false));
queue<State> q;
q.push(State(start.first, start.second));
visited[start.first][start.second] = true;
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
while (!q.empty()) {
State curr = q.front();
q.pop();
if (curr.x == end.first && curr.y == end.second) {
return curr.path; // 返回完整路径
}
for (int i = 0; i < 4; i++) {
int nx = curr.x + dx[i];
int ny = curr.y + dy[i];
if (nx >= 0 && nx < rows && ny >= 0 && ny < cols &&
grid[nx][ny] == 0 && !visited[nx][ny]) {
visited[nx][ny] = true;
q.push(State(nx, ny, curr.path));
}
}
}
return {}; // 无路径
}
int bidirectionalBFS(vector<vector<int>>& graph, int start, int end) {
int n = graph.size();
if (start == end) return 0;
// 从起点和终点开始的两个队列
queue<int> q1, q2;
vector<int> dist1(n, -1), dist2(n, -1);
q1.push(start);
q2.push(end);
dist1[start] = 0;
dist2[end] = 0;
while (!q1.empty() && !q2.empty()) {
// 从较小的队列扩展
if (q1.size() > q2.size()) {
swap(q1, q2);
swap(dist1, dist2);
}
int curr = q1.front();
q1.pop();
for (int next : graph[curr]) {
if (dist1[next] == -1) {
dist1[next] = dist1[curr] + 1;
q1.push(next);
// 检查是否与另一方向的搜索相遇
if (dist2[next] != -1) {
return dist1[next] + dist2[next];
}
}
}
}
return -1; // 无路径
}
struct Node {
int id;
int cost; // 从起点到当前的实际代价
int heuristic; // 从当前到终点的估计代价
bool operator>(const Node& other) const {
return cost + heuristic > other.cost + other.heuristic;
}
};
int aStarSearch(vector<vector<pair<int, int>>>& graph,
int start, int end,
function<int(int, int)> h) {
int n = graph.size();
vector<int> cost(n, INT_MAX);
priority_queue<Node, vector<Node>, greater<Node>> pq;
pq.push({start, 0, h(start, end)});
cost[start] = 0;
while (!pq.empty()) {
Node curr = pq.top();
pq.pop();
if (curr.id == end) return curr.cost;
if (curr.cost > cost[curr.id]) continue;
for (auto& [next, weight] : graph[curr.id]) {
int newCost = curr.cost + weight;
if (newCost < cost[next]) {
cost[next] = newCost;
pq.push({next, newCost, h(next, end)});
}
}
}
return -1; // 无路径
}
剪枝技术:
记忆化搜索:
无权图最短路径:
// 计算从起点到所有点的最短距离
vector<int> bfsShortestPath(vector<vector<int>>& graph,
int start) {
int n = graph.size();
vector<int> distance(n, -1); // -1表示不可达
queue<int> q;
q.push(start);
distance[start] = 0;
while (!q.empty()) {
int curr = q.front();
q.pop();
for (int next : graph[curr]) {
if (distance[next] == -1) {
distance[next] = distance[curr] + 1;
q.push(next);
}
}
}
return distance;
}
迷宫最短路径:
// 迷宫最短路径
int shortestPathInMaze(vector<vector<int>>& maze,
pair<int, int> start,
pair<int, int> end) {
int rows = maze.size(), cols = maze[0].size();
vector<vector<int>> dist(rows, vector<int>(cols, -1));
queue<pair<int, int>> q;
q.push(start);
dist[start.first][start.second] = 0;
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
if (x == end.first && y == end.second) {
return dist[x][y];
}
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 0 && nx < rows && ny >= 0 && ny < cols &&
maze[nx][ny] == 0 && dist[nx][ny] == -1) {
dist[nx][ny] = dist[x][y] + 1;
q.push({nx, ny});
}
}
}
return -1; // 无法到达终点
}
二叉树层序遍历:
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
if (!root) return result;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int levelSize = q.size();
vector<int> currentLevel;
for (int i = 0; i < levelSize; i++) {
TreeNode* node = q.front();
q.pop();
currentLevel.push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
result.push_back(currentLevel);
}
return result;
}
图的层次遍历:
// 图的层次遍历
vector<vector<int>> levelOrderTraversal(
vector<vector<int>>& graph, int start) {
int n = graph.size();
vector<bool> visited(n, false);
vector<vector<int>> levels;
queue<int> q;
q.push(start);
visited[start] = true;
while (!q.empty()) {
int levelSize = q.size();
vector<int> currentLevel;
for (int i = 0; i < levelSize; i++) {
int curr = q.front();
q.pop();
currentLevel.push_back(curr);
for (int next : graph[curr]) {
if (!visited[next]) {
q.push(next);
visited[next] = true;
}
}
}
levels.push_back(currentLevel);
}
return levels;
}
应用示例:
连通分量计算:
// 计算无向图的连通分量
int countConnectedComponents(vector<vector<int>>& graph) {
int n = graph.size();
vector<bool> visited(n, false);
int count = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
count++;
// BFS遍历连通分量
queue<int> q;
q.push(i);
visited[i] = true;
while (!q.empty()) {
int curr = q.front();
q.pop();
for (int next : graph[curr]) {
if (!visited[next]) {
q.push(next);
visited[next] = true;
}
}
}
}
}
return count;
}
判断二分图:
// 判断是否为二分图
bool isBipartite(vector<vector<int>>& graph) {
int n = graph.size();
vector<int> color(n, -1); // -1表示未染色,0和1表示两种颜色
for (int start = 0; start < n; start++) {
if (color[start] != -1) continue; // 已经染色的节点跳过
queue<int> q;
q.push(start);
color[start] = 0; // 初始颜色
while (!q.empty()) {
int curr = q.front();
q.pop();
for (int next : graph[curr]) {
if (color[next] == -1) {
// 未染色,染成相反的颜色
color[next] = 1 - color[curr];
q.push(next);
} else if (color[next] == color[curr]) {
// 相邻节点颜色相同,不是二分图
return false;
}
}
}
}
return true;
}
寻找割点和桥:
应用场景:
状态空间搜索概念:
八数码问题:
struct PuzzleState {
vector<vector<int>> board;
int zeroRow, zeroCol; // 空格位置
int steps; // 从初始状态到当前状态的步数
string path; // 记录移动路径
// 生成状态的唯一标识
string getKey() const {
string key;
for (const auto& row : board) {
for (int val : row) {
key += to_string(val) + ",";
}
}
return key;
}
};
int solvePuzzle(vector<vector<int>> initial,
vector<vector<int>> target) {
// 方向数组:上、右、下、左
int dr[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
string dirs = "URDL"; // 对应方向的字符表示
PuzzleState start;
start.board = initial;
start.steps = 0;
start.path = "";
// 找到初始状态中的空格位置
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (initial[i][j] == 0) {
start.zeroRow = i;
start.zeroCol = j;
break;
}
}
}
queue<PuzzleState> q;
unordered_set<string> visited;
q.push(start);
visited.insert(start.getKey());
while (!q.empty()) {
PuzzleState curr = q.front();
q.pop();
// 检查是否达到目标状态
if (curr.board == target) {
return curr.steps;
}
// 尝试四个方向的移动
for (int i = 0; i < 4; i++) {
int newRow = curr.zeroRow + dr[i];
int newCol = curr.zeroCol + dy[i];
// 检查新位置是否有效
if (newRow >= 0 && newRow < 3 &&
newCol >= 0 && newCol < 3) {
// 创建新状态
PuzzleState next = curr;
// 交换空格和相邻数字
swap(next.board[curr.zeroRow][curr.zeroCol],
next.board[newRow][newCol]);
next.zeroRow = newRow;
next.zeroCol = newCol;
next.steps++;
next.path += dirs[i];
// 检查是否访问过该状态
string key = next.getKey();
if (visited.find(key) == visited.end()) {
visited.insert(key);
q.push(next);
}
}
}
}
return -1; // 无解
}
骑士巡游问题:
// 骑士在棋盘上从起点到终点的最少步数
int knightMinSteps(pair<int, int> start,
pair<int, int> end,
int n) {
// 骑士的8种移动方式
int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
vector<vector<bool>> visited(n, vector<bool>(n, false));
queue<pair<pair<int, int>, int>> q; // 位置和步数
q.push({start, 0});
visited[start.first][start.second] = true;
while (!q.empty()) {
auto [pos, steps] = q.front();
q.pop();
if (pos == end) return steps;
for (int i = 0; i < 8; i++) {
int nx = pos.first + dx[i];
int ny = pos.second + dy[i];
if (nx >= 0 && nx < n && ny >= 0 && ny < n &&
!visited[nx][ny]) {
visited[nx][ny] = true;
q.push({{nx, ny}, steps + 1});
}
}
}
return -1; // 无法到达
}
状态空间搜索的应用:
谜题求解:
游戏AI:
路径规划:
问题描述:
解题思路:
代码实现:
int shortestPath(vector<vector<int>>& maze,
pair<int, int> start,
pair<int, int> end) {
int rows = maze.size();
int cols = maze[0].size();
// 方向数组:上、右、下、左
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
// 初始化距离数组
vector<vector<int>> dist(rows, vector<int>(cols, -1));
queue<pair<int, int>> q;
// 起点入队
q.push(start);
dist[start.first][start.second] = 0;
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
// 到达终点
if (x == end.first && y == end.second) {
return dist[x][y];
}
// 尝试四个方向
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
// 检查是否有效且未访问
if (nx >= 0 && nx < rows && ny >= 0 && ny < cols &&
maze[nx][ny] == 0 && dist[nx][ny] == -1) {
dist[nx][ny] = dist[x][y] + 1;
q.push({nx, ny});
}
}
}
return -1; // 无法到达终点
}
进阶版本:
记录路径的实现:
vector<pair<int, int>> getPath(vector<vector<int>>& maze,
pair<int, int> start,
pair<int, int> end) {
int rows = maze.size();
int cols = maze[0].size();
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
vector<vector<int>> dist(rows, vector<int>(cols, -1));
vector<vector<pair<int, int>>> parent(rows,
vector<pair<int, int>>(cols, {-1, -1}));
queue<pair<int, int>> q;
q.push(start);
dist[start.first][start.second] = 0;
bool found = false;
while (!q.empty() && !found) {
auto [x, y] = q.front();
q.pop();
if (x == end.first && y == end.second) {
found = true;
break;
}
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 0 && nx < rows && ny >= 0 && ny < cols &&
maze[nx][ny] == 0 && dist[nx][ny] == -1) {
dist[nx][ny] = dist[x][y] + 1;
parent[nx][ny] = {x, y};
q.push({nx, ny});
}
}
}
// 重建路径
vector<pair<int, int>> path;
if (!found) return path;
pair<int, int> curr = end;
while (curr != start) {
path.push_back(curr);
curr = parent[curr.first][curr.second];
}
path.push_back(start);
reverse(path.begin(), path.end());
return path;
}
应用场景:
问题描述:
示例:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",返回长度5
解题思路:
代码实现:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
// 将wordList转换为unordered_set以便快速查找
unordered_set<string> wordSet(wordList.begin(), wordList.end());
// 检查endWord是否在字典中
if (wordSet.find(endWord) == wordSet.end()) {
return 0;
}
// BFS队列
queue<string> q;
q.push(beginWord);
// 记录转换次数
int level = 1;
while (!q.empty()) {
int size = q.size();
// 处理当前层的所有单词
for (int i = 0; i < size; i++) {
string currWord = q.front();
q.pop();
// 尝试修改currWord的每个位置
for (int j = 0; j < currWord.length(); j++) {
char originalChar = currWord[j];
// 尝试替换为a-z的每个字母
for (char c = 'a'; c <= 'z'; c++) {
if (c == originalChar) continue;
currWord[j] = c;
// 如果找到endWord
if (currWord == endWord) {
return level + 1;
}
// 如果新单词在字典中
if (wordSet.find(currWord) != wordSet.end()) {
q.push(currWord);
wordSet.erase(currWord); // 避免重复访问
}
}
// 恢复原始字符
currWord[j] = originalChar;
}
}
level++;
}
return 0; // 无法转换到endWord
}
优化方法:
相关变种:
问题描述:
BFS实现(Kahn算法):
代码实现:
vector<int> topologicalSort(vector<vector<int>>& graph) {
int n = graph.size();
vector<int> inDegree(n, 0);
// 计算入度
for (int u = 0; u < n; u++) {
for (int v : graph[u]) {
inDegree[v]++;
}
}
// 将入度为0的顶点加入队列
queue<int> q;
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
q.push(i);
}
}
vector<int> result;
while (!q.empty()) {
int u = q.front();
q.pop();
result.push_back(u);
// 减少邻居的入度
for (int v : graph[u]) {
if (--inDegree[v] == 0) {
q.push(v);
}
}
}
// 检查是否存在环
if (result.size() != n) {
return {}; // 存在环,无拓扑排序
}
return result;
}
应用场景:
课程安排:
任务调度:
构建系统:
数据处理流水线:
课程表问题示例:
bool canFinish(int numCourses,
vector<vector<int>>& prerequisites) {
// 构建图(邻接表)
vector<vector<int>> graph(numCourses);
vector<int> inDegree(numCourses, 0);
for (auto& pre : prerequisites) {
int course = pre[0];
int prereq = pre[1];
graph[prereq].push_back(course);
inDegree[course]++;
}
// 拓扑排序
queue<int> q;
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) {
q.push(i);
}
}
int count = 0;
while (!q.empty()) {
int curr = q.front();
q.pop();
count++;
for (int next : graph[curr]) {
if (--inDegree[next] == 0) {
q.push(next);
}
}
}
return count == numCourses;
}
注意事项:
HDU 1241. Oil Deposits
int countOilDeposits(vector<vector<char>>& grid) {
if (grid.empty()) return 0;
int rows = grid.size(), cols = grid[0].size();
int count = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == '@') {
count++;
bfs(grid, i, j);
}
}
}
return count;
}
POJ 3278. Catch That Cow
CSES 1193. Labyrinth
POJ 3984. 迷宫问题
CSES 1192. Counting Rooms
CSP-J 2022. 种植水稻
USACO Gold. Cow Navigation
POJ 1324. Holedox Moving
CSES 1707. Graph Girth
循序渐进:
优化技巧:
代码实现:
常见陷阱:
提高建议:
BFS的本质:
实现关键点:
优化方法:
常见应用场景:
算法优化:
实际应用:
结合其他算法:
性能调优:
基础巩固:
实践应用:
持续学习:
注意事项:
这个讲义使用了Awesome Marp主题的蓝色风格。 内容针对竞赛选手设计,注重基础概念和实际应用。