🚍 Queue – Hàng đợi FIFO
Queue xử lý phần tử theo nguyên tắc vào trước, ra trước. Từ mô phỏng hàng chờ, Queue trở thành cấu trúc trung tâm của BFS, tìm đường đi ngắn nhất trên đồ thị không trọng số, thuật toán loang và BFS đa nguồn.
🎯 Mục tiêu bài học
Cần hiểu
- Nguyên tắc FIFO.
- Các thao tác
push,front,pop,empty,size. - Vì sao BFS phải đánh dấu khi đưa vào Queue.
- Vì sao BFS tìm đường ngắn nhất trong đồ thị không trọng số.
- Cách mở rộng sang lưới và BFS đa nguồn.
Dấu hiệu nhận biết
- Xử lý theo thứ tự đến trước.
- Duyệt theo tầng hoặc theo khoảng cách.
- Lan truyền từng bước/phút.
- Đường đi ngắn nhất khi mọi cạnh có cùng trọng số.
- Nhiều nguồn lan đồng thời.
1. Đặt vấn đề
Có q người lần lượt đến quầy. Mỗi thao tác là:
ADD x: người x vào cuối hàng;SERVE: phục vụ người ở đầu hàng;FRONT: xem ai đang đứng đầu.
Cấu trúc cần bảo đảm người đến trước được phục vụ trước. Stack không phù hợp vì Stack lấy người đến sau nhất ra trước.
Cho đồ thị không trọng số. Hãy tìm số cạnh ít nhất từ đỉnh S đến mọi đỉnh khác.
Nếu duyệt theo thứ tự tùy ý, ta có thể tới một đỉnh bằng đường dài trước. Ta cần mở rộng các đỉnh theo từng lớp khoảng cách:
- Lớp 0: đỉnh S.
- Lớp 1: các đỉnh kề S.
- Lớp 2: các đỉnh chưa thăm kề lớp 1.
2. Queue cơ bản trong C++
Thao tác chính
q.push(x): thêm cuối.q.front(): xem đầu.q.back(): xem cuối.q.pop(): xóa đầu.q.empty(): kiểm tra rỗng.q.size(): số phần tử.
Độ phức tạp
push: O(1).front/back: O(1).pop: O(1).- Không hỗ trợ truy cập
q[i]. - Muốn duyệt toàn bộ thường phải pop hoặc sao chép Queue.
q.pop() không trả về phần tử bị xóa. Muốn lấy giá trị, phải đọc q.front() trước rồi mới q.pop().3. Mô phỏng Queue và highlight code
4. BFS trên đồ thị
Queue BFS
4.1. Quy trình chuẩn
- Đánh dấu nguồn đã thăm.
- Đưa nguồn vào Queue.
- Lặp: lấy đầu Queue, duyệt các đỉnh kề chưa thăm.
- Đánh dấu đỉnh kề ngay khi đưa vào Queue.
5. BFS trên lưới – thuật toán loang
Mỗi ô là một đỉnh; hai ô kề nhau tạo thành cạnh nếu được phép di chuyển. Mọi bước đi có cùng chi phí 1 nên BFS tìm được số bước ít nhất.
Queue ô
6. BFS đa nguồn
Đưa tất cả nguồn vào Queue ngay từ đầu với khoảng cách 0. BFS sẽ mô phỏng các nguồn lan đồng thời.
Virus/lửa
Tính thời gian lan đến mọi ô.
Khoảng cách gần nhất
Khoảng cách từ mỗi ô đến nguồn gần nhất.
Nhiều S – nhiều T
Tìm khoảng cách ngắn nhất từ bất kỳ S đến một T.
7. Phân tích thuật toán
| Bài toán | Thời gian | Bộ nhớ | Giải thích |
|---|---|---|---|
| q thao tác Queue | O(q) | O(q) | Mỗi thao tác O(1). |
| BFS đồ thị | O(V+E) | O(V) | Mỗi đỉnh và cạnh được xử lý hữu hạn lần. |
| BFS lưới n×m | O(nm) | O(nm) | Mỗi ô vào Queue tối đa một lần. |
| Multi-source BFS | O(V+E) | O(V) | Chỉ khác bước khởi tạo nhiều nguồn. |
8. Code mẫu
#include
using namespace std;
int main() {
queue q;
q.push(10);
q.push(20);
q.push(30);
cout << q.front() << '
';
cout << q.back() << '
';
if (!q.empty()) {
int value = q.front();
q.pop();
cout << "Removed: " << value << '
';
}
cout << q.size();
} #include
using namespace std;
int main() {
int n, m, source;
cin >> n >> m >> source;
vector> graph(n + 1);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
graph[u].push_back(v);
graph[v].push_back(u);
}
vector distance(n + 1, -1);
vector parent(n + 1, -1);
queue q;
distance[source] = 0;
q.push(source);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : graph[u]) {
if (distance[v] != -1) continue;
distance[v] = distance[u] + 1;
parent[v] = u;
q.push(v);
}
}
} #include
using namespace std;
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int main() {
int n, m;
cin >> n >> m;
vector grid(n);
for (string &row : grid) cin >> row;
vector> distance(
n,
vector(m, -1)
);
queue> q;
if (grid[0][0] != '#') {
distance[0][0] = 0;
q.push({0, 0});
}
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
for (int direction = 0; direction < 4; direction++) {
int nx = x + dx[direction];
int ny = y + dy[direction];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) {
continue;
}
if (grid[nx][ny] == '#') continue;
if (distance[nx][ny] != -1) continue;
distance[nx][ny] = distance[x][y] + 1;
q.push({nx, ny});
}
}
cout << distance[n - 1][m - 1];
} queue> q;
vector> distance(n, vector(m, -1));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 'S') {
distance[i][j] = 0;
q.push({i, j});
}
}
}
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
for (int direction = 0; direction < 4; direction++) {
int nx = x + dx[direction];
int ny = y + dy[direction];
if (!inside(nx, ny)) continue;
if (grid[nx][ny] == '#') continue;
if (distance[nx][ny] != -1) continue;
distance[nx][ny] = distance[x][y] + 1;
q.push({nx, ny});
}
} from collections import deque
def bfs(graph, source):
distance = [-1] * len(graph)
queue = deque([source])
distance[source] = 0
while queue:
vertex = queue.popleft()
for neighbor in graph[vertex]:
if distance[neighbor] != -1:
continue
distance[neighbor] = distance[vertex] + 1
queue.append(neighbor)
return distance9. Giải thích code
9.1. Vì sao phải đọc front trước pop?
pop() chỉ xóa phần tử, không trả về giá trị. Vì vậy mẫu chuẩn là:
9.2. Vì sao đánh dấu ngay khi push?
Ngay khi một đỉnh được phát hiện, ta đánh dấu để không đỉnh nào khác đưa nó vào Queue lần nữa.
9.3. Vì sao BFS cho khoảng cách ngắn nhất?
Queue xử lý các đỉnh theo khoảng cách tăng dần: lớp 0 trước lớp 1, lớp 1 trước lớp 2. Do đó lần đầu một đỉnh được phát hiện chính là bằng số cạnh ít nhất.
9.4. Vì sao khoảng cách ban đầu là -1?
-1 vừa biểu diễn chưa thăm, vừa tránh cần một mảng visited riêng.
9.5. Vì sao BFS lưới vẫn là BFS đồ thị?
Mỗi ô là một đỉnh; các phép di chuyển hợp lệ tạo thành cạnh. Ta chỉ không xây danh sách cạnh tường minh mà sinh hàng xóm trực tiếp bằng mảng hướng.
9.6. Multi-source BFS khác gì BFS thường?
Chỉ khác bước khởi tạo: đưa tất cả nguồn vào Queue với khoảng cách 0. Phần vòng lặp BFS giữ nguyên.
10. Lỗi thường gặp
- Gọi
front()hoặcpop()khi Queue rỗng. - Tưởng
pop()trả về phần tử. - Đánh dấu visited khi pop thay vì khi push.
- Quên gán khoảng cách cho nguồn bằng 0.
- Quên kiểm tra biên khi BFS trên lưới.
- Không kiểm tra tường hoặc ô đã thăm.
- Dùng BFS thường cho đồ thị có trọng số khác nhau.
- Multi-source BFS nhưng chạy BFS riêng từ từng nguồn, gây chậm.
- Muốn in đường đi nhưng không lưu mảng parent.
11. Quiz và bài tập
📘 Cơ bản
- Mô phỏng q thao tác Queue.
- In phần tử đầu sau mỗi thao tác.
- Tự cài Queue bằng mảng vòng.
📗 Trung bình
- BFS thứ tự duyệt.
- Khoảng cách từ một nguồn.
- Khôi phục đường đi bằng parent.
📙 Nâng cao
- BFS mê cung.
- Multi-source BFS.
- BFS trạng thái.
🐉 HSG
- 0–1 BFS.
- Bidirectional BFS.
- BFS trên không gian trạng thái nén.
Bài thực hành 1 – Hàng chờ
Xử lý các lệnh E x, D, F. Với thao tác không hợp lệ, in EMPTY.
Bài thực hành 2 – Mê cung
Tìm số bước ít nhất từ S đến T trên lưới có tường.
Bài thực hành 3 – Virus
Nhiều nguồn virus lan đồng thời. Tìm thời gian phủ toàn bộ ô có thể đi.
💳 Quét mã ủng hộ tuỳ tâm nhé!