Tóm lại hàng đợi là một danh sách gồm các phần tử (cùng kiểu), việc thêm vào và lấy ra các phần tử trong hàng đợi buộc phải tuân thủ theo cơ chế FIFO.
Có thể dừng mảng một chiều hoặc danh sách liên kết đơn để xây dựng hàng đợi (queue)
Các thao tác cơ bản trên hàng đợi:
- Kiểm tra hàng đợi rồng hay chưa. - Kiểm tra hàng đợi đầy hay chưa.
- Thêm một phần tử vào hàng đợi (enQueue). - Lấy một phần tử ra khỏi hàng đợi (deQueue). - Khởi tạo hàng đợi rỗng.
Chú ý:
- Front: cho biết vị trí trên hàng đợi, nơi mà một phần tử sẽ bị lấy ra khỏi hàng đợi (chỉ phần tử ở đầu hàng đợi)
Rear: cho biết vị trí trên hàng đợi, nơi mà một phần tử mới sẽ được thêm vào tại đó (chỉ phần tử ở cuối hàng đợi)
6.2. Cài đặt Hàng đọi bằng mảng vói phương pháp tịnh tiến:
Sau đây là hình mơ tả hàng đợi trong các tình huống: rồng, có một phần tử, có nhiều phần tử và khi hàng đợi đầy:
n=6 phẩn tử
Rear Front
n=6 phân tử
-1
thêm giá trị 10 vào
n=ó phần tữ n=6 phẩn tú 5 5 4 4 3 3 R ear— ►! 7 Rear — ► 2 7 1 15 Front— ► 1 15 Front— ►o 10 0 -1 -1 thêm 15. rồi thcm 7 lấv 10 ra
n=6 phần tử
Ta muốn thêm 105 vào hàng đợi thì phải làm sao?
Đây là trạng thái đầy giả, ta không thể thêm vào được nữa. Nếu muốn thèm vào thì phải tịnh tiến các giá trị trong hàng đợi xuống phía dưới. Vì vậy ta gọi đây là phưcmg pháp tịnh tiến.
n=6 phân tú
Rear — »
Front
•1
n=6 phẩn tử Rear— ► 5 4 3 2 Front— ► 0 •I
Hàng đợi sau khi thêm 105
n=6 phần từ
Thêm tiếp 37 vào thì hàng đợi đầy thật. Khai báo cẩu trúc dữ liệu cho hàng đợi: struct tqueue
{
int * queue Array; int n;
};
typedef struct tqueue QUEUE; a) Hàm khởi tạo hàng đợi:
void queuelnitialize(QUEUE &q, int spt) { q.queueArray = new int[spt];
q .n = spt; q.front = -1; q.rear = -1;
}
b) Hàm kiểm tra hàng đợi rỗng hay chưa:
bool isEmpty(QUEUE q) {
if (q.front == -1) return true; return false;
}
c) Hàm kiểm tra hàng đợi đầy hay chưa:
Vì hàng đợi có thể bị đầy giả (tức là chưa đầy thật), cho nên nếu chỉ viết: if (q.rear == q.n - 1) là khơng đúng? n -ó phần tử n=6 phần tử Rear — ► 5 100 R ear— ► 5 37 4 30 4 105 3 21 3 100 Front— *■ 2 7 2 30 1 1 21 0 Front— ► 0 7 -1 -1
bool isFull(QUEUE q) {
if (q.rear - q.front == q.n - 1) return true; return false;
}
d) Hàm thêm một phần tử vào hàng đợi (enQueue):
bool addltem (QUEUE &q, int in_item) { if (isFull(q))
return false; if (isEmpty (q) )
q .front++; //q.front=0; if (q.rear == q.n - 1) {
for (int i = q.front; i < q.n; i++) q .queueArray[i - 1] = q .queueArray[i]; q .rear -= 1; q .front += 1; } q.queueArray[++q.rear] = in_item; return true; }
e) Hàm lấy một phần tử ra khỏi hàng đợi (deQueue):
n=6 phần tử
Hàng đợi trước khi lấy ra 50 n=6 phẩn từ 5 -ront— > 4 R.ear — ► 3 2 1 0 -1
Sau khi lấy 50 ra thì hàng đợi rồng. Do đó ta phải cập nhật lại ngay Front=-l và Rear=-1.
bool remov e l t e m (QUEUE &q, int &out_item) { if (isEmpty(q))
return false;
out_item = q.queueArray[q.front++]; if (q.front > q.rear)
return true;
}
void outputToTestQueue(QUEUE q) { int i ;
if (isEmpty(q) ) {
cout « "Queue is empty!";
cout « "\nq.front = " « q.front; cout « " q.rear = " « q.rear; cout « " q.n = " « q.n;
return;
}
for (int i = q.front; i <= q.rear; i++) cout « q.queueArray[i] « " ";
cout « "\nq.front = " « q.front; cout « " q.rear = " « q.rear; cout « " q.n = " « q.n; } g) M ột ví dụ về hàm main: void main() { QUEUE q; int i; int out_item; queueInitialize(q,10);
cout « "Queue after initializing is:\n"; outputToTestQueue(q) ;
for (i = 0; i < 15; i++) i f (ãaddltem(q,i))
cout ô "Queue is full!\n"; outputToTestQueue(q) ;
cout « "\nRemove 5 items from queue:\n"; for (i = 0; i < 5; i++)
if (removeItem(q,out_item))
cout « "Have just removed item: " « out_item « endl;
outputToTestQueue(q);
cout « "\nAdd one item that values 100:\n"; addltem(q,100);
outputToTestQueue(q);
cout « "\nNow, we remove all items from queue:\n"; for (i = 0; i < q.n; i++)
if (removeltem(q,out_item))
cout « "Have just removed item: " « out_item « endl;
else cout « "Queue has no item left." « endl; outputToTestQueue (q) ;
cout « endl;
}
6.3. Cài đặt Hàng đọi bằng danh sách liên kết
Tương tự như danh sách liên kết. Phần tử đầu của danh sách liên kết (pHead) sẽ là phần tử đầu của Hàng đợi (front), phần tử cuối của danh sách liên kết (pTail) sẽ là phàn tử cuối của Hàng đợi (rear).
Khai báo cấu trúc dữ liệu: struct Node { DataType data; Node * pNext; } ; struct Queue {
Node * front, *rear; };
a Khởi tạo Queue rồng:
void init (Queue &q) { q.front = q.rear = NULL;
}
b. Kiểm tra hàng đợi rồng
int isEmpty (Queue &q) { if (q.front — NULL)
return 1; return 0;
}
c. Thêm một phần tử p vào cuối hàng đợi Queue:
int enQueue (Queue &q, DataType x) { Node * new_node = new Node;
if (new_node == NULL) return 0; // không dủ bộ nhớ new_node -> pNext = NULL;
new_node -> data = x;
if (q.front = NULL) // trường hợp Hàng đợi rỗng q.front = q.rear = new_node;
else {
q.rear -> pNext = new_node; q .rear = new_node;
}
return 1;
}
d. Lấy phần tử ra khỏi cuối hàng đợi
DataType deQueue (Queue &q) { if (isEmpty(q) ) {
cout « "Queue is empty"; exit(1);
}
Node *p = q.front;
q.front = q.front -> pNext;
if (q.front = NULL) q.rear = NULL; DataType X = p - > data;
p -> pNext = NULL; delete p;
return x;
}
e. Xem thông tin của phần tử ở đầu Hàng đợi:
DataType viewFront (Queue q) { if (isEmpty(q)) {
cout « "Queue is empty"; exit (1) ;
}
return q.front -> data;
}
6.4. ử n g dụng
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, ...
Thông thường các vấn đề liên quan đến cơ chế “vào trước ra trước” đều có thể dừng cấu trúc dữ liệu Hàng đợi. Ví dụ như cơ chế sản xuất và tiêu thụ, hàng hóa sản xuất trước được đưa vào kho và sẽ được xuất ra trước. Các ứng dụng đặt vé tàu lửa máy bay, hệ thống rút tiền...Ngồi ra hàng đợi cịn được dùng nhiều trong hệ điều hành: bộ đệm ứng dụng, hàng đợi xử lý các sự kiện, hàng đợi xử lý phím nhấn, tiến trình...
• Bài tốn quản lý kho hàng:
Đây là dạng bài toán sản xuất & tiêu dùng, mặt hàng được sản xuất ra sẽ được lưu vào kho, hàng hóa từ kho này sẽ được xuất ra ngoài cho nhà phân phối. Khi đó những mặt hàng nào đưa vào kho trước tiên sẽ ra được xuất kho trước. Đây là dạng FIFO nên chúng ta có thể dùng cấu trúc dừ liệu Hàng đợi để minh họa cho nó.
Cài đặt: Việc cài sinh viên tự cài đặt. Cách cài đặt tương tự như cài đặt Hàng đợi (chứa các số nguyên ở trên), ở đây chỉ khác là kiểu dữ liệu không phải là in mà là kiểu Data được khai báo như sau:
typedef struct {
char maSP[10]; // Ma san pham char tenSP[50]; // Ten san pham } Data;
TỎNG KÉT CHƯƠNG
Sau khi học xong chương này chúng ta cần nhớ:
- Sự khác biệt giữa cấu trúc dữ liệu động và cấu trúc dữ liệu tĩnh (một điểm mấu chốt dễ nhớ nhất là hai chữ “động” và “tĩnh”. Động có nghĩa là có thể thay đổi linh hoạt còn tĩnh thi không thể thay đổi). Mồi loại đều có ưu và nhược điểm nhất định và tùy vào ứng dụng trong thực tế mà người ta sẽ quyết định xem nên sử dụng loại cấu trúc dừ liệu nào để đạt được hiệu quả tối đa kể cả về mặt lưu trữ, thao ..ác và thời gian thực hiện ứng dụng.
- Con trỏ có ứng dụng rất nhiều trong thực tế, vì vậy chứng ta phải nắm rõ bản chất của biến con trỏ và người ta thường sử dụng nó để quản lý vùng nhớ động.
- Phân biệt hai loại danh sách đó là danh sách đặc và danh sách liên kết. Trong chương này chúng ta đã được tìm hiểu sâu về loại danh sách liên kết đơn - làm việc với danh sách động với các thao tác thêm, xóa, sửa.
- Có hai loại danh sách đặc biệt được ứng dụng nhiều trong thực tế đó là ngăn xếp (Stack) và hàng đợi (Queue). Ngăn xếp là loại danh sách hoạt động theo cơ chế LIFO (vào sau ra trước) còn Hàng đợi là loại danh sách hoạt động theo cơ che FIFO (vào trước ra trước). Trên thực tể khi gặp bài toán nào mà bắt buộc phải làm việc theo cơ chế của Ngăn xểp hoặc Hàng đợi thì chúng ta nên áp dụng nó để giải quyết bài toán.
CÂU HỎI VẢ BẢI TÁP
1. Biến con trỏ là gì? Nêu các thao tác trên biển con trỏ 2. Nêu sự khác nhau giữa biến tĩnh và biến động
3. Cho biết kết quả xuất ra màn hình của đoạn code sau:
int *p = new int; int X = 10;
int *q = &x; *p = 20; X + + ; cout « *q « endl; q = p; (*q)++; cout « *p « endl; P++; cout « p - q « endl;
4. Xây dựng cấu trúc dữ liệu để mô tả danh sách đặc chứa các số nguyên. Biết rằng để quản lý một danh sách đặc thì cần phải lưu trữ một mảng các phần tử và tổng số lượng phần tử. Dựa vào cấu trúc xây dựng được hãy viết chưorng trình để thể hiện đủ các thao tác trên danh sách đặc (lưu ý các thao tác này đã được giới thiệu ở phần 4.2 - Các thao tác trên danh sách)
5. Viết chương trình sắp xếp một danh sách chứa các số nguyên, trong các trường hợp:
a. Danh sách được cài đặt bằng mảng (danh sách đặc). b. Danh sách được cài đặt bằng con trỏ (danh sách liên kết).
6. Viết chương trình thêm một phần tử trong danh sách đã có thứ tự sao cho ta vẫn có một danh sách có thứ tự bằng cách vận dụng các phép toán cơ bản trên danh sách
7. Viết chương trình tìm kiếm và xóa một phần tử trong danh sách có thứ tự. 8. Viết chương trình loại bỏ các phần tử trùng nhau (giữ lại duy nhất 1 phần tử)
trong một danh sách có thứ tự không giảm, trong hai trường hợp: cài đặt bằng mảng và cài đặt danh sách liên kết đơn.
9. Viết chương trình trộn hai danh sách liên kết chứa các số nguyên theo thứ tự tăng để được một danh sách cũng có thứ tự tăng.
10. Viết chương trình xố khỏi danh sách lưu trữ các số nguyên các phần tử là số nguyên lẻ, cũng trong hai trường họp: cài đặt bằng mảng và bằng con trỏ. 11. Viết chương trình con tách một danh sách chứa các số nguyên thành hai danh
sách: một danh sách gồm các số chẵn còn cái kia chứa các sổ lẻ.
12. Viết chương trình để quản lý danh sách sinh viên của một lớp học, biết rằng mồi sinh viên được mô tả bởi mã sinh viên, họ tên, lớp, ngày sinh và điểm trung bình. Chương trình có những chức năng sau:
a. Nhập danh sách động lưu các sinh viên b. Xuất danh sách ra màn hình
c. sẳp xếp danh sách sinh viên tăng dần theo họ tên dùng giải thuật chọn trực tiếp
d. Giáo viên chủ nhiệm cần xét học bổng cho sinh viên trong lóp. Hãy giúp giáo viên xuất ra được tối đa 5 bạn được học bổng. Biết rằng để đạt học bổng tlù sinh viên đó phải có điểm trung bình lớn hơn hoặc băng 7
e. Tim và xóa sinh viên có mã sinh viên là X, với X nhập từ bàn phím
13. Tổ chức dữ liệu quản lí danh mục các bộ phim VIDEO, các thông tin liên quan đến bộ phim này như sau:
- Tên phim (tựa phim).
- Thể loại (3 loại : hình sự, tình cảm, hài). - Tên đạo diễn.
- Tên diễn viên nam chính. - Tên diễn viên nữ chính. - Năm sản xuất.
- Hãng sản xuất
Viết chương trình thực hiện những công việc sau :
a. Nhập vào danh sách các bộ phim cùnệ với các thông tin liên quan đến bộ phim này. Lưu ý yêu cầu lưu trữ bằng danh sách liên kết đơn. b. Nhập một thể loại: In ra danh sách các bộ phim thuộc thể loại này. c. Nhập một tên nam diễn viên. In ra các bộ phim có diễn viên này đóng. d. Nhập tên đạo diễn. In ra danh sách các bộ phim do đạo diễn này dàn
14. M ột thư viện cần quản lý thông tin về các đầu sách. Mồi đầu sách bao gồm các thông tin sau : MaSSach (mã số sách), TenSach (tên sách), TacGia (tác giả), SL (số lượng các cuốn sách của đầu sách). Viết chng trình thực hiện các chức năng sau:
a. Nhập vào một danh sách liên kết đon gồm các đầu sách
b. Nhập vào tên của quyển sách. In ra thông tin đầy đủ về các sách có tên đó, nếu khơng có thi tên của quyển sách đó thì báo là :Khơng Tim Thấy.
c. Tính tổng số sách có trong thư viện.
15. Nhập vào một biểu thức trung tố, chuyển đổi thành biểu thực hậu tố và tính giá trị của biểu thức. Ví dụ: (20+5)*3+(10/5) = > 2 0 5 + 3 * 1 0 5 / +
Kết quả: 77
16. Viết chng trình mơ phỏng hàng đợi mua vé xem phim. Thông tin của việc đăng ký gồm: họ tên, địa chỉ, tuổi và số ghế.
CHƯƠNG 6. CẤ U T R Ú C C Â Y
MUC TIÊU
❖ Nắm vững khái niệm về cây (trees), cây nhị phân tìm kiểm, cây nhị phân tìm kiếm cân bằng.
♦♦♦ Trình bày được các cách duyệt cây.
❖ Trình bày cấu trúc dữ liệu của cây nhị phân, cây nhị phân tìm kiểm cân bằng.
♦> Trình bày được các trường hợp mất cân bằng và cách cân bằng lại cây khi mất cân bằng.
Kiến thức cơ bản cần thiết
Đe học tốt chương này, sinh viên phải nắm vững kỹ năng lập trình căn bản như:
❖ Kiểu con trỏ (pointer)
♦♦♦ Các cấu trúc điều khiển, lệnh vòng lặp
❖ Lập trình theo từng module (chương trình con) và cách gọi chương trình con đó.
❖ Lập trình đệ qui và gọi đệ qui. ❖ Kiểu dữ liệu danh sách
1. KHÁI N Ệ M CÂY
Cây là một tập họp các phần tử gọi 1 nút (nodes) trong đó có một nút được phân biệt gọi là nút gốc (root). Trên tập hợp các nút này có một quan hệ, gọi là mối quan hệ cha - con (parenthood), để xác định hệ thống cấu trúc trên các nút. Mồi nút, trừ nút gốc, có duy nhất một nút cha. M ột nút có thể có nhiều nút con hoặc khơng có nút con nào. Mồi nút biểu diễn một phần tử trong tập họp đang xét và nó có thể có một kiểu nào đó bất kỳ, thường ta biểu diễn nút bằng một kí tự, một chuồi hoặc một số ghi trong vòng tròn. Mồi quan hệ cha con được biểu diễn theo
qui ước nút cha ở dòng trên nút con ở dòng dưới và được nôi bởi một đoạn thăng một cách hình thức ta có thể định nghĩa cây một cách đệ quy như sau:
Định nghĩa:
❖ Một nút đon độc lập là một cây. Nút này cũng chính là nút góc của cây. ❖ Giả sử ta có n là một nút đon độc và k cây T I T k với các nút góc tưong
ứng là n l n k thì có thể xây dựng một cây mới bằng cách cho nút n là cha của các nút nl,.., nk. Cây mới này có nút gốc là nút n và các cây Tl,.., Tk được gọi là các cây con. Tập rồng cũng được coi là một cây và gọi là cây rỗng.
Ví dụ: