🚍 Bài giảng: Cấu trúc dữ liệu Queue (Hàng đợi)

Queue là cấu trúc dữ liệu tuyến tính lưu trữ phần tử theo nguyên tắc FIFO – First In, First Out (vào trước, ra trước). Rất nhiều ứng dụng trong đời sống hoạt động giống “hàng chờ”: mua vé, xếp hàng ATM, xử lý tiến trình trong máy tính.

I. Giới thiệu & Ví dụ thực tế

Hãy tưởng tượng bạn đang xếp hàng mua trà sữa 🍹. Người vào hàng trước sẽ được phục vụ trước. Người đến sau phải chờ đến lượt mình.

A
B
C
Front ⟶ A → B → C ⟶ Rear

Nguyên tắc FIFO – “vào trước ra trước”.

II. Lý thuyết cơ bản

  • Enqueue(x): thêm phần tử x vào cuối hàng (rear).
  • Dequeue(): loại bỏ phần tử ở đầu hàng (front).
  • Front(): xem phần tử đầu hàng.
  • Empty(): kiểm tra hàng có rỗng không.
  • Size(): số phần tử trong hàng.
#include <iostream>
#include <queue>
using namespace std;
int main() {
    queue<int> q;
    q.push(10); // enqueue
    q.push(20);
    cout << q.front() << "\n"; // 10
    q.pop();  // dequeue 10
    cout << q.size(); // 1
}

III. Trình mô phỏng Queue (hình ảnh + mô tả)

Mô phỏng dưới đây giúp bạn quan sát cách các phần tử đi vào **cuối hàng** và lấy ra **đầu hàng** theo nguyên tắc FIFO – First In, First Out.

🖱 Thao tác

💡 Nhập giá trị và chọn thao tác để quan sát Queue.

💻 Code đang thực thi

queue<int> q; // tạo hàng rỗng
int x; cin >> x; // đọc giá trị
q.push(x); // enqueue – thêm vào cuối
if(!q.empty()) // kiểm tra rỗng
cout << q.front(); // xem phần tử đầu
if(!q.empty()) // kiểm tra rỗng
q.pop(); // dequeue – xoá đầu hàng
cout << q.size(); // kích thước hàng

📊 Kết quả Queue

size=0

IV. Khi nào dùng Queue

Nhóm bài toánMô tảVí dụLý do dùng Queue
1. Xử lý luồng dữ liệu Các sự kiện đến và được xử lý theo thứ tự. Máy in, lập lịch tiến trình CPU, xử lý gói mạng. Giúp đảm bảo “đến trước – phục vụ trước”.
2. BFS – duyệt đồ thị theo tầng Thuật toán duyệt theo chiều rộng (Breadth-First Search). Duyệt cây, tìm đường đi ngắn nhất trong đồ thị vô hướng. Queue lưu các nút chờ xử lý để mở rộng dần từng lớp.
3. Giải thuật mô phỏng hàng chờ Mô phỏng người xếp hàng hoặc tiến trình trong hệ thống. ATM, quầy vé, điều phối tác vụ. Thể hiện đúng bản chất hàng đợi FIFO.
4. Bộ nhớ đệm (Buffer) Dữ liệu đến nhanh hơn tốc độ xử lý. Queue lưu tạm gói dữ liệu – ví dụ network buffer, pipeline. Đảm bảo dữ liệu không mất mà vẫn đúng thứ tự.

V. Bài tập luyện tập

Queue không chỉ dùng để mô phỏng hàng chờ, mà còn là công cụ cực kỳ mạnh trong thuật toán duyệt đồ thị (BFS) và các bài toán "loang" (lan truyền theo lớp). Dưới đây là hệ thống bài tập chia theo cấp độ: từ cơ bản → ứng dụng → thuật toán loang nâng cao.

📘 Egg – Cơ bản

1️⃣ Bài 1: Mô phỏng hàng chờ

Đề: Có n thao tác “E x” (enqueue x) và “D” (dequeue). Sau khi thực hiện, in các phần tử còn lại trong queue.

Ý tưởng: Dùng queue<int>, đọc từng thao tác, thực hiện tương ứng.

#include <bits/stdc++.h>
using namespace std;
int main(){
  int n; cin>>n; queue<int> q;
  while(n--){
    char c; cin>>c;
    if(c=='E'){int x;cin>>x;q.push(x);}
    else if(c=='D' && !q.empty()) q.pop();
  }
  while(!q.empty()){cout<<q.front()<<' ';q.pop();}
}

Ví dụ:
Input: 5
E 3
E 5
D
E 2
E 1
Output: 5 2 1

📗 Chicken – Ứng dụng Queue trong đồ thị

2️⃣ Bài 2: Duyệt đồ thị theo tầng (BFS cơ bản)

Đề: Cho đồ thị vô hướng n đỉnh m cạnh. In thứ tự duyệt BFS bắt đầu từ đỉnh 1.

Ý tưởng: Dùng queue lưu các đỉnh đang chờ duyệt; khi lấy ra thì duyệt toàn bộ đỉnh kề chưa thăm.

#include <bits/stdc++.h>
using namespace std;
int main(){
  int n,m;cin>>n>>m;
  vector<vector<int>> g(n+1);
  for(int i=0,u,v;i<m;i++){cin>>u>>v;g[u].push_back(v);g[v].push_back(u);}
  vector<int> vis(n+1);
  queue<int> q;
  q.push(1); vis[1]=1;
  while(!q.empty()){
    int u=q.front(); q.pop(); cout<<u<<' ';
    for(int v:g[u]) if(!vis[v]){ vis[v]=1; q.push(v); }
  }
}

