1 .GIỚI THIỆU
4. KIỂU DỮ LIỆU DANH SÁCH
5.1.6 Vấn đề và giải pháp
Bài toán 1: Sử dụng stack để kiểm tra xâu ngoặc đúng Giải pháp:
a. Tạo stack
b. while (chưa hết chuỗi) {
1. Nếu kí tự khơng phải là kí hiệu cần kiểm tra thì bỏ qua 2. Nếu kí tự là 1 kí hiệu mở như (, [, { thì đẩy vào stack
97
3. Nếu kí tự là kí hiệu đóng như ), ], } thì nếu stack rỗng (tức trước đó chưa có kí tự mở) thì báo lỗi, ngược lại pop từ stack 1 kí tự
4. Nếu kí hiệu được lấy ra khỏi stack khơng tương ứng cặp với kí hiệu mở thì báo lỗi
}
c. Khi hết chuỗi mà nếu stack vẫn cịn phần tử (tức dư kí hiệu mở) thì cũng thơng báo lỗi.
Ví dụ:
(A+B)+(C-D) là chuỗi đúng
[(A+B)+(C-D) khơng là chuỗi đúng vì thiếu một dấu đóng của [ [(A+B)+(C-D) } ] không là chuỗi đúng
[{(A+B)+(C-D) } ] là chuỗi đúng
Bài tốn 2: Tính giá trị biểu thức hậu tố dùng Stack
Giải pháp: Trước khi đưa ra phương pháp chúng ta xem biểu thức trung tố, tiền tố,
hậu tố thơng qua bảng ví dụ sau:
Trung tố (infix) Tiền tố (prefix) Hậu tố (posfix)
Toán tử đứng giữa hai toán hàng
Toán tử đứng trước hai toán hạng
Toán tử đứng sau hai toán hạng
A A A
A + B +AB AB+
(A + B) + (C - D) ++AB-CD AB+CD-+
(A+B)*C-D -*+ABCD AB+C*D-
Giải thuật tính giá trị biểu thức hậu tố dùng stack: 1. Duyệt chuỗi từ trái qua phải
98
2. Khởi tạo Stack rỗng
3. Lặp lại bước 4 và 5 cho tới khi duyệt hết chuỗi 4. Nếu kí tự hiện tại là một tốn hạng thì đẩy vào stack 5. Nếu kí tự hiện tại là một tốn tử.
a. Nếu tốn tử 1 ngơi thì lấy 1 phần tử từ stack. b. Nếu tốn tử 2 ngơi thì lấy 2 phần tử từ stack.
Sau đó thực hiện phép tính cho tốn tử và tốn hạng vừa lấy ra. Kết quả thu được sẽ đẩy lại vào stack.
6. Sau khi tất cả kí tự được duyệt hết, thì chúng ta sẽ thu được chỉ một giá trị ở trong stack. Đó là kết quả của biểu thức
7. Trả về phần tử trên đỉnh stack.
Ví dụ: cho chuỗi hậu tố như sau s = “123*+5-“
Đầu tiên duyệt từ trái qua phải gặp 1, 2, 3 đẩy vào stack
Kế tiếp gặp toán tử *, pop 2 phần tử từ đỉnh stack ra. Và thực hiện phép toán * cho 2 toán hạng vừa lấy ra khỏi stack.
99
Kết quả thu được đẩy lại vào Stack:
Tiếp theo gặp toán tử “+”, pop 2 phần tử hiện tại ở đỉnh stack ra, sau đó thực hiện phép + cho 2 toán hạng này và cũng đẩy kết quả vào stack.
100
Kế tiếp gặp toán hạng 5, đẩy vào stack:
Gặp toán tử -, thực hiện tương tự như trên ta sẽ thu được những hình dưới đây cho tới khi hết chuỗi:
101
Kết quả cuối cùng:
Chuỗi hậu tố: 123*+5-
Giá trị của biểu thức hậu tố: 2
5.2 | HÀNG ĐỢI (QUEUE)
5.2.1 | Khái niệm
Một hàng đợi là một cấu trúc dữ liệu dùng để lưu trữ dữ liệu (tương tự như Linked List và stack). Trong hàng đợi, trật tự của các phần tử rất quan trọng. Ví dụ về hàng đợi như một hàng người đang xếp hàng để mua vé.
Vậy một hàng đợi (queue) là một danh sách có trật tự mà mỗi phần tử sẽ được chèn vào cuối hàng đợi (rear) và khi lấy phần tử ra khỏi hàng đợi sẽ lấy ra từ một đầu khác đó là đầu hàng đợi (end). Phần tử được đưa vào hàng đợi trước thì sẽ được đưa ra hàng đợi trước. Hàng đợi là một danh sách làm việc theo cơ chế First in First out (FIFO – Vào trước ra trước) hoặc Last in Last out (LILO – vào sau ra sau).
102
Tương tự như stack, hàng đợi có hai thao tác chính đó là thêm phần tử vào cuối hàng đợi, gọi là enQueue, và lấy phần tử ra khỏi đầu hàng đợi gọi deQueue.
Thao tác deQueue thực hiện trên hàng rỗng gọi là underflow và thao tác enQueue thực hiện trên hàng đợi đầy gọ là overflow. Chúng ta sẽ xử lý hai trường hợp trên như là những ngoại lệ (exception): “Empty queue exception” và “Full queue excepton”.
5.2.2 | Queue ADT
Các thao tác sau sẽ làm cho một hàng đợi trở thành một ADT (Abstract Data Type). Việc thêm vào hay lấy phần tử ra khỏi hàng đợi phải tuân thủ theo nguyên tắc FIFO. Các thao tác sau, giả định kiểu dữ liệu của phần tử là kiểu số nguyên.
Các thao tác chính:
enQueue(int data): Chèn một phần tử vào cuối hàng đợi int deQueue(): Xóa và trả về phần tử ở trước hàng đợi. Các thao tác bổ trợ:
int front(): Trả về phần tử ở đầu hàng đợi mà khơng xóa khỏi hàng int queeSize(): Trả về số lượng phần tử của hàng đợi
int isEmptyQueue(): Chỉ ra hàng đợi rỗng hay không
Rear
103
5.2.3 | Ứng dụng queue
Trong tin học, 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...
5.2.4 | Cài đặt queue bằng mảng
Có thể cài đặt hàng đợi bằng mảng với phương pháp tịnh tiến hoặc có thể dùng mảng vòng.
Sau đây là phương pháp cài đặt hàng đợi dù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:
104
105
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ương pháp tịnh tiến.
106
Hàng đợi sau khi thêm 105:
Thêm tiếp 37 vào thì hàng đợi đầy thật. Cài đặt:
107
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 thực sự hết bộ nhớ của hàng đợi), cho nên nếu chỉ viết:
108
Hàm lấy một phần tử ra khỏi hàng đợi (deQueue):
Nếu hàng đợi chỉ còn một phần tử. Ta xét các trường hợp sau: Hàng đợi trước khi lấy ra 50:
109
Sau khi lấy 50 ra thì hàng đợi rỗng. Do đó ta phải cập nhật lại ngay Front=-1 và Rear=-1.
111