Deque: Kẻ 2 mặt cân team trong thế giới C++
C++

Deque: Kẻ 2 mặt cân team trong thế giới C++

Author

Admin System

@root

Ngày xuất bản

22 Mar, 2026

Lượt xem

2 Lượt

"deque"

Chào các "coder nhí" tương lai của vũ trụ lập trình! Anh Creyt đây, hôm nay chúng ta sẽ cùng "hack não" một "Từ Khóa Công Nghệ" nghe có vẻ hơi "deep" nhưng thực ra lại "chill phết" trong C++: đó là std::deque.

1. Giới thiệu deque - Kẻ hai mặt linh hoạt

Bạn đã bao giờ xếp hàng mua trà sữa chưa? Thường thì chúng ta chỉ xếp vào cuối hàng đúng không? Nhưng nếu có một hàng đặc biệt, bạn có thể chen vào đầu hàng (kiểu VIP) hoặc xếp vào cuối hàng như bình thường. Đó chính là deque trong C++ đấy!

std::deque (phát âm là "deck" hoặc "dee-queue", viết tắt của Double-Ended QUEue - hàng đợi hai đầu) là một container trong Thư viện Chuẩn C++ (STL). Nó cho phép bạn thêm hoặc xóa các phần tử một cách hiệu quả từ cả hai đầu (phía trước và phía sau). Giống như một chiếc xe buýt có thể đón và trả khách ở cả hai cửa trước và sau mà không cần phải đi vòng vèo qua tất cả các ghế.

Để làm gì? Đơn giản là khi bạn cần một cấu trúc dữ liệu vừa linh hoạt như std::vector (có thể truy cập ngẫu nhiên các phần tử bằng chỉ số [i]) nhưng lại cần hiệu quả khi thêm/xóa ở cả hai đầu, điều mà std::vector chỉ làm tốt ở cuối (thêm/xóa ở đầu vector rất "đau ví" về hiệu năng).

2. Bóc tách deque: Bên trong có gì mà "chill phết" vậy?

"Học thuật sâu của Harvard" một chút nhé, nhưng anh Creyt sẽ "dịch" cho dễ hiểu. deque không giống vector là một khối bộ nhớ liên tục. Thay vào đó, nó được tổ chức như một "mảng các mảng" (array of arrays) hoặc "mảng các khối" (array of blocks). Tưởng tượng thế này:

  • vector là một dải đất liền mạch, muốn thêm nhà ở đầu thì phải dịch chuyển tất cả nhà cũ đi.
  • deque là một chuỗi các khu đất nhỏ (blocks), mỗi khu đất là một "mini-vector". Khi bạn thêm phần tử ở đầu hoặc cuối, deque chỉ cần tìm một khu đất trống gần nhất hoặc tạo thêm một khu đất mới và nối vào. Các "khu đất" này không nhất thiết phải nằm cạnh nhau trong bộ nhớ, nhưng deque sẽ quản lý chúng để bạn có thể truy cập chúng như thể chúng liên tục vậy.

Cơ chế này cho phép deque thực hiện các thao tác push_front(), pop_front(), push_back(), pop_back() với độ phức tạp thời gian trung bình là O(1) (hằng số), cực kỳ nhanh. Trong khi đó, truy cập phần tử bất kỳ bằng chỉ số (deque[i]) cũng là O(1), tương tự vector.

Illustration

3. Code ví dụ: "Flex" sức mạnh của deque

Cùng "bật mode" code để thấy deque "ngon" như thế nào nhé!

#include <iostream>
#include <deque>
#include <string>
#include <algorithm> // For std::for_each

