1 .GIỚI THIỆU
4. KIỂU DỮ LIỆU DANH SÁCH
4.7 VẤN ĐỀ VÀ GIẢI PHÁP
Bài tốn 1: Tìm phần tử thứ n tính từ cuối của một danh sách liên kết
Giải pháp: Phương pháp Frute-Force: Bắt đầu từ phần tử đầu tiên và đếm số lượng
phần tử ở phía sau phần tử này (gọi là nCountAfter). Để đếm số lượng cịn lại ở phía sau phần tử hiện hành phải dùng 1 vòng lặp để đếm. Nếu nCountAfter < n – 1 thì kết thúc và nói “số lượng phần tử của danh sách ít hơn n”. Nếu nCountAfter > n – 1 thì tiếp tục đi tiếp sang phần tử kế tiếp và lặp lại việc đếm cho tới khi thu được nCountAfter = n – 1. Khi đó phần tử thứ n là phần tử hiện hành mà tại đó thu được nCountAfter = n – 1.
Độ phức tạp: O(n2) vì phải chạy duyệt số phần tử cịn lại với mỗi node.
Bài toán 2: Cải thiện bài toán 1 với một lần lặp.
Phương pháp 1: Để tìm ra phần tử thứ n chúng ta chỉ cần duyệt danh sách đúng 2 lần:
Lần thứ nhất: duyệt tồn bộ danh sách để có được số lượng phần tử M
Vậy lần duyệt thứ hai, chỉ cần đi từ đầu danh sách và đếm đến phần tử M – n + 1 thì dừng lại và phần tử hiện hành chính là phần tử thứ n.
Phương pháp 2: Sử dụng hai con trỏ pNthNode và pTemp.
Đầu tiên cả 2 con trỏ này đều xuất phát từ phần tử đầu tiên của danh sách. Nhưng sau khi pTemp di chuyển được n phần tử thì pNthNode mới bắt đầu di chuyển. Từ đây cả 2 con trỏ đều di chuyển đồng thời về cuối danh sách cho tới khi pTemp đi tới cuối danh sách thì pNthNode sẽ chỉ vào phần tử thứ n tính từ cuối danh sách.
83
Bài tốn 3: Chèn một phần tử vào một danh sách liên kết đã được sắp xếp Giải pháp: Duyệt danh sách và tìm một vị trí thích hợp và chèn vào.
84
Bài toán 4: Đảo danh sách liên kết theo từng cặp. Ví dụ một danh sách liên kết 12
3 4 x, thì sau khi xử lý danh sách liên kết sẽ là 2143x.
Bài toán 5: Cho một danh sách liên kết chứa số chẵn, số lẻ. Hãy viết một giải thuật để
thay đổi danh sách sao cho tất cả các số chẵn sẽ xuất hiện ở phần đầu của danh sách.
BÀI TẬP
1. Liệt kê các số nguyên tố trong DSLK đơn
2. Xóa k phần tử ở đầu danh sách (k nhập từ bàn phím) 3. Xóa phần tử ở vị trí thứ k trong danh sách
4. Xóa tất cả các phần tử lặp lại trong DSLK đơn
5. Viết hàm Count() thực hiện việc đếm các lần xuất hiện của một số nguyên nhập từ bàn phím trong một danh sách.
6. Viết hàm Nth() trả về đối tượng thứ n trong danh sách tính từ đầu danh sách 7. Tìm phần tử thứ n tính từ cuối của một danh sách liên kết
85
9. Đảo danh sách liên kết theo từng cặp. Ví dụ một danh sách liên kết 12 3
4 x, thì sau khi xử lý danh sách liên kết sẽ là 2143x.
10. 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ự tăng dần, 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.
11. Thêm một phần tử vào danh sách sao cho phần tử thêm vào không trùng với các phần tử trong danh sách.
12. Trong chương trình chính cho phép tạo ra linked List L1 và thực hiện các chức năng trên. Sau đó tạo thêm một linke list L2 và xây dựng thêm hàm để:
a. Kiểm tra 2 danh sách L1 và L2 có cùng giá trị hay khơng?
b. Xóa một phần tử đầu tiên trong L1 thõa mãn điều kiện: Giá trị của nó lớn hơn tổng giá trị phần tử của L2
13. 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 thì sinh viên đó phải có điểm trung bình lớn hơn hoặc bằng 7 e. Tìm và xóa sinh viên có mã sinh viên là x, với x nhập từ bàn phím
14. 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 điễn viên nam chính. - Tên diễn viên nữ chính.
86
- 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ùng 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
dựng.
15. Hãy dùng LinkedList để viết chương trình cho phép tạo ra 2 đa thức, sau đó cộng 2 đa thức. Ví dụ:
87
5. NGĂN XẾP, HÀNG ĐỢI
Giới thiệu:
Trong chương này giới thiệu hai kiểu dữ liệu mới đó là Ngăn xếp (Stack) và hàng đợi (Queue).
Mục tiêu:
- So sánh cấu trúc Stack và Queue
- Cài đặt cấu trúc dữ liệu Stack và Queue cũng như bộ thao tác trên từng kiểu dữ liệu.
88
5.1 | NGĂN XẾP (STACK)
5.1.1 | Khái niệm
Một ngăn xếp (stack) là một cấu trúc dữ liệu đơn giản được sử dụng để lưu trữ dữ liệu (tương tự như Linked List). Trong một Stack, trật tự lưu trữ dữ liệu rất quan trọng. Một chồng đĩa là một ví dụ về stack. Mỗi chiếc đĩa khi đưa vào stack đều được đặt trên đỉnh và chiếc đĩa đầu tiên được đặt vào stack chính là chiếc cuối cùng được sử dụng.
Vậy một Stack là một danh sách có thứ tự trong đó việc thêm phần tử vào hay lấy ra
đều diễn ra tại một đầu, gọi là đỉnh (top) của Stack. Phần tử cuối cùng đưa vào Stack chính là phần tử đầu tiên sẽ được lấy ra. Vì thế Stack được gọi là một danh sách làm việc theo cơ chế LIFO (Last in First out – vào sau ra trước) hoặc FILO (First in Last out – Vào trước ra sau).
Khi một phần tử được đưa vào đỉnh Stack, gọi là push, và khi một phần tử được lấy ra từ đỉnh Stack, gọi là pop. Khi Stack đang rỗng, nếu chúng ta cố gắng lấy ra (pop out) thì trường hợp này được gọi là underflow và ngược lại khi Stack đã đầy, nếu chúng ta cố gắng đưa phần tử vào thì trường hợp này gọi là overflow. Cả hai trường hợp này
89
5.1.2 | Stack ADT
Những thao tác sau sẽ làm stack trở thành ADT (abstract data type – kiểu dữ liệu trừu tượng). Để tiện cho việc minh họa, giả sử dữ liệu của phần tử trong stack là kiểu int.
Các thao tác chính
push(int data): Chèn data vào Stack.
int pop(): Xóa và trả về phần tử trên đỉnh Stack.
Các thao tác bổ trợ
int top(): trả về phần tử trên đỉnh Stack mà không lấy ra khỏi Stack. int size(): Trả về số lượng phần tử được lưu trữ trong Stack
int isEmptyStack(): Kiểm tra Stack có rỗng hay khơng int isFullStack(): Kiểm tra Stack có đầy hay khơng
Ngoại lệ
Ngoại lệ là việc cố gắng thực hiện một thao tác nào đó mà có thể gây ra lỗi. Trong stack ADT, các thao tác pop và top không thể được thực hiện nếu stack rỗng. Nếu cố gắng pop (top) trên một stack rỗng thì sẽ tạo ra (throw) một ngoại lệ. Tương tự nếu cố gắng push một phần tử vào trong một stack đã đầy sẽ tạo ra một ngoại lệ.
5.1.3 | Ứng dụng stack
Sau đây là một vài ứng dụng sử dụng stack và cho thấy được tầm quan trọng của stack:
Kiểm tra xâu ngoặc đúng (Balancing of symbols) Chuyển biểu thức trung tố (infix) sang hậu tố (posfix) Tính giá trị của biểu thức hậu tố (posfix expression)
90 Cơ chế gọi hàm (bao gồm cả đệ quy)
Lịch sử truy cập trang web trên web browser [Nút Back] Trình tự Undo trong mơi trường soạn thảo văn bản Kiểm tra sự phù hợp của thẻ trong thẻ HTML và XML
5.1.4 | Cài đặt stack bằng mảng
Trong mảng, chúng ta thêm các phần tử từ trái sang phải và sử dụng một biến để lưu lại vị trí của phần tử trên đỉnh của stack.
Việc sử dụng mảng để lưu trữ phần tử của Stack có thể bị đầy. Thao tác push sẽ tạo ra một ngoại lệ (full stack). Tương tự, thao tác pop sẽ tạo ra một ngoại lệ (stack empty).
92
Hạn chế của phương pháp này: Kích thước tối đa của stack phải được định trước và
khơng thể thay đổi. Vì thế nếu cố thêm một phần tử vào stack đã đầy thì sẽ tạo ra một ngoại lệ. Ngồi ra nếu chúng ta khơng muốn phải cố định kích thước trước thì vẫn có
93
một giải pháp đó là dùng mảng động về kích thước (Dynamic Array). Tức là tại thời điểm ban đầu chúng ta định sẵn sức chứa là 1, sau đó mỗi lần thêm phần tử vào Stack chúng ta sẽ tăng kích thước mảng bằng cách xin cấp phát lại và sao chép dữ liệu qua, sau đó đưa phần tử vào. Để tăng kích thước mảng thì có hai cách tiếp cận:
Cách 1: mỗi lần tăng kích thước mảng lên 1. Với cách này chi phí bỏ ra để sao chép lại mảng mỗi khi có 1 phần tử thêm vào là quá lớn O(n2)
Cách 2: Mỗi lần thêm một phần tử thì tăng gấp đơi kích thước mảng (phương pháp doubling). Với cách này chi phí có giảm chỉ cịn O(n). Nhưng với cách này nếu như nhân đơi nhiều lần thì sẽ có thể gây ra quá tải vùng nhớ.