Tổng quan,khái niệm các phép toán trên danh sách
Mục tiêu Sau khi học xong chương này, sinh viên Nắm vững các kiểu dữ liệu trừu tượng như: danh sách, ngăn xếp, hàng đợi. Cài đặt các kiểu dữ liệu bằng ngôn ngữ lập trình cụ thể. Ứng dụng ...
Mục tiêu
Sau khi học xong chương này, sinh viên
- Nắm vững các kiểu dữ liệu trừu tượng như: danh sách, ngăn xếp, hàng đợi.
- Cài đặt các kiểu dữ liệu bằng ngôn ngữ lập trình cụ thể.
- Ứng dụng được các kiểu dữ liệu trừu tượng trong bài toán thực tế.
Kiến thức cơ bản cần thiết
Để 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 cấu trúc (struct) , kiểu mảng và kiểu con trỏ.
- Các cấu trúc điều khiển, lệnh vòng lặp.
- Lập trình theo từng modul (chương trình con) và cách gọi chương trình con đó.
Tài liệu tham khảo
[1] Aho, A. V. , J. E. Hopcroft, J. D. Ullman. "Data Structure and Algorithms", Addison–Wesley; 1983 (chapter 2)
[2] Đỗ Xuân Lôi . "Cấu trúc dữ liệu và giải thuật". Nhà xuất bản khoa học và kỹ thuật. Hà nội, 1995 (chương 4,5 trang 71-119).
[3] Nguyễn Trung Trực, "Cấu trúc dữ liệu". BK tp HCM, 1990 (chương 2 trang 22-109).
[4] Lê Minh Trung ; “Lập trình nâng cao bằng Pascal với các cấu trúc dữ liệu “; 1997 (chương 7, 8)
Nội dung cốt lõi
Trong chương này chúng ta sẽ nghiên cứu một số kiểu dữ liệu trừu tượng cơ bản như sau:
- Kiểu dữ liệu trừu tượng danh sách (LIST)
- Kiểu dữ liệu trừu tượng ngăn xếp (STACK)
- Kiểu dữ liệu trừu tượng hàng đợi (QUEUE)
Mô hình toán học của danh sách là một tập hợp hữu hạn các phần tử có cùng một kiểu, mà tổng quát ta gọi là kiểu phần tử (Elementtype). Ta biểu diễn danh sách như là một chuỗi các phần tử của nó: a1, a2, . . ., anvới n ≥ 0. Nếu n=0 ta nói danh sách rỗng (empty list). Nếu n > 0 ta gọi a1 là phần tử đầu tiên và an là phần tử cuối cùng của danh sách. Số phần tử của danh sách ta gọi là độ dài của danh sách.
Một tính chất quan trọng của danh sách là các phần tử của danh sách có thứ tự tuyến tính theo vị trí (position) xuất hiện của các phần tử. Ta nói ai đứng trước ai+1, với i từ 1 đến n-1; Tương tự ta nói ailà phần tử đứng sau ai-1, với i từ 2 đến n. Ta cũng nói ai là phần tử tại vị trí thứ i, hay phần tử thứ i của danh sách.
Ví dụ: Tập hợp họ tên các sinh viên của lớp TINHOC 28 được liệt kê trên giấy như sau:
1. Nguyễn Trung Cang
2. Nguyễn Ngọc Chương
3. Lê Thị Lệ Sương
4. Trịnh Vũ Thành
5. Nguyễn Phú Vĩnh
là một danh sách. Danh sách này gồm có 5 phần tử, mỗi phần tử có một vị trí trong danh sách theo thứ tự xuất hiện của nó.
Để thiết lập kiểu dữ liệu trừu tượng danh sách (hay ngắn gọn là danh sách) ta phải định nghĩa các phép toán trên danh sách. Và như chúng ta sẽ thấy trong toàn bộ giáo trình, không có một tập hợp các phép toán nào thích hợp cho mọi ứng dụng (application). Vì vậy ở đây ta sẽ định nghĩa một số phép toán cơ bản nhất trên danh sách. Để thuận tiện cho việc định nghĩa ta giả sử rằng danh sách gồm các phần tử có kiểu là kiểu phần tử (elementType); vị trí của các phần tử trong danh sách có kiểu là kiểu vị trí và vị trí sau phần tử cuối cùng trong danh sách L là ENDLIST(L). Cần nhấn mạnh rằng khái niệm vị trí (position) là do ta định nghĩa, nó không phải là giá trị của các phần tử trong danh sách. Vị trí có thể là đồng nhất với vị trí lưu trữ phần tử hoặc không.
Các phép toán được định nghĩa trên danh sách là:
INSERT_LIST(x,p,L): xen phần tử x ( kiểu ElementType ) tại vị trí p (kiểu position) trong danh sách L. Tức là nếu danh sách là a1,a2, . , ap-1, ap ,. . , an thì sau khi xen ta có kết quả a1, a2, . . ., ap-1, x, ap, . . . , an. Nếu vị trí p không tồn tại trong danh sách thì phép toán không được xác định.
LOCATE(x,L) thực hiện việc định vị phần tử có nội dung x đầu tiên trong danh sách L. Locate trả kết quả là vị trí (kiểu position) của phần tử x trong danh sách. Nếu x không có trong danh sách thì vị trí sau phần tử cuối cùng của danh sách được trả về, tức là ENDLIST(L).
RETRIEVE(p,L) lấy giá trị của phần tử ở vị trí p (kiểu position) của danh sách L; nếu vị trí p không có trong danh sách thì kết quả không xác định (có thể thông báo lỗi).
DELETE_LIST(p,L) chương trình con thực hiện việc xoá phần tử ở vị trí p (kiểu position) của danh sách. Nếu vị trí p không có trong danh sách thì phép toán không được định nghĩa và danh sách L sẽ không thay đổi
NEXT(p,L) cho kết quả là vị trí của phần tử (kiểu position) đi sau phần tử p; nếu p là phần tử cuối cùng trong danh sách L thì NEXT(p,L) cho kết quả là ENDLIST(L). Next không xác định nếu p không phải là vị trí của một phần tử trong danh sách.
PREVIOUS(p,L) cho kết quả là vị trí của phần tử đứng trước phần tử p trong danh sách. Nếu p là phần tử đầu tiên trong danh sách thì Previous(p,L) không xác định. Previous cũng không xác định trong trường hợp p không phải là vị trí của phần tử nào trong danh sách.
FIRST(L) cho kết quả là vị trí của phần tử đầu tiên trong danh sách. Nếu danh sách rỗng thì ENDLIST(L) được trả về.
EMPTY_LIST(L) cho kết quả TRUE nếu danh sách có rỗng, ngược lại nó cho giá trị FALSE.
MAKENULL_LIST(L) khởi tạo một danh sách L rỗng.
Trong thiết kế các giải thuật sau này chúng ta dùng các phép toán trừu tượng đã được định nghĩa ở đây như là các phép toán nguyên thủy.
Ví dụ: Dùng các phép toán trừu tượng trên danh sách, viết một chương trình con nhận một tham số là danh sách rồi sắp xếp danh sách theo thứ tự tăng dần (giả sử các phần tử trong danh sách thuộc kiểu có thứ tự).
Giả sử SWAP(p,q) thực hiện việc đổi chỗ hai phần tử tại vị trí p và q trong danh sách, chương trình con sắp xếp được viết như sau:
void SORT(LIST L){
Position p,q;
//kiểu vị trí của các phần tử trong danh sách
p= FIRST(L);
//vị trí phần tử đầu tiên trong danh sách
while (p!=ENDLIST(L)){
q=NEXT(p,L);
//vị trí phần tử đứng ngay sau phần tử p
while (q!=ENDLIST(L)){
if (RETRIEVE(p,L) > RETRIEVE(q,L))
swap(p,q); // dịch chuyển nội dung phần tử
q=NEXT(q,L);
}
p=NEXT(p,L);
}
}
Tuy nhiên, cần phải nhấn mạnh rằng, đây là các phép toán trừu tượng do chúng ta định nghĩa, nó chưa được cài đặt trong các ngôn ngữ lập trình. Do đó để cài đặt giải thuật thành một chương trình chạy được thì ta cũng phải cài đặt các phép toán thành các chương trình con trong chương trình. Hơn nữa, trong khi cài đặt cụ thể, một số tham số hình thức trong các phép toán trừu tượng không đóng vai trò gì trong chương trình con cài đặt chúng, do vậy ta có thể bỏ qua nó trong danh sách tham số của chương trình con. Ví dụ: phép toán trừu tượng INSERT_LIST(x,p,L) có 3 tham số hình thức: phần tử muốn thêm x, vị trí thêm vào p và danh sách được thêm vào L. Nhưng khi cài đặt danh sách bằng con trỏ (danh sách liên kết đơn), tham số L là không cần thiết vì với cấu trúc này chỉ có con trỏ tại vị trí p phải thay đổi để nối kết với ô chứa phần tử mới. Trong bài giảng này, ta vẫn giữ đúng những tham số trong cách cài đặt để làm cho chương trình đồng nhất và trong suốt đối với các phương pháp cài đặt của cùng một kiểu dữ liệu trừu tượng.