int main() {
    // Khởi tạo một deque chứa các món ăn yêu thích của Gen Z
    std::deque<std::string> playlist;

    std::cout << "\n--- Bắt đầu playlist ---" << std::endl;

    // Thêm món vào cuối playlist (push_back)
    playlist.push_back("Lofi Chill");
    playlist.push_back("EDM Remix");
    std::cout << "Thêm Lofi Chill và EDM Remix vào cuối." << std::endl;

    // Thêm món ưu tiên vào đầu playlist (push_front)
    playlist.push_front("K-Pop Hit New");
    std::cout << "Thêm K-Pop Hit New vào đầu (VIP)." << std::endl;

    // Duyệt qua playlist hiện tại
    std::cout << "Playlist hiện tại: ";
    for (const std::string& song : playlist) {
        std::cout << "'" << song << "' ";
    }
    std::cout << std::endl;

    // Truy cập một phần tử bất kỳ (như vector)
    std::cout << "Bài hát thứ 2 trong playlist là: '" << playlist[1] << "'" << std::endl;

    // Xóa bài hát đầu tiên (pop_front)
    if (!playlist.empty()) {
        std::cout << "Xóa bài hát đầu tiên: '" << playlist.front() << "'" << std::endl;
        playlist.pop_front();
    }

    // Xóa bài hát cuối cùng (pop_back)
    if (!playlist.empty()) {
        std::cout << "Xóa bài hát cuối cùng: '" << playlist.back() << "'" << std::endl;
        playlist.pop_back();
    }

    std::cout << "Playlist sau khi xóa: ";
    if (playlist.empty()) {
        std::cout << "(Trống)" << std::endl;
    } else {
        for (const std::string& song : playlist) {
            std::cout << "'" << song << "' ";
        }
        std::cout << std::endl;
    }

    // Thêm một vài thứ nữa để thử chèn giữa
    playlist.push_back("Indie Vibe");
    playlist.push_front("Rap Việt");
    playlist.push_back("Acoustic Cover");

    // Chèn vào giữa (iterator)
    // Chèn "Pop Ballad" vào vị trí thứ 2 (chỉ số 1)
    auto it = playlist.begin();
    std::advance(it, 1); // Di chuyển iterator đến vị trí muốn chèn
    playlist.insert(it, "Pop Ballad");
    std::cout << "\nPlaylist sau khi chèn 'Pop Ballad' vào vị trí thứ 2: ";
    for (const std::string& song : playlist) {
        std::cout << "'" << song << "' ";
    }
    std::cout << std::endl;

    std::cout << "\n--- Kết thúc playlist ---" << std::endl;

    return 0;
}

Output dự kiến:

--- Bắt đầu playlist ---
Thêm Lofi Chill và EDM Remix vào cuối.
Thêm K-Pop Hit New vào đầu (VIP).
Playlist hiện tại: 'K-Pop Hit New' 'Lofi Chill' 'EDM Remix' 
Bài hát thứ 2 trong playlist là: 'Lofi Chill'
Xóa bài hát đầu tiên: 'K-Pop Hit New'
Xóa bài hát cuối cùng: 'EDM Remix'
Playlist sau khi xóa: 'Lofi Chill' 

Playlist sau khi chèn 'Pop Ballad' vào vị trí thứ 2: 'Rap Việt' 'Pop Ballad' 'Indie Vibe' 'Acoustic Cover' 

--- Kết thúc playlist ---

4. Mẹo nhỏ từ Creyt: Dùng deque sao cho "pro"

  • Khi nào chọn deque thay vì vector?
    • Nếu bạn cần thêm/xóa phần tử thường xuyên ở cả hai đầu của container. vector rất kém hiệu quả khi thêm/xóa ở đầu (O(N)).
    • Bạn vẫn cần truy cập ngẫu nhiên nhanh chóng (O(1)).
  • Khi nào chọn vector thay vì deque?
    • Nếu bạn chỉ thêm/xóa ở cuối container và cần hiệu suất bộ nhớ cao nhất (cache locality tốt hơn vì vector là một khối liền mạch).
    • Khi kích thước container ít thay đổi hoặc chỉ tăng lên, và bạn muốn tránh overhead của việc quản lý nhiều block bộ nhớ.
  • Khi nào chọn list thay vì deque?
    • std::list (danh sách liên kết đôi) rất hiệu quả khi chèn/xóa phần tử ở bất cứ đâu trong container (O(1) nếu có iterator đến vị trí đó). Tuy nhiên, list không hỗ trợ truy cập ngẫu nhiên (O(N) để tìm phần tử thứ k).
    • deque vẫn kém hiệu quả hơn list khi chèn/xóa ở giữa, vì nó vẫn phải dịch chuyển các phần tử trong một block.

