24/05/2018, 19:20

Danh sách tuyến tính kiểu hàng đợi

Hàng đợi là một vật chứa (container) các đối tượng làm việc theo cơ chế FIFO (First In First Out) nghĩa là việc thêm một đối tượng vào hàng đợi hoặc lấy một đối tượng ra khỏi hàng đợi được thực hiện theo cơ chế "Vào trước ra trước". ...

Hàng đợi là một vật chứa (container) các đối tượng làm việc theo cơ chế FIFO (First In First Out) nghĩa là việc thêm một đối tượng vào hàng đợi hoặc lấy một đối tượng ra khỏi hàng đợi được thực hiện theo cơ chế "Vào trước ra trước".

Các đối tượng có thể được thêm vào hàng đợi bất kỳ lúc nào nhưng chỉ có đối tượng thêm vào đầu tiên mới được phép lấy ra khỏi hàng đợi.

Thao tác thêm một đối tượng vào hàng đợi và lấy một đối tượng ra khỏi hàng đợi lần lượt được gọi là "enqueue" và "dequeue".

Việc thêm một đối tượng vào hàng đợi luôn diễn ra ở cuối hàng đợi và một phần tử luôn được lấy ra từ đầu hàng đợi.

Ta hình dung nguyên tắc hoạt động của Queue như sau:

Trong tin học, CTDL hàng đợi có nhiều ứng dụng: khử đệ qui, tổ chức lưu vết các quá trình tìm kiếm theo chiều rộng và quay lui, vét cạn, tổ chức quản lý và phân phối tiến trình trong các hệ điều hành, tổ chức bộ đệm bàn phím, .

Ta có thể định nghĩa CTDL hàng đợi như sau: hàng đợi là một CTDL trừu tượng (ADT) tuyến tính. Tương tự như stack, hàng đợi hỗ trợ các thao tác:

EnQueue(o): Thêm đối tượng o vào cuối hàng đợi

DeQueue(): Lấy đối tượng ở đầu queue ra khỏi hàng đợi và trả về giá trị của nó. Nếu hàng đợi rỗng thì lỗi sẽ xảy ra.

IsEmpty(): Kiểm tra xem hàng đợi có rỗng không.

Front(): Trả về giá trị của phần tử nằm ở đầu hàng đợi mà không hủy nó. Nếu hàng đợi rỗng thì lỗi sẽ xảy ra.

Các thao tác thêm, trích và huỷ một phần tử phải được thực hiện ở 2 phía khác nhau của hàng đợi do đó hoạt động của hàng đợi được thực hiện theo nguyên tắc FIFO (First In First Out - vào trước ra trước).

Cũng như stack, ta có thể dùng cấu trúc mảng 1 chiều hoặc cấu trúc danh sách liên kết để biểu diễn cấu trúc hàng đợi.

Cài đặt Queue bằng mảng

Ta có thể tạo một hàng đợi bằng cách sử dụng một mảng 1 chiều với kích thước tối đa là N (ví dụ, N có thể bằng 1000) theo kiểu xoay vòng (coi phần tử an-1 kề với phần tử a0).

Như vậy hàng đợi có thể chứa tối đa N phần tử. Phần tử nằm ở đầu hàng đợi (front element) sẽ có chỉ số f. Phần tử nằm ở cuối hàng đợi (rear element) sẽ có chỉ số r (xem hình).

Ðể khai báo một hàng đợi, ta cần một mảng một chiều Q, hai biến nguyên f, r cho biết chỉ số của đầu và cuối của hàng đợi và hằng số N cho biết kích thước tối đa của hàng đợi. Ngoài ra, khi dùng mảng biểu diễn hàng đợi, ta cũng cần một giá trị đặc biệt để gán cho những ô còn trống trên hàng đợi. Giá trị này là một giá trị nằm ngoài miền xác định của dữ liệu lưu trong hàng đợi. Ta ký hiệu nó là NULLDATA như ở những phần trước.

Trạng thái hàng đợi lúc bình thường:

Trạng thái hàng đợi lúc xoay vòng:

Hoặc:

Khi queue rỗng R = F = 0. Nếu mỗi phần tử của queue được lưu trữ trong một từ máy thì khi bổ sung một phần tử vào queue R sẽ tăng lên 1, còn khi loại bỏ phần tử ra khỏi queue F sẽ tăng lên 1.

