Priority Queue: Xếp hàng VIP cho dữ liệu của bạn!
C++

Priority Queue: Xếp hàng VIP cho dữ liệu của bạn!

Author

Admin System

@root

Ngày xuất bản

22 Mar, 2026

Lượt xem

1 Lượt

"priority_queue"

Chào các "coder nhí" tương lai của thế giới số! Anh là Creyt đây, và hôm nay chúng ta sẽ "bóc tách" một khái niệm siêu "cool ngầu" mà lại cực kỳ hữu ích trong lập trình: priority_queue.

1. Priority Queue là gì? "Xếp hàng VIP" cho dữ liệu của bạn!

Các em hình dung thế này: Khi mình đi xem concert của idol, có phải ai đến trước thì được vào trước không? Đúng, đó là hàng đợi (queue) thông thường. Nhưng nếu có hàng "VIP" hoặc "Fast Pass" thì sao? Dù bạn đến sau, nhưng vì có "ưu tiên" (priority) cao hơn, bạn sẽ được vào trước, đúng không? priority_queue trong C++ chính là cái "hàng VIP" đó!

Nói một cách hàn lâm hơn nhưng vẫn dễ hiểu, priority_queue là một container adapter (một kiểu gói ghém các container khác như vector hoặc deque) mà nó luôn đảm bảo rằng phần tử có độ ưu tiên cao nhất sẽ luôn nằm ở đầu hàng đợi.

Để làm gì? Đơn giản là để các em luôn có thể lấy ra cái "quan trọng nhất", cái "khẩn cấp nhất" hoặc cái "lớn nhất/nhỏ nhất" một cách nhanh chóng mà không cần phải lục tung cả đống dữ liệu lên.

2. Cách hoạt động: "Heap" là ông trùm!

Đằng sau cái vẻ "ưu tiên" kia, priority_queue thường được triển khai bằng một cấu trúc dữ liệu gọi là Heap (cụ thể hơn là Max-Heap theo mặc định trong C++). Heap là một cây nhị phân gần hoàn chỉnh có một "tính chất" đặc biệt: giá trị của mỗi nút luôn lớn hơn hoặc bằng giá trị của các nút con của nó. Nhờ vậy, cái phần tử "to nhất" (ưu tiên cao nhất) luôn nằm ở gốc cây, tức là ở "đầu" priority_queue.

Các thao tác cơ bản:

  • push(element): Thêm một phần tử vào hàng đợi. Nó sẽ tự động sắp xếp lại để đảm bảo phần tử có ưu tiên cao nhất vẫn ở đầu. (Độ phức tạp: O(log N))
  • pop(): Xóa phần tử có ưu tiên cao nhất ra khỏi hàng đợi. (Độ phức tạp: O(log N))
  • top(): Xem phần tử có ưu tiên cao nhất mà không xóa nó. (Độ phức tạp: O(1))
  • empty(): Kiểm tra xem hàng đợi có rỗng không. (Độ phức tạp: O(1))
  • size(): Trả về số lượng phần tử. (Độ phức tạp: O(1))
Illustration

3. Code Ví Dụ Minh Họa: "Thực chiến" thôi!

Ví dụ 1: Xếp hàng ưu tiên cho số nguyên (Max-Heap mặc định)

#include <iostream>
#include <queue> // Thư viện chứa priority_queue
#include <vector> // Mặc định dùng vector làm container

int main() {
    // Khai báo một priority_queue kiểu int (mặc định là Max-Heap)
    std::priority_queue<int> pq;

    // Thêm các phần tử vào hàng đợi
    pq.push(10);
    pq.push(30);
    pq.push(20);
    pq.push(5);
    pq.push(15);

    std::cout << "Cac phan tu trong priority_queue (tu lon nhat den nho nhat):\n";
    while (!pq.empty()) {
        std::cout << pq.top() << " "; // Xem phan tu lon nhat
        pq.pop(); // Xoa phan tu lon nhat
    }
    std::cout << std::endl;
    // Output: 30 20 15 10 5

    return 0;
}

Ví dụ 2: Tạo Min-Heap (ưu tiên số nhỏ nhất)

Để có một Min-Heap (tức là phần tử nhỏ nhất được ưu tiên), chúng ta cần chỉ định một comparator (bộ so sánh) khác. std::greater<int> sẽ làm điều đó.

#include <iostream>
#include <queue>
#include <vector>
#include <functional> // Can thiet cho std::greater

