Thao tác này hoàn toàn tương tự như trong danh sách liên kết đơn. - Thuật toán: B1: DLL_Initialize(NewList) B2: CurNode = DLL_List.DLL_First B3: IF (CurNode = NULL) Thực hiện Bkt B4: DLL_Add_Last(NewList, CurNode->Key) B5: CurNode = CurNode->NextNode B6: Lặp lại B3 Bkt: Kết thúc - Cài đặt thuật toán:
Hàm DLL_Copy có prototype:
DLLP_Type DLL_Copy (DLLP_Type &DList, DLLP_Type &NewList);
Hàm thực hiện việc sao chép nội dung danh sách DList thành danh sách NewList có cùng nội dung thành phần dữ liệu theo thứ tự của các nút trên DList. Hàm trả về giá trị của danh sách mới nếu việc sao chép thành công, ngược lại hàm trả về giá trị khởi tạo của danh sách.
Nội dung của hàm như sau:
DLLP_Type DLL_Copy (DLLP_Type &DList, DLLP_Type &NewList) { DLL_Initialize(NewList);
Giáo trình: Cấu Trúc Dữ Liệu và Giải Thuật while (CurNode != NULL)
{ if (DLL_Add_Last (NewList, CurNode->Key) == NULL) { DLL_Detete (NewList); break; } CurNode = CurNode->NextNode; } return (NewList); }
4.4.4. Ưu nhược điểm của danh sách liên kết
Do các phần tử (nút) được lưu trữ không liên tiếp nhau trong bộ nhớ, do vậy danh sách liên kết có các ưu nhược điểm sau đây:
- Mật độ sử dụng bộ nhớ của danh sách liên kết không tối ưu tuyệt đối (<100%); - Việc truy xuất và tìm kiếm các phần tử của danh sách liên kết mất nhiều thời gian
bởi luôn luôn phải duyệt tuần tự qua các phần tử trong danh sách;
- Tận dụng được những không gian bộ nhớ nhỏ để lưu trữ từng nút, tuy nhiên bộ nhớ lưu trữ thông tin mỗi nút lại tốn nhiều hơn do còn phải lưu thêm thông tin về vùng liên kết. Như vậy nếu vùng dữ liệu của mỗi nút là lớn hơn thì tỷ lệ mức tiêu tốn bộ nhớ này là không đáng kể, ngược lại thì nó lại gây lãng phí bộ nhớ.
- Việc thêm, bớt các phần tử trong danh sách, tách nhập các danh sách khá dễ dàng do chúng ta chỉ cần thay đổi mối liên kết giữa các phần tử với nhau.
4.5. Danh sách hạn chế
Trong các thao tác trên danh sách không phải lúc nào cũng có thể thực hiện được tất cả mà nhiều khi các thao tác này bị hạn chế trong một số loại danh sách, đó là danh sách hạn chế.
Như vậy, danh sách hạn chế là danh sách mà các thao tác trên đó bị hạn chế trong một chừng mực nào đó tùy thuộc vào danh sách. Trong phần này chúng ta xem xét hai loại danh sách hạn chế chủ yếu đó là:
- Hàng đợi (Queue); - Ngăn xếp (Stack).
4.5.1. Hàng đợi (Queue) A. Khái niệm - Cấu trúc dữ liệu:
Hàng đợi là một danh sách mà trong đó thao tác thêm một phần tử vào trong danh sách được thực hiện ở một đầu này và thao tác lấy ra một phần tử từ trong danh sách lại được thực hiện ở đầu kia.
Như vậy, các phần tử được đưa vào trong hàng đợi trước sẽ được lấy ra trước, phần tử đưa vào trong hàng đợi sau sẽ được lấy ra sau. Do đó mà hàng đợi còn được gọi là danh sách vào trước ra trước (FIFO List) và cấu trúc dữ liệu này còn được gọi là cấu trúc FIFO (First In – First Out).
Giáo trình: Cấu Trúc Dữ Liệu và Giải Thuật - Sử dụng danh sách đặc,
- Sử dụng danh sách liên kết,
Tuy nhiên, điều quan trọng và cần thiết là chúng ta phải quản lý vị trí hai đầu của hàng đợi thông qua hai biến: Biến trước (Front) và Biến sau (Rear). Hai biến này có thể cùng chiều hoặc ngược chiều với thứ tự các phần tử trong mảng và trong danh sách liên kết. Điều này có nghĩa là đầu hàng đợi có thể là đầu mảng, đầu danh sách liên kết mà cũng có thể là cuối mảng, cuối danh sách liên kết. Để thuận tiện, ở đây chúng ta giả sử đầu hàng đợi cũng là đầu mảng, đầu danh sách liên kết. Trường hợp ngược lại, sinh viên tự áp dụng tương tự.
Ở đây chúng ta sẽ biểu diễn và tổ chức hàng đợi bằng danh sách đặc và bằng danh sách liên kết đơn được quản lý bởi hai con trỏ đầu và cuối danh sách. Do vậy cấu trúc dữ liệu của hàng đợi cũng như các thao tác trên hàng đợi sẽ được trình bày thành hai trường hợp khác nhau.
- Biểu diễn và tổ chức bằng danh sách đặc: typedef struct Q_C
{ int Len; // Chiều dài hàng đợi int Front, Rear;
T * List; // Nội dung hàng đợi } C_QUEUE;
C_QUEUE CQ_List; Hình ảnh minh họa:
CQ_List Front Rear
T 15 10 20 18 40 35 30
Len = 14 - Biểu diễn và tổ chức bằng danh sách liên kết đơn;
typedef struct Q_Element { T Key;
Q_Element * Next; // Vùng liên kết quản lý địa chỉ phần tử kế tiếp } Q_OneElement;
typedef Q_OneElement * Q_Type; typedef struct QP_Element
{ Q_Type Front; Q_Type Rear; } S_QUEUE; S_QUEUE SQ_List;
Giáo trình: Cấu Trúc Dữ Liệu và Giải Thuật Hình ảnh minh họa:
SQ_List
Front Rear NULL
15 10 20 18 40 35 30
B. Các thao tác trên hàng đợi tổ chức bằng danh sách đặc:
Do hạn chế của danh sách đặc cho nên mỗi hàng đợi đều có một chiều dài cố định. Do vậy, trong quá trình thao tác trên hàng đợi có thể xảy ra hiện tượng hàng đợi bị đầy hoặc hàng đợi bị tràn.
- Khi hàng đợi bị đầy: số phần tử của hàng đợi bằng chiều dài cho phép của hàng đợi. Lúc này chúng ta không thể thêm bất kỳ một phần tử nào vào hàng đợi. - Khi hàng đợi bị tràn: số phần tử của hàng đợi nhỏ hơn chiều dài cho phép của
hàng đợi nhưng Rear = Len. Lúc này chúng ta phải khắc phục tình trạng tràn hàng đợi bằng cách dịch tất cả các phần tử của hàng đợi ra phía trước Front-1 vị trí hoặc xoay vòng để Rear chuyển lên vị trí đầu danh sách đặc. Trong phần này chúng ta sử dụng phương pháp xoay vòng. Như vậy theo phương pháp này, hàng đợi bị đầy trong các trường hợp sau:
+ Front = 1 và Rear = Len, khi: Front < Rear + Rear + 1 = Front, khi: Rear < Front
Ghi chú:
Nếu chúng ta khắc phục hàng đợi bị tràn bằng phương pháp dịch tất cả các phần tử của hàng đợi ra phía trước Front-1 vị trí thì hàng đợi bị đầy khi thỏa mãn điều kiện: Front = 1 và Rear = Len (Ở đây ta luôn luôn có: Front ≤ Rear).