Menu
CoddyTech

Hàng đợi (queue)

Cập nhật lần cuối

Hàng đợi (queue) có hai đầu cùng hoạt động. Giá trị mới vào ở cuối, còn giá trị rời đi từ đầu, nên thứ nào chờ lâu nhất sẽ được phục vụ trước. Đó là FIFO, và cũng chính là cách một hàng người xếp trước quầy vận hành: vào ở cuối và được phục vụ từ đầu là điều khiến việc chờ đợi công bằng. Nhấn Phát ở trên và xem các giá trị đi vào ở một phía và rời đi ở phía kia.

Vì mỗi đầu được theo dõi bằng chỉ số hoặc con trỏ riêng, cả hai thao tác đều tốn O(1) và không thao tác nào phải dời phần dữ liệu còn lại. Đó là lý do hàng đợi nằm bên dưới mọi thứ xử lý công việc theo thứ tự đến: lệnh in, hàng đợi tác vụ và tin nhắn, bộ đệm request, và tìm kiếm theo chiều rộng, thuật toán duyệt đồ thị theo từng tầng chính vì nó giữ biên duyệt trong một hàng đợi. Chuyển đầu lấy ra về phía cuối và bạn có một ngăn xếp.

Độ phức tạp thời gian và không gian

Với hàng đợi dùng bộ đệm vòng (ring buffer) hoặc danh sách liên kết, hai cách cài đặt tiêu chuẩn:

Thao tácĐộ phức tạpGhi chú
EnqueueO(1)Ghi vào cuối và tăng chỉ số cuối.
DequeueO(1)Đọc ở đầu và tăng chỉ số đầu, không phải dời phần tử.
Peek (đầu)O(1)Đọc giá trị ở đầu mà không lấy nó ra.
Tìm kiếmO(n)Không phải việc của hàng đợi: bạn phải lấy hết ra mới xem được bên trong.
Không gianO(n)Mỗi giá trị đang chờ chiếm một ô.

Từng bước

BướcĐiều gì xảy ra
1Hàng đợi bắt đầu rỗng, đầu và cuối cùng trỏ vào một ô.
2Enqueue ghi giá trị vào cuối, rồi tăng chỉ số cuối thêm một.
3Mỗi lần enqueue tiếp theo đặt giá trị phía sau các giá trị đang chờ.
4Dequeue đọc giá trị ở đầu, rồi tăng chỉ số đầu thêm một.
5Giá trị được trả về luôn là giá trị đã chờ lâu nhất.
6Khi đầu gặp cuối, hàng đợi lại rỗng, và dequeue thêm nữa sẽ gây lỗi.

Ví dụ minh họa

Enqueue 3, 7, 5 rồi lấy hết hàng đợi ra:

Thao tácHàng đợi (từ đầu đến cuối)Trả về
enqueue(3)[3]không có gì
enqueue(7)[3, 7]không có gì
enqueue(5)[3, 7, 5]không có gì
dequeue()[7, 5]3, giá trị cũ nhất
dequeue()[5]7
dequeue()[]5, giá trị mới nhất, ra sau cùng

Khi nào nên dùng hàng đợi

Nên dùng khiTránh dùng khi
Công việc phải được xử lý theo thứ tự đến: hàng đợi tác vụ, bộ đệm request, bộ đệm lệnh inBạn cần phần tử mới nhất trước, đó là việc của ngăn xếp
Bạn duyệt theo từng tầng, như tìm kiếm theo chiều rộngCác phần tử phải được phục vụ theo độ ưu tiên thay vì thứ tự đến, khi đó heap phù hợp hơn
Bên sản xuất và bên tiêu thụ chạy ở tốc độ khác nhau và cần một bộ đệm ở giữaBạn cần tìm kiếm hoặc truy cập theo chỉ số vào giữa dữ liệu
Bạn muốn chèn và lấy ra trong O(1) mà không phải dời phần tửBạn định cài đặt nó bằng cách dời mảng mỗi lần dequeue, khiến nó thành O(n)

Code Queue

Một cách cài đặt Queue gọn gàng, chạy được ngay bằng Python, JavaScript, Java, C++, C. Chọn một ngôn ngữ, sao chép code hoặc mở sẵn trong Coddy Playground.

Code Queue bằng Python

Python
1from collections import deque2
3queue = deque()4
5# Enqueue three values at the rear6for value in [3, 7, 5]:7    queue.append(value)8    print(f"enqueue {value} -> {list(queue)}")9
10# Dequeue them from the front: first in, first out11while queue:12    value = queue.popleft()13    print(f"dequeue {value} -> {list(queue)}")14
15print("empty:", len(queue) == 0)
Chạy code này trong Playground Python

Câu hỏi thường gặp về hàng đợi

FIFO nghĩa là gì?
First in, first out (vào trước, ra trước): giá trị chờ lâu nhất sẽ được phục vụ tiếp theo. Hàng người xếp trước quầy bán vé là hình ảnh quen thuộc hằng ngày. Ngăn xếp theo nguyên tắc ngược lại, LIFO.
Hàng đợi và ngăn xếp khác nhau như thế nào?
Chỉ khác ở đầu mà bạn lấy phần tử ra. Cả hai đều thêm vào cuối trong O(1); hàng đợi lấy ra từ đầu (FIFO), ngăn xếp lấy ra từ chính đầu đã thêm vào (LIFO). Ngoài điểm đó, bảng độ phức tạp của chúng giống hệt nhau.
Các thao tác chính trên hàng đợi là gì?
enqueue thêm một giá trị vào cuối, dequeue lấy ra và trả về giá trị ở đầu, peek (hoặc front) đọc giá trị ở đầu mà không lấy ra, và is_empty cho biết còn gì đang chờ không. Cả bốn đều tốn O(1).
Tại sao dequeue chậm nếu tôi dùng mảng thường?
Vì xóa chỉ số 0 khỏi mảng sẽ dời mọi phần tử còn lại sang trái, khiến mỗi lần dequeue tốn O(n). Các cài đặt thực tế tránh điều này bằng bộ đệm vòng có chỉ số đầu tăng dần, hoặc danh sách liên kết có con trỏ head. collections.deque của Python và ArrayDeque của Java làm việc này cho bạn, còn list.pop(0) thì không.
Hàng đợi vòng là gì?
Là hàng đợi nằm trong một mảng có kích thước cố định, trong đó chỉ số đầu và cuối quay về 0 khi vượt quá cuối mảng. Nó tái sử dụng các ô được giải phóng sau mỗi lần dequeue, nên một hàng đợi sức chứa n có thể chạy mãi thay vì đi ra ngoài cuối mảng.
Hàng đợi được dùng ở đâu trong các chương trình thực tế?
Hàng đợi tác vụ và tin nhắn giữa các dịch vụ, bộ đệm lệnh in và công việc, bộ đệm request trong máy chủ web, bộ đệm bàn phím và sự kiện, các pipeline sản xuất và tiêu thụ, và tìm kiếm theo chiều rộng, nơi hàng đợi chính là thứ khiến việc duyệt đi theo từng tầng.
Hình minh họa các ngôn ngữ lập trình của Coddy

Thành thạo thuật toán cùng Coddy

Bắt đầu