int main() {
    // Khai bao priority_queue kieu int, su dung std::greater<int> de lam Min-Heap
    std::priority_queue<int, std::vector<int>, std::greater<int>> min_pq;

    min_pq.push(10);
    min_pq.push(30);
    min_pq.push(20);
    min_pq.push(5);
    min_pq.push(15);

    std::cout << "Cac phan tu trong min_priority_queue (tu nho nhat den lon nhat):\n";
    while (!min_pq.empty()) {
        std::cout << min_pq.top() << " "; // Xem phan tu nho nhat
        min_pq.pop(); // Xoa phan tu nho nhat
    }
    std::cout << std::endl;
    // Output: 5 10 15 20 30

    return 0;
}

Ví dụ 3: Ưu tiên với đối tượng tùy chỉnh (Custom Object)

Giả sử bạn muốn ưu tiên các sinh viên dựa trên điểm số của họ.

#include <iostream>
#include <queue>
#include <vector>
#include <string>

// Định nghĩa một struct SinhVien
struct SinhVien {
    std::string ten;
    int diem;

    // Constructor
    SinhVien(std::string t, int d) : ten(t), diem(d) {}

    // Hàm so sánh cho priority_queue (để tạo Max-Heap theo điểm)
    // Nếu muốn sinh viên điểm cao hơn được ưu tiên, thì operator< sẽ trả về true 
    // khi 'this' có điểm THẤP hơn 'other'. Nghe hơi ngược đời nhưng nó là vậy đó!
    // priority_queue dùng operator< để quyết định phần tử nào 'nhỏ hơn' 
    // và phần tử 'nhỏ hơn' sẽ có ưu tiên THẤP hơn.
    // Để điểm cao được ưu tiên, ta cần đảo ngược logic so sánh mặc định.
    // HOẶC đơn giản hơn: viết một struct comparator riêng.
    
    // Cách 1: Overload operator< (phổ biến hơn)
    bool operator<(const SinhVien& other) const {
        return diem < other.diem; // Sinh vien co diem THAP HON se bi coi la 'nho hon' -> uu tien THAP HON
                                 // => Nghia la sinh vien diem CAO HON se duoc uu tien CAO HON (Max-Heap theo diem)
    }
};

// Cách 2: Định nghĩa một struct comparator riêng (minh bạch hơn cho một số trường hợp)
// struct CompareSinhVien {
//     bool operator()(const SinhVien& a, const SinhVien& b) {
//         return a.diem < b.diem; // Max-Heap theo diem
//     }
// };

int main() {
    std::priority_queue<SinhVien> danhSachThiDua;
    // Hoac: std::priority_queue<SinhVien, std::vector<SinhVien>, CompareSinhVien> danhSachThiDua;

    danhSachThiDua.push(SinhVien("An", 85));
    danhSachThiDua.push(SinhVien("Binh", 92));
    danhSachThiDua.push(SinhVien("Cuong", 78));
    danhSachThiDua.push(SinhVien("Dung", 95));

    std::cout << "Danh sach sinh vien theo thu tu uu tien (diem cao nhat):\n";
    while (!danhSachThiDua.empty()) {
        SinhVien sv = danhSachThiDua.top();
        std::cout << "Ten: " << sv.ten << ", Diem: " << sv.diem << std::endl;
        danhSachThiDua.pop();
    }
    // Output:
    // Ten: Dung, Diem: 95
    // Ten: Binh, Diem: 92
    // Ten: An, Diem: 85
    // Ten: Cuong, Diem: 78

    return 0;
}

4. Mẹo hay & Best Practices từ "Giáo sư Creyt"

  • Nhớ kỹ mặc định là Max-Heap: Cứ std::priority_queue<int> là nó sẽ ưu tiên số lớn nhất. Muốn số nhỏ nhất thì phải thêm std::greater<int> vào nhé!
  • Custom Object? Overload operator<: Khi làm việc với struct hoặc class của riêng mình, hãy overload operator< để priority_queue biết cách so sánh và xác định độ ưu tiên. Nhớ là operator< trả về true khi this có ưu tiên thấp hơn other (để tạo Max-Heap). Hoặc viết một comparator riêng cho nó minh bạch.
  • Không phải lúc nào cũng là giải pháp: priority_queue rất mạnh mẽ nhưng không phải là "thuốc tiên". Nếu bạn cần truy cập ngẫu nhiên (random access) vào các phần tử, hoặc cần duyệt qua tất cả các phần tử theo thứ tự không ưu tiên, thì std::vector hoặc std::list có thể là lựa chọn tốt hơn.
  • Độ phức tạp là bạn: Nhớ O(log N) cho push/popO(1) cho top. Điều này cực kỳ quan trọng khi các em đối phó với dữ liệu lớn.