Kết quả: Thứ tự duyệt theo tầng (1 → các hàng xóm → hàng xóm của hàng xóm...)

3️⃣ Bài 3: Tìm đường đi ngắn nhất trong mê cung (thuật toán loang)

Đề: Cho ma trận n x m gồm các ô 0 (đi được) và 1 (tường). Tìm khoảng cách ngắn nhất từ ô (1,1) đến (n,m), di chuyển 4 hướng.

Ý tưởng: Dùng thuật toán loang (BFS): bắt đầu từ (1,1), mỗi bước lan sang 4 ô lân cận hợp lệ, lưu khoảng cách bằng queue.

#include <bits/stdc++.h>
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<string> a(n);
  for(int i=0;i<n;i++) cin>>a[i];
  vector<vector<int>> d(n, vector<int>(m,-1));
  queue<pair<int,int>> q;
  if(a[0][0]=='0'){ q.push({0,0}); d[0][0]=0; }
  while(!q.empty()){
    auto [x,y]=q.front(); q.pop();
    for(int k=0;k<4;k++){
      int nx=x+dx[k], ny=y+dy[k];
      if(nx<0||ny<0||nx>=n||ny>=m) continue;
      if(a[nx][ny]=='1'||d[nx][ny]!=-1) continue;
      d[nx][ny]=d[x][y]+1;
      q.push({nx,ny});
    }
  }
  cout<<d[n-1][m-1];
}

Ví dụ:
Input:
4 4
0001
1100
0010
0000

Output: 6

Giải thích: BFS giúp tìm đường ngắn nhất vì mỗi ô được thăm lần đầu tiên bằng đường đi ngắn nhất.

4️⃣ Bài 4: Lan truyền virus (multi-source BFS)

Đề: Cho lưới n×m gồm các ô: 0 – chưa nhiễm, 1 – nhiễm virus ban đầu. Mỗi phút, virus lan sang 4 hướng. Tính thời gian lan đến toàn bộ ô có thể.

Ý tưởng: Dùng BFS đa nguồn: ban đầu enqueue tất cả ô có virus (1), sau đó lan đồng thời.

#include <bits/stdc++.h>
using namespace std;
int dx[4]={-1,1,0,0}, dy[4]={0,0,-1,1};
int main(){
  int n,m;cin>>n>>m;
  vector<vector<int>> a(n, vector<int>(m));
  queue<pair<int,int>> q;
  for(int i=0;i<n;i++) for(int j=0;j<m;j++){
    cin>>a[i][j];
    if(a[i][j]==1) q.push({i,j});
  }
  int t=-1;
  while(!q.empty()){
    int sz=q.size(); t++;
    while(sz--){
      auto [x,y]=q.front(); q.pop();
      for(int k=0;k<4;k++){
        int nx=x+dx[k], ny=y+dy[k];
        if(nx<0||ny<0||nx>=n||ny>=m) continue;
        if(a[nx][ny]!=0) continue;
        a[nx][ny]=1; q.push({nx,ny});
      }
    }
  }
  cout<<t;
}

Giải thích: Duyệt từng “lớp” lan truyền trong queue. Mỗi vòng while tương ứng một phút lan toàn mạng.

📙 Chicken Farmer – Nâng cao

5️⃣ Bài 5: Tìm khoảng cách ngắn nhất giữa nhiều nguồn và nhiều đích

Đề: Cho ma trận có các điểm xuất phát (S) và điểm đích (T). Tính khoảng cách ngắn nhất từ bất kỳ S đến bất kỳ T.

Ý tưởng: - Đưa tất cả điểm S vào queue ban đầu (multi-source BFS). - Lan loang đến khi gặp ô T đầu tiên → dừng lại.

#include <bits/stdc++.h>
using namespace std;
int dx[4]={-1,1,0,0}, dy[4]={0,0,-1,1};
int main(){
  int n,m;cin>>n>>m;
  vector<string> a(n); for(auto &s:a) cin>>s;
  queue<pair<int,int>> q; vector<vector<int>> d(n,vector<int>(m,-1));
  for(int i=0;i<n;i++) for(int j=0;j<m;j++) if(a[i][j]=='S'){q.push({i,j}); d[i][j]=0;}
  while(!q.empty()){
    auto [x,y]=q.front(); q.pop();
    if(a[x][y]=='T'){ cout<<d[x][y]; return 0; }
    for(int k=0;k<4;k++){
      int nx=x+dx[k], ny=y+dy[k];
      if(nx<0||ny<0||nx>=n||ny>=m) continue;
      if(a[nx][ny]=='#'||d[nx][ny]!=-1) continue;
      d[nx][ny]=d[x][y]+1; q.push({nx,ny});
    }
  }
  cout<<-1;
}

Ứng dụng: Các bài như “Tìm khoảng cách từ nhiều ngọn lửa đến người”, “Thoát khỏi mê cung”, “Lũ lụt lan dần”,... đều dùng dạng này.

VI. Tổng kết & Mở rộng

  • Queue phù hợp cho bài toán theo trình tự thời gian.
  • Deque (Double-ended Queue) mở rộng cho cả hai đầu – cực kỳ hữu ích cho các bài trượt cửa sổ.
  • Hãy thử tự cài đặt Queue bằng mảng/vòng tròn (circular queue) để hiểu cơ chế sâu hơn.

🚀 Queue – công cụ mạnh mẽ cho các thuật toán theo luồng và hàng chờ. Hiểu chắc FIFO sẽ giúp bạn nắm vững nền tảng của BFS, hệ thống và mô phỏng thực tế.