Một KDLTT có thể cài đặt bởi các phương pháp khác nhau. Trong chương 4, chúng ta đã nghiên cứu cách cài đặt KDLTT danh sách bởi mảng (mảng tĩnh hoặc mảng động). Mục 5.4 đã trình bày cách cài đặt danh sách bởi DSLK. Trong mục này chúng ta sẽ phân tích đánh giá ưu khuyết điểm của mỗi phương pháp. Dựa vào sự phân tích này, bạn có thể đưa ra quyết
Cài đặt danh sách bởi mảng là cách lựa chọn tự nhiên hợp lý: các phần tử của danh sách lần lượt được lưu trong các thành phần liên tiếp của mảng, kể từ đầu mảng. Giả sử bạn cài đặt danh sách bởi mảng tĩnh có cỡ là MAX. Nếu MAX là số rất lớn, khi danh sách còn ít phần tử, thì cả một không gian nhớ rộng lớn trong mảng không sử dụng đến, và do đó sẽ lãng phí bộ nhớ. Khi danh sách phát triển, tới một lúc nào đó nó có thể có số phần tử vượt quá cỡ của mảng. Đó là hạn chế cơ bản của cách cài đặt danh sách bởi mảng tĩnh. Bây giờ giả sử bạn lưu danh sách trong mảng động. Mỗi khi mảng đầy, bạn có thể cấp phát một mảng động mới có cỡ gấp đôi mảng động cũ. Nhưng khi đó bạn lại phải mất thời gian để sao chép dữ liệu từ mảng cũ sang mảng mới. Sự lãng phí bộ nhớ vẫn xảy ra khi mà danh sách thì ngắn mà cỡ mảng thì lớn.
Trong cách cài đặt danh sách bởi DSLK, các phần tử của danh sách được lưu trong các thành phần của DSLK, các thành phần này được cấp phát động. DSLK có thể móc nối thêm các thành phần mới hoặc loại bỏ các thành phần trả về cho hệ thống mỗi khi cần thiết. Do đó cài đặt danh sách bởi DSLK sẽ tiết kiệm được bộ nhớ.
Một ưu điểm của cài đặt danh sách bởi mảng là ta có thể truy cập trực tiếp tới mỗi phần tử của danh sách. Nếu ta cài đặt danh sách bởi mảng A, thì phần tử thứ i trong danh sách được lưu trong thành phần A[i - 1] của mảng, do đó thời gian truy cập tới phần tử bất kỳ của danh sách là O(1). Vì vậy thời gian của phép toán tìm phần tử thứ i trong danh sách Element(i) là O(1), với i bất kỳ.
Song nếu chúng ta sử dụng DSLK để cài đặt danh sách, thì chúng ta không có cách nào truy cập trực tiếp tới thành phần của DSLK chứa phần tử thứ i của danh sách. Chúng ta phải sử dụng con trỏ P chạy trên DSLK bắt đầu từ đầu, lần lượt qua các thành phần kế tiếp để đạt tới thành phần chứa phần tử thứ i của danh sách. Do vậy, thời gian để thực hiện phép toán tìm phần tử thứ i của danh sách khi danh sách được cài đặt bởi DSLK là phụ thuộc vào i và là O(i).
Nếu danh sách được cài đặt bởi mảng A, thì để xen một phần tử mới vào vị trí thứ i trong danh sách (hoặc loại khỏi danh sách phần tử ở vị trí thứ i), chúng ta phải “đẩy” các phần tử của danh sách chứa trong các thành phần của mảng kể từ A[i] ra phía sau một vị trí (hoặc đẩy lên trước một vị trí các phần tử chứa trong các thành phần kể từ A[i + 1]). Do đó, các phép toán Insert(x,i) và Delete(i) đòi hỏi thời gian O(n – i), trong đó n là độ dài của danh sách.
Mặt khác, nếu cài đặt danh sách bởi DSLK thì để thực hiện phép toán xen, loại ở vị trí thứ i của danh sách, chúng ta lại phải mất thời gian để định vị thành phần của DSLK chứa phần tử thứ i của danh sách (bằng cách cho con trỏ P chạy từ đầu DSLK). Do đó, thời gian thực hiện các phép toán xen, loại là O(i).
Thời gian thực hiện các phép toán danh sách trong hai cách cài đặt bởi mảng và bởi DSLK được cho trong bảng sau, trong bảng này n là độ dài của danh sách.
Phép toán Danh sách cài đặt bởi mảng Danh sách cài đăt bởi DSLK
Insert (x, i) Delete (i) Append (x) Element (i) Add (x) Remove ( ) Current ( ) O(n – i) O(n – i) O(1) O(1) O(n) O(n) O(1) O(i) O(i) O(1) O(i) O(1) O(1) O(1) 5.6 CÀI ĐẶT TẬP ĐỘNG BỞI DSLK
Trong mục 4.4, chúng ta đã nghiên cứu phương pháp cài đặt tập động bởi mảng. Ở đó lớp tập động DSet đã được cài đặt bằng cách sử dụng lớp danh sách động Dlist như lớp cơ sỏ private. Đương nhiên chúng ta cũng có thể sử dụng lớp danh sách liên kết LList như lớp cơ sở private để cài đặt lớp DSet. Song cài đặt như thế thì phép toán tìm kiếm trên tập động sẽ đòi hỏi thời gian không phải là O(n) như khi sử dụng lớp DList. Hàm tìm kiếm tuần tự trong lớp DSet sử dụng lớp cơ sở DList (xem mục 4.4) chứa dòng lệnh:
for (int i = 1; i < = length( ); i + +) if (Element (i). key = = k) return true;
Trong lớp DList, thời gian của phép toán Element(i) là O(1) với mọi i, do đó thời gian của phép toán tìm kiếm là O(n), với n là độ dài của danh sách. Tuy nhiên trong lớp LList, thời gian của Element(i) là O(i), do đó thời gian của phép toán tìm kiếm trên tập động nếu chúng ta cài đặt lớp DSet bằng cách sử dụng lớp LList làm lớp cơ sở private sẽ là O(n2).
Một cách tiếp cận khác để cài đặt tập động bởi DSLK là chúng ta biểu diễn tập động bởi một List với List là một đối tượng của lớp LList. Lớp DSet sẽ chứa một thành phần dữ liệu là List.
Cũng như trong mục 4.4, chúng ta giả thiết rằng tập động chứa các dữ liệu có kiểu Item, và Item là một cấu trúc chứa một thành phần là khoá (key) với kiểu là keyType. Lớp DSet được định nghĩa như sau:
template <class Item>
class DSet {
bool Search (keyType k) const ; Item & Max( ) const ;
Item & Min( ) const ;
private :
LList <Item> List; };
Chú ý rằng, lớp DSet có các hàm sau đây được tự động thừa hưởng từ lớp LList:
• Hàm kiến tạo mặc định tự động (hàm kiến tạo copy tự động), nó
kích hoạt hàm kiến tạo mặc định (hàm copy, tương ứng) của lớp LList để khởi tạo đối tượng List.
• Toán tử gán tự động, nó kích hoạt toán tử gán của lớp LList.
• Hàm huỷ tự động, nó kích hoạt hàm huỷ của lớp LList.
Các phép toán tập động sẽ được cài đặt bằng cách sử dụng các phép toán bộ công cụ lặp để duyệt DSLK List. Chẳng hạn, hàm tìm kiếm được cài đặt như sau:
template <class Item>
bool DSet<Item> :: Search(keyType k) {
LListIterator<Item> It<List> ; // Khởi tạo It là đối tượng của lớp // công cụ lặp, It gắn với List. for (It.Start( ) ; It.Valid( ); It.Advance( ))
if (It.Current( ).key = = k)
return true;
return false; }
Hàm loại khỏi tập động dữ liệu với khoá k được cài đặt tương tự: duyệt DSLK List, khi gặp dữ liệu cần loại thì sử dụng hàm Remove( ) trong bộ công cụ lặp.
template <class Item>
void DSet<Item> :: DsetDelete (keyType k) {
LListIterator<Item> It(List);
for (It.Start( ); It.Valid( ); It.Advance( ))
if (It.Current( ).key = = k) { It.Remove( ) ; break); } }
Hàm xen một dữ liệu mới x vào tập động được thực hiện bằng cách gọi hàm Append trong lớp LList để xen x vào đuôi DSLK.
template <class Item>
void DSet<Item> :: DsetInsert(const Item & x) {
if (! Search (x.key)) List.Append(x); }
Các phép toán tìm phần tử có khoá lớn nhất (hàm Max), tìm phần tử có khoá nhỏ nhất (hàm Min) để lại cho độc giả , xem như bài tập.
Các phép toán trong bộ công cụ lặp chỉ cần thời gian O(1), do đó với cách cài đặt lớp DSet như trên, tất cả các phép toán trong tập động chỉ đòi hỏi thời gian O(n), với n là số dữ liệu trong tập động.
Chú ý. Khi cài đặt tập động bởi DSLK chúng ta chỉ có thể tìm kiếm tuần tự. Cho dù các dữ liệu của tập động được lưu trong DSLK lần lượt theo giá trị khoá tăng dần, chúng ta cũng không thể áp dụng kỹ thuật tìm kiếm nhị phân, lý do đơn giản là trong DSLK chúng ta không thể truy cập trực tiếp tới thành phần ở giữa DSLK.
BÀI TẬP.
1. Cho DSLK đơn với con trỏ ngoài head trỏ tới đầu DSLK, và P là con trỏ trỏ tới một thành phần của DSLK đó. Hãy viết ra các mẫu hàm và cài đặt các hàm thực hiện các nhiệm vụ sau:
a. Xen thành phần mới chứa dữ liệu d vào trước P. b. Loại thành phần P.
c. In ra tất cả các dữ liệu trong DSLK.
d. Loại khỏi DSLK tất cả các thành phần chứa dữ liệu d.
2. Cho hai DSLK, hãy viết hàm kết nối hai DSLK đó thành một DSLK vòng tròn, chẳng hạn với hai DSLK:
ta nhận được DSLK vòng tròn sau:
tail
Chú ý rằng, một trong hoặc cả hai DSLK đã cho có thể rỗng.
3. Cho DSLK vòng tròn với con trỏ ngoài tail trỏ tới đuôi DSLK. Hãy cài đặt các hàm sau:
a. Xen thành phần mới chứa dữ liệu d vào đuôi DSLK. b. Xen thành phần mới chứa dữ liệu d vào đầu DSLK. c. Loại thành phần ở đầu DSLK.
4. Hãy cài đặt các hàm kiến tạo copy, hàm huỷ, toán tử gán trong lớp Dlist bởi các hàm đệ quy.
5. Hãy cài đặt lớp Llist, trong đó danh sách được cài đặt bởi DSLK vòng tròn với một con trỏ ngoài tail.