5. Ứng dụng thực tế: "Priority Queue" có ở đâu?

priority_queue không chỉ là lý thuyết suông đâu, nó là "ngôi sao" thầm lặng đằng sau rất nhiều ứng dụng mà các em dùng hàng ngày đó:

  • Hệ điều hành (Operating Systems): Khi máy tính của em chạy nhiều chương trình cùng lúc, CPU cần quyết định chương trình nào sẽ được chạy tiếp theo. Các tác vụ quan trọng hơn (như xử lý sự kiện chuột) sẽ có ưu tiên cao hơn các tác vụ nền (như cập nhật phần mềm). Đó chính là priority_queue giúp quản lý hàng đợi các tiến trình.
  • Thuật toán tìm đường (Pathfinding Algorithms): Các thuật toán như Dijkstra's hoặc A* (dùng trong game, Google Maps) để tìm đường đi ngắn nhất đều sử dụng priority_queue để luôn chọn điểm đến tiếp theo có "chi phí" (quãng đường) thấp nhất.
  • Mạng máy tính (Networking): Các router dùng priority_queue để ưu tiên các gói dữ liệu quan trọng hơn (ví dụ: dữ liệu thoại/video cần độ trễ thấp) so với các gói dữ liệu khác.
  • Mô phỏng sự kiện (Event Simulation): Trong các hệ thống mô phỏng, priority_queue giúp sắp xếp các sự kiện theo thời gian xảy ra, đảm bảo sự kiện nào đến trước (hoặc có ưu tiên cao hơn) sẽ được xử lý trước.
  • Y tế (Healthcare): Trong phòng cấp cứu, bệnh nhân sẽ được ưu tiên điều trị dựa trên mức độ nghiêm trọng của tình trạng, chứ không phải ai đến trước. priority_queue có thể mô phỏng hệ thống ưu tiên này.

6. Thử nghiệm và Hướng dẫn sử dụng

Khi nào nên dùng priority_queue?

  • Khi bạn luôn cần truy cập hoặc xóa phần tử "tốt nhất" (best) hoặc "tồi tệ nhất" (worst) (dựa trên một tiêu chí ưu tiên nào đó) trong một tập hợp dữ liệu thay đổi liên tục.
  • Khi bạn cần triển khai các thuật toán như Dijkstra's (tìm đường ngắn nhất), Prim's (tìm cây bao trùm tối thiểu), Huffman Coding (nén dữ liệu).
  • Khi bạn muốn quản lý các tác vụ hoặc sự kiện theo mức độ khẩn cấp hoặc thời gian.

Khi nào KHÔNG nên dùng priority_queue?

  • Khi bạn cần một hàng đợi FIFO (First-In, First-Out) truyền thống. Hãy dùng std::queue.
  • Khi bạn cần một hàng đợi LIFO (Last-In, First-Out) (stack). Hãy dùng std::stack.
  • Khi bạn cần truy cập ngẫu nhiên vào các phần tử hoặc duyệt qua tất cả các phần tử theo thứ tự không ưu tiên. std::vector hoặc std::list có thể phù hợp hơn.
  • Khi bạn cần một cấu trúc dữ liệu mà bạn có thể xóa một phần tử bất kỳ không phải là phần tử ưu tiên cao nhất một cách hiệu quả (thường thì các cấu trúc cây cân bằng như std::set hoặc std::map sẽ tốt hơn cho việc này).

Thử nghiệm "nhẹ" cho các em:

Hãy thử viết một chương trình mô phỏng việc xếp hàng mua vé xem phim. Có hàng thường và hàng VIP. Hàng VIP được ưu tiên hơn hàng thường, nhưng trong mỗi hàng thì ai đến trước được phục vụ trước. Các em có thể dùng 2 priority_queue hoặc kết hợp priority_queue với std::pair để lưu cả ưu tiên và thời gian đến. Đây là một bài tập nhỏ để các em "vận động não" và áp dụng kiến thức vừa học đó!

Vậy là chúng ta đã cùng nhau khám phá "thế giới VIP" của priority_queue. Hy vọng các em đã nắm vững khái niệm và sẵn sàng áp dụng nó vào các dự án của mình. Nhớ nhé, lập trình là phải "thực chiến"!

Giáo sư Creyt out!

Thuộc Series: C++

Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!