Câu hỏi đặt ra: khi giá trị f=r cho ta điều gì ? Ta thấy rằng, lúc này hàng đợi chỉ có thể ở một trong hai trạng thái là rỗng hoặc đầy. Coi như một bài tập các bạn hãy tự suy nghĩ tìm câu trả lời trước khi đọc tiếp để kiểm tra kết quả.

Hàng đợi có thể được khai báo cụ thể như sau:

Data Q[N] ;

int f, r;

Cũng như strack, do khi cài đặt bằng mảng một chiều, hàng đợi có ki?hước tối đa nên ta cần xây dựng thêm một thao tác phụ cho hàng đợi:

IsFull(): Kiểm tra xem hàng đợi có đầy chưa.

Cài đặt Queue bằng danh sách

Ta có thể tạo một hàng đợi bằng cách sử dụng một DSLK đơn.

Phần tử đầu DSKL (head) sẽ là phần tử đầu hàng đợi, phần tử cuối DSKL (tail) sẽ là phần tử cuối hàng đợi.

Sau đây là các thao tác tương ứng cho array-queue:

Tạo hàng đợi rỗng:

Lệnh Q.pHead = Q.pTail = NULL sẽ tạo ra một hàng đợi rỗng.

Kiểm tra hàng đợi rỗng :

char IsEmpty(LIST Q)

{

if (Q.pHead == NULL) // stack rỗng

return 1;

else return 0;

}

Thêm một phần tử p vào cuối hàng đợi

void EnQueue(LIST Q, Data x)

{

InsertTail(Q, x);

}

  • Loại bỏ phần tử ở đầu hàng đợi

Data DeQueue(LIST Q)

{ Data x;

if (IsEmpty(Q)) return NULLDATA;

x = RemoveFirst(Q);

return x;

}

Xem thông tin của phần tử ở đầu hàng đợi

Data Front(LIST Q)

{

if (IsEmpty(Q)) return NULLDATA;

return Q.pHead->Info;

}

Các thao tác trên đều làm việc với chi phí O(1).

Chương trình minh họa hàng đợi có ưu tiên, cách cài đặt các nút trên hàng đợi có độ ưu tiên giảm dần từ front tới rear.

pqinsert: Chèn nút mới vào vị trí thích hợp trên hàng đợi để đảm bảo độ ưu tiên của các nút giảm dần từ front tới rear

pqremove: Xóa nút có độ ưu tiên cao nhất, nút này là nút tại front

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

#define MAXQUEUE 100

#define TRUE 1

#define FALSE 0

// Khai bao cau truc pqueue

struct pqueue

{

int rear; // front luon la 0

int nodes[MAXQUEUE]; // moi nut la mot so nguyen chi do uu tien

};

// Tac vu pqinitialize: khoi dong hang doi co uu tien

void pqinitialize(struct pqueue *ppq)

{

ppq->rear = -1;

}

// Tac vu pqempty: kiem tra hang doi co rong khong

int pqempty(struct pqueue *ppq)

{

return((ppq->rear == -1) ? TRUE : FALSE);

}

// Tac vu pqueuesize: xac dinh so nut co trong hang doi

int pqueuesize(struct pqueue *ppq)

{

return(ppq->rear+1);

}

// Tac vu pqinsert: them nut vao hang doi co uu tien

void pqinsert(struct pqueue *ppq, int x)

{

int i, j;

if(ppq->rear == MAXQUEUE-1)

printf("Hang doi bi day, khong them nut duoc");

else

{

// tim vi tri chen

for(i = 0; i < pqueuesize(ppq) && ppq->nodes[i] >= x; i++)

;

// doi cho cac nut tu nut cuoi den nut i+1 len mot vi tri

for (j = pqueuesize(ppq) ; j > i; j--)

ppq->nodes[j] = ppq->nodes[j-1];

ppq->nodes[i] = x;

ppq->rear++;

}

}

/* Tac vu pqremove: xoa nut co do uu tien cao nhat (nut o front), truong hop

nay ta phai doi cac nut tu nut thu hai den nut cuoi xuong mot vi tri */

int pqremove(struct pqueue *ppq)