Mẹo ghi nhớ: Hãy nghĩ deque là "con lai" của vectorlist. Nó có khả năng truy cập ngẫu nhiên như vector và khả năng thêm/xóa nhanh ở hai đầu như list (nhưng chỉ ở hai đầu thôi nhé!).

5. deque trong đời thực: Ai đã dùng rồi?

deque không phải là container phổ biến nhất, nhưng nó là "ngôi sao" trong những trường hợp cụ thể. Các ứng dụng thực tế bao gồm:

  • Hệ thống Undo/Redo (Hoàn tác/Làm lại): Khi bạn gõ văn bản, mỗi thao tác có thể được lưu vào một deque. Khi Undo, bạn pop_back() hành động cuối cùng. Khi Redo, bạn có thể push_front() lại nếu có một deque riêng cho các hành động đã Undo.
  • Lịch sử duyệt web: Lưu trữ các URL đã truy cập. Bạn có thể thêm URL mới vào cuối và khi quay lại trang trước, bạn đang "pop" từ cuối.
  • Quản lý bộ đệm (Buffer Management): Trong các hệ thống xử lý dữ liệu theo luồng, deque có thể dùng làm bộ đệm nơi dữ liệu được thêm vào một đầu và xử lý/xóa ở đầu kia.
  • Thuật toán tìm kiếm theo chiều rộng (BFS - Breadth-First Search): Mặc dù std::queue thường được dùng, deque có thể thay thế và đôi khi linh hoạt hơn (ví dụ trong 0-1 BFS).

6. Lời khuyên cuối từ Creyt: Khi nào thì "bật mode" deque?

Anh Creyt đã từng "thử nghiệm" deque trong một dự án xử lý dữ liệu thời gian thực, nơi các gói tin đến liên tục và cần được xử lý theo thứ tự nhưng đôi khi lại có các gói tin "ưu tiên" cần chèn vào đầu hàng đợi. Ban đầu dùng vector, mỗi lần chèn ưu tiên là cả hệ thống "đứng hình" vài mili giây vì phải dịch chuyển hàng ngàn phần tử. Chuyển sang deque, mọi thứ "mượt mà" hẳn, hiệu năng được cải thiện đáng kể.

Khi nào nên dùng?

  • Khi bạn biết chắc chắn rằng mình sẽ cần thao tác thêm/xóa thường xuyên ở cả hai đầu của container.
  • Khi bạn vẫn cần khả năng truy cập phần tử bất kỳ bằng chỉ số (như deque[i]).
  • Khi bạn làm việc với các thuật toán yêu cầu hàng đợi hai đầu hoặc cần sự linh hoạt trong việc quản lý dữ liệu theo cả hai hướng.

Khi nào không nên?

  • Nếu bạn chỉ cần thêm/xóa ở cuối và cần hiệu suất tối đa (cache locality): dùng std::vector.
  • Nếu bạn cần chèn/xóa ở giữa container rất thường xuyên và truy cập ngẫu nhiên không phải là ưu tiên: dùng std::list.

Nhớ nhé, không có "vũ khí" nào là tốt nhất cho mọi trận chiến. Hiểu rõ ưu nhược điểm của từng loại container sẽ giúp bạn trở thành một "kiến trúc sư code" tài ba, chọn đúng công cụ cho đúng việc. Chúc các bạn "code đỉnh"!

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!