Bài 3: Cấu trúc dữ liệu ngăn xếp (stack) hàng đợi (queue)
2. Hàng đợi - Queue 1 Khái niệm
Hàng đợi là một tập hợp các phần tử cùng kiểu được tổ chức một cách tuần tự (tuyến tính) trong đó phần tử được thêm vào đầu tiên sẽ là phần tử được loại bỏ đầu tiên khỏi hàng đợi. Các hàng đợi thường được gọi là các cấu trúc FIFO (First In First Out).
Các ví dụ thực tế về hàng đợi mà chúng ta có thể thấy trong cuộc sống hàng ngày đó là đoàn người xếp hàng chờ mua vé tầu, danh sách các cuộc hẹn của một giám đốc, danh sách các công việc cần làm của một người …
Cũng có thể định nghĩa hàng đợi là một danh sách tuyến tính các phần tử giống nhau với một số
thao tác hạn chế tới các phần tử trên danh sách đó.
2.2 Các thao tác cơ bản của một hàng đợi
Tương tự như cấu trúc ngăn xếp, chúng ta định nghĩa các thao tác trên hàng đợi tuân theo cài đặt chuẩn của hàng đợi trong thư viện STL và các tài liệu khác, gồm có:
1. push(d): thêm phần tử d vào vị trí ở cuối hàng đợi.
2. pop(): loại bỏ phần tử ở đầu hàng đợi.
3. front(): trả về giá trị phần tử ở đầu hàng đợi.
4. back(): trả về giá trị phần tử ở cuối hàng đợi.
5. size(): trả về số phần tử đang ở trong hàng đợi.
6. empty(): kiểm tra hàng đợi có rỗng hay không.
7. full(): kiểm tra hàng đợi đầy (chỉ cần khi cài đặt hàng đợi bằng mảng).
Ví dụ:
2.3 Cài đặt hàng đợi sử dụng mảng
Để cài đặt cấu trúc hàng đợi chúng ta có thể sử dụng mảng hoặc sử dụng con trỏ (phần này sẽ học sau phần danh sách liên kết):
Ta lưu các phần tử của hàng đợi trong một mảng data. Đầu của hàng đợi là phần tử đầu tiên, và đuôi được chỉ ra bằng cách sử dụng một biến tail.
push(d) được thực hiện một cách dễ dàng: tăng tail lên 1 và chèn phần tử vào vị trí đó
pop() được thực hiện không hiệu quả: tất cả các phần tử đều sẽ bị dồn về đầu mảng do đó độ
phức tạp là O(n).
Làm thế nào chúng ta có thể cải thiện tình hình này?
Thay vì chỉ sử dụng một biến chỉ số tail chúng ta sử dụng hai biến tail và head, khi cần loại bỏ
(pop) một phần tử khỏi hàng đợi chúng ta sẽ tăng biến head lên 1:
Tuy vậy vẫn còn có vấn đề, đó là sau n lần push() (n là kích thước mảng) mảng sẽ đầy kể cả
trong trường hợp nó gần như rỗng về mặt logic. Để giải quyết vấn đề này chúng ta sẽ sử dụng lại các phần tử ở đầu mảng. Khi push() một phần tử mới tail sẽ được tăng lên 1 nhưng nếu như nó ở
cuối mảng thì sẽ đặt nó bằng 0.
Vấn đề mới nảy sinh ở đây là làm thế nào chúng ta có thể xác định được khi nào hàng đợi rỗng hoặc đầy?
Cách giải quyết đơn giản là ta sẽ dùng một biến lưu số phần tử thực sự của hàng đợi để giải quyết cho tất cả các thao tác kiểm tra hàng đợi rỗng, đầy hoặc lấy số phần tử của hàng đợi.
#include <stdio.h>
#include <stdlib.h>
const int MAX_ELEMENT = 100; // so phan tu toi da cua queue la 100 // khai bao queue chua cac so nguyen
typedef struct {
int * data; // khai bao mang dong int head;
int tail;
int cap; // luu so phan tu cua hang doi } queue;
// ham khoi tao queue rong void init(queue *q);
void push(queue * s, int d);
void pop(queue *q);
int front(const queue *q);
int back(const queue *q);
int size(const queue *q);
int empty(const queue *q);
int full(const queue *q);
// ham giai phong bo nho danh cho queue void clear(queue *q);
int main() {
int a[] = {3, 5, 1, 8};
int n = 4;
int i;
int d;
queue q;
init(&q);
for(i=0;i<n;i++) push(&q, a[i]);
while(!empty(&q)) {
d = front(&q);
printf("%d ", d);
pop(&q);
}
clear(&q);
return 0;
}
void init(queue *q) {
q->data = (int*)malloc(MAX_ELEMENT * sizeof(int));
q->head = q->tail = -1;
q->cap = 0;
}
void clear(queue *q) {
if(q->data != NULL) free(q->data);
q->head = q->tail = -1;
q->cap = 0;
}
void push(queue *q, int d) {
q->tail = (q->tail + 1) % MAX_ELEMENT;
q->data[q->tail] = d;
if(q->cap==0)
// neu hang doi rong thi sau khi push // ca head va tail deu chi vao 1 phan tu
q->head = q->tail;
q->cap++;
}
void pop(queue *q) {
q->head = (q->head + 1)%MAX_ELEMENT;
q->cap--;
if(q->cap==0)
q->head = q->tail = -1;
}
int front(const queue *q) {
return q->data[q->head];
}
int back(const queue *q) {
return q->data[q->tail];
}
int size(const queue *q) {
return q->cap;
}
int empty(const queue *q)
{
return (q->cap==0)?(1):(0);
}
int full(const queue *q) {
return (q->cap==MAX_ELEMENT-1)?(1):(0);
}
2.4 Ví dụ về hoạt động của hàng đợi với cài đặt bằng mảng vòng tròn
Ta giả sử mảng lưu các phần tử của hàng đợi là E[0..3], các biến head, tail lưu vị trí của phần tử
ở đầu và cuối hàng đợi, cột R là cột kết quả thực hiện các thao tác trên hàng đợi, các dấu ? tương ứng với giá trị bất kỳ.
Một số hình ảnh thực hiện chương trình
Menu lựa chọn chức năng
Chức năng 2 liệt kê các hoán vị có thể di chuyển (429 hoán vị với n=7)
Thực hiện kiểm tra hoán vị xem có cách di chuyển hay không, đưa ra cách di chuyển