{

int x, i;

if(pqempty(ppq))

printf("Hang doi bi rong, khong xoa nut duoc");

else

{

x = ppq->nodes[0]; // do uu tien cua nut can xoa

// doi cho

for (i = 0; i < ppq->rear; i++)

ppq->nodes[i] = ppq->nodes[i+1];

ppq->rear--;

return x;

}

}

// Tac vu pqtraverse: duyet hang doi co uu tien tu front den rear

void pqtraverse(struct pqueue *ppq)

{

int i;

if(pqempty(ppq))

printf("hang doi bi rong");

else

for(i = 0; i <= ppq->rear; i++)

printf("%d ", ppq->nodes[i]);

}

void main(void)

{

struct pqueue pq;

int douutien, chucnang;

char c;

clrscr();

// khoi dong hang doi

pqinitialize(&pq);

do

{

// menu chinh cua chuong trinh

printf(" CHUONG TRINH MINH HOA HANG DOI CO UU TIEN ");

printf(" Cac chuc nang cua chuong trinh: ");

printf(" 1: Them nut vao hang doi co uu tien ");

printf(" 2: Xoa nut co do uu tien cao nhat ");

printf(" 3: Xoa toan bo hang doi ");

printf(" 4: Duyet hang doi ");

printf(" 0: Ket thuc chuong trinh ");

printf("Chuc nang ban chon: ");

scanf("%d", &chucnang);

switch(chucnang)

{

case 1:

{

printf(" Do uu tien cua nut moi: ");

scanf("%d", &douutien);

pqinsert(&pq, douutien);

break;

}

case 2:

{

pqremove(&pq);

break;

}

case 3:

{

printf(" Ban co chac khong (c/k): ");

c = getche();

if(c == 'c' || c == 'C')

pqinitialize(&pq);

break;

}

case 4:

{

printf(" Duyet hang doi: ");

pqtraverse(&pq);

break;

}

}

} while(chucnang != 0);

}

Lưu ý, nếu không quản lý phần tử cuối xâu, thao tác dequeue sẽ có độ phức tạp O(n).

Hàng đợi có thể được sử dụng trong một số bài toán:

Bài toán sản xuất và tiêu thụ (ứng dụng trong các hệ điều hành song song).

Bộ đệm (ví dụ: Nhấn phím -> Bộ đệm -> CPU xử lý).

Xử lý các lệnh trong máy tính (ứng dụng trong HÐH, trình biên dịch), hàng đượi các tiến trình chờ được xử lý, ..

Một số ví dụ:

Chuong trinh quan ly kho

Mat hang nao nhap kho truoc se duoc xuat kho truoc

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

#define MAXQUEUE 100

#define TRUE 1

#define FALSE 0

// Khai bao cau truc mathang

typedef struct mathang

{

int mamh;

char tenmh[12];

};

struct queue

{

int front, rear;

mathang nodes[MAXQUEUE];

};

void initialize(struct queue *pq)

{

pq->front = pq->rear = MAXQUEUE-1;

}

int empty(struct queue *pq)

{

return((pq->front == pq->rear) ? TRUE : FALSE);

}

void insert(struct queue *pq, mathang x)

{

if(pq->rear == MAXQUEUE-1)

pq->rear = 0;

else

(pq->rear)++;

if(pq->rear == pq->front)

printf("kho hang bi day");

else

pq->nodes[pq->rear] = x;

}

mathang remove(struct queue *pq)

{

if(empty(pq))

printf("kho khong con hang");

else

{

if(pq->front == MAXQUEUE-1)

pq->front = 0;

else

(pq->front)++;

return(pq->nodes[pq->front]);

}

}

// Tac vu traverse: duyet kho hang tu front toi rear

void traverse(struct queue *pq)

{

int i;

if(empty(pq))

{

printf(" Kho khong con hang");

return;

}

if(pq->front == MAXQUEUE-1)

i = 0;

else

i = pq->front+1;

// vong lap in cac nut tu front den nut ke cuoi

while(i != pq->rear)

{

printf(" %11d%15s", pq->nodes[i].mamh, pq->nodes[i].tenmh);

if(i == MAXQUEUE-1)

i = 0;

else

i++;

}

// in nut cuoi (nut tai rear)

printf(" %11d%15s", pq->nodes[i].mamh, pq->nodes[i].tenmh);

}

// chuong trinh chinh

void main(void)

{

struct queue q;

int chucnang, front1;

char c;

mathang mh;

clrscr();

// khoi tao queue

initialize(&q);

do

{

printf(" CHUONG TRINH QUAN LY KHO");

printf(" (NHAP TRUOC - XUAT TRUOC)");

printf(" Cac chuc nang cua chuong trinh: ");

printf(" 1: Nhap mot mat hang ");

printf(" 2: Xuat mot mat hang ");

printf(" 3: Xem mat hang chuan bi xuat ");

printf(" 4: Xem mat hang moi nhap ");

printf(" 5: Xem cac mat hang co trong kho ");

printf(" 6: Xuat toan bo kho hang ");

printf(" 0: Ket thuc chuong trinh ");

printf("Chuc nang ban chon: ");

scanf("%d", &chucnang);

switch(chucnang)

{

case 1:

{

printf(" Ma mat hang: ");

scanf("%d", &mh.mamh);

printf("Ten mat hang: ");

scanf("%s", &mh.tenmh);

insert(&q, mh);

break;

}

case 2:

{

if(!empty(&q))

{

mh = remove(&q);

printf(" Mat hang xuat: Ma:%d Ten:%s", mh.mamh, mh.tenmh);

}

else

printf(" Kho khong con hang");

break;

}

case 3:

{

front1 = (q.front == MAXQUEUE-1 ? 0 : q.front+1);

printf(" Mat hang chuan bi xuat: Ma:%d Ten:%s",

q.nodes[front1].mamh, q.nodes[front1].tenmh);

break;

}

case 4:

{

printf(" Mat hang moi nhap: Ma:%d Ten:%s",

q.nodes[q.rear].mamh, q.nodes[q.rear].tenmh);

break;

}

case 5:

{

printf(" Cac mat hang co trong kho:");

printf(" %11s%15s", "MA MAT HANG", " TEN MAT HANG");

traverse(&q);

break;

}

case 6: // xoa toan bo queue (khoi dong queue)

{

printf(" Ban co chac khong (c/k): ");

c = getche();

if(c == 'c' || c == 'C')

initialize(&q);

break;

}

}

} while(chucnang != 0);

}

Hàng đợi hai đầu (double-ended queue)

Hàng đợi hai đầu (gọi tắt là Deque) là một vật chứa các đối tượng mà việc thêm hoặc hủy một đối tượng được thực hiện ở cả 2 đầu của nó.

Ta có thể định nghĩa CTDL deque như sau: deque là một CTDL trừu tượng (ADT) hỗ trợ các thao tác chính sau:

 InsertFirst(e): Thêm đối tượng e vào đầu deque

InsertLast(e): Thêm đối tượng e vào cuối deque

RemoveFirst(): Lấy đối tượng ở đầu deque ra khỏi deque và trả về giá trị của nó.

  RemoveLast(): Lấy đối tượng ở cuối deque ra khỏi deque và trả về giá trị của nó.

Ngoài ra, deque cũng hỗ trợ các thao tác sau:

 IsEmpty(): Kiểm tra xem deque có rỗng không.

  First(): Trả về giá trị của phần tử nằm ở đầu deque mà không hủy nó.

  Last(): Trả về giá trị của phần tử nằm ở cuối deque mà không hủy nó.

Dùng deque để cài đặt stack và queue

Ta có thể dùng deque để biểu diễn stack. Khi đó ta có các thao tác tương ứng như sau:

STT Stack Deque
1 Push InsertLast
2 Pop RemoveLast
3 Top Last
4 IsEmpty IsEmpty

Tương tự, ta có thể dùng deque để biểu diễn queue. Khi đó ta có các thao tác tương ứng như sau:

STT Queue Deque
1 Enqueue InsertLast
2 Dequeue RemoveFist
3 Front First
4 IsEmpty IsEmpty

Cài đặt deque

Do đặc tính truy xuất hai đầu của deque, việc xây dựng CTDL biểu diễn nó phải phù hợp.

Ta có thể cài đặt CTDL deque bằng danh sách liên kết đơn. Tuy nhiên, khi đó thao tác RemoveLast hủy phần tử ở cuối deque sẽ tốn chi phí O(n). Ðiều này làm giảm hiệu quả của CTDL. Thích hợp nhất để cài đặt deque là dùng danh sách liên kết kép. Tất cả các thao tác trên deque khi đó sẽ chỉ tốn chi phí O(1)

0