CHƯƠNG 5: DANH SÁCH LIÊN KẾT docx

31 509 0
CHƯƠNG 5: DANH SÁCH LIÊN KẾT docx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

CHƯƠNG DANH SÁCH LIÊN KẾT KDLTT danh sách xác định chương với phép toán xen vào, loại bỏ tìm kiếm đối tượng cho biết vị trí danh sách loạt phép toán khác cần thiết cho xử lý đa dạng khác danh sách Trong chương cài đặt danh sách mảng Hạn chế cách cài đặt mảng có cỡ cố định, mà danh sách ln phát triển ta thực phép tốn xen vào, q trình xử lý danh sách, có độ dài vượt cỡ mảng Một cách lựa chọn khác cài đặt danh sách cấu trúc liệu danh sách liên kết (DSLK) Các thành phần liệu CTDL liên kết với trỏ; thành phần chứa trỏ trỏ tới thành phần DSLK “nối dài ra” cần thiết, mảng lưu số cố định đối tượng liệu Nội dung chương sau: Mục 5.1 trình bày kiến thức cần thiết trỏ cấp phát động nhớ C + + sử dụng thường xuyên sau Mục 5.2 nói DSLK đơn dạng DSLK khác: DSLK vòng tròn, DSLK kép Trong mục 5.3, cài đặt KDLTT danh sách DSLK Sau đó, mục 5.4, phân tích so sánh hai phương pháp cài đặt KDLTT danh sách: cài đặt mảng cài đặt DSLK Cuối cùng, mục 5.5, thảo luận cài đặt KDLTT tập động DSLK 5.1 CON TRỎ VÀ CẤP PHÁT ĐỘNG BỘ NHỚ Cũng nhiều ngơn ngữ lập trình khác, C + + có trỏ, cho phép ta xây dựng nên DSLK CTDL phức tạp khác Biến trỏ (pointer variable), hay gọi tắt trỏ (pointer), biến chứa địa tế bào nhớ nhớ máy tính Nhờ có trỏ, ta định vị tế bào nhớ truy cập nội dung Để khai báo biến trỏ P lưu giữ địa tế bào nhớ chứa liệu kiểu Item, viết Item * P; Chẳng hạn, khai báo int * P ; nói P biến trỏ nguyên, tức P trỏ tới tế bào nhớ chứa số nguyên Muốn khai báo P Q hai trỏ nguyên, ta cần viết int * P; 137 int * Q; int * P , * Q; Cần nhớ rằng, viết int * P, Q; có P biến trỏ nguyên, Q biến nguyên Nếu khai báo: int * P; int x; biến cấp phát nhớ thời gian dịch, cấp phát nhớ gọi cấp phát tĩnh, xảy trước chương trình thực Nội dung tế bào nhớ cấp phát cho P x chưa xác định, xem minh hoạ hình 5.1a Bạn đặt địa x vào P cách sử dụng toán tử lấy địa & sau: P = &x; Khi có hồn cảnh minh hoạ hình 5.16, ký hiệu *P biểu diễn tế bào nhớ mà trỏ P trỏ tới Đến đây, bạn viết tiếp: *P = 5; biến x có giá trị 5, tức hiệu lệnh gán *P = tương đương với lệnh gán x = 5, minh hoạ hình 5.1c Sự cấp phát nhớ cịn xảy thời gian thực chương trình, gọi cấp phát động (dynamic allocation) Toán tử new C + + cho phép ta thực cấp phát động nhớ Bây giờ, bạn viết P = new int; tế bào nhớ lưu giữ số nguyên cấp phát cho biến động *P trỏ P trỏ tới tế bào nhớ đó, minh hoạ hình 5.1d Cần lưu ý biến động *P tồn cấp phát nhớ, đó, sử dụng biến khác Tiếp theo, bạn viết *P = 8; số nguyên đặt tế bào nhớ mà trỏ P trỏ tới, ta có hồn cảnh hình 5.1e Giả sử, bạn viết tiếp: int * Q; Q = P; Khi đó, trỏ Q trỏ tới tế bào nhớ mà trỏ P trỏ tới, minh hoạ hình 5.1f Nếu bạn không muốn trỏ trỏ tới tế bào nhớ nhớ, bạn sử dụng trỏ NULL Trong minh hoạ, biểu diễn NULL dấu chấm Dòng lệnh: 138 Q = NULL cho hiệu hình 5.1g Một biến động *P khơng cần thiết chương trình, thu hồi tế bào nhớ cấp phát cho trả cho hệ thống Tốn tử delete thực cơng việc Dòng lệnh delete P; thu hồi tế bào nhớ cấp phát cho biến động *P tốn tử new, có hồn cảnh minh hoạ hình 5.1h a) int * P; int x; P x b) P = &x ; P c) *P = 5; (hoặc x = 5;) x *P P x *P d) P = new int ; P f) int * Q ; Q=P; P *P x P x e) *P = ; *P *P *Q x Q g) Q = NULL; P *P x · Q 139 h) delete P ; P x · Q Hình 5.1 Các thao tác với trỏ Cấp phát mảng động Khi khai báo mảng, chẳng hạn int A[30]; chương trình dịch cấp phát 30 tế bào nhớ liên tiếp để lưu số nguyên, trước chương trình thực Mảng A mảng tĩnh, nhớ cấp phát cho cố định tồn suốt thời gian chương trình thực hiện, đồng thời A trỏ trỏ tới thành phần A[0] mảng Chúng ta sử dụng tốn tử new để cấp phát mảng Sự cấp phát xảy thời gian thực chương trình, gọi cấp phát mảng động Giả sử có khai báo sau: int size; double * B; Sau đó, đưa vào lệnh: size = 5; B = new double[size]l Khi đó, tốn tử new cấp phát mảng động B gồm tế bào nhớ double, trỏ B trỏ tới thành phần mảng này, hình 5.2b Chúng ta truy cập tới thành phần thứ i mảng động B ký hiệu truyền thống B[i], *(B + i) Chẳng hạn, bạn viết tiếp B[3] = 3.14; *(B + 3) = 3.14; thành phần thứ mảng B lưu 3.14, minh hoạ hình 5.3c Tóm lại, thao tác mảng động giống mảng tĩnh Một mảng động khơng cịn sử dụng nữa, thu hồi nhớ cấp phát cho trả cho hệ thống để sử dụng lại cho công việc khác Nếu bạn viết delete [ ] B; mảng động B thu hồi trả lại cho hệ thống có hồn cảnh hình 5.2d 140 a) int size; double * B; size B b) size = 5; B = new double[size]; size B c) B[3] = 3.14; (hoặc *(B + 3) = 3.14;) size B 3.14 d) delete [ ] B; size B Hình 5.2 Cấp phát thu hồi mảng động 5.2 CẤU TRÚC DỮ LIỆU DANH SÁCH LIÊN KẾT Trong mục này, trình bày cấu trúc liệu DSLK phép toán DSLK Danh sách liên kết đơn Danh sách liên kết đơn, gọi tắt danh sách liên kết (DSLK) tạo nên từ thành phần liên kết với trỏ Mỗi thành phần DSLK chứa liệu trỏ trỏ tới thành phần Chúng ta mô tả thành phần DSLK hộp gồm hai ngăn: ngăn chứa liệu data ngăn chứa trỏ next, hình 5.3a Hình 5.3b biểu diễn DSLK chứa liệu số nguyên 141 (a) Node data (b) … next · · Head (c) … Head (d) Tail …… · Hình 5.3 a) Một thành phần DSLK b) DSLK chứa số nguyên c) DSLK với trỏ Head d) DSLK với hai trỏ Head Tail Chúng ta biểu diễn thành phần DSLK cấu trúc C + +, cấu trúc gồm hai biến thành phần: biến data có kiểu Item đó, biến next trỏ trỏ tới cấu trúc struct Node { Item data ; Node* next ; }; Cần lưu ý rằng, thành phần cuối DSLK, giá trị next trỏ NULL, có nghĩa khơng trỏ tới đâu biểu diễn dấu chấm Để tiến hành xử lý DSLK, cần phải có khả truy cập tới thành phần DSLK Nếu biết thành phần đầu tiên, “đi theo” trỏ next, ta truy cập tới thành phần thứ hai, từ thành phần thứ hai ta truy cập tới thành phần thứ ba, … Do đó, lưu trữ DSLK, cần phải xác định trỏ trỏ tới thành phần DSLK, trỏ gọi trỏ đầu Head Như vậy, lập trình xử lý DSLK với trỏ đầu Head, cần đưa vào khai báo 142 Node* Head ; Khi mà DSLK không chứa thành phần (ta nói DSLK rỗng), lấy trỏ NULL làm giá trị biến Head Do đó, sử dụng DSLK với trỏ đầu Head, để khởi tạo DSLK rỗng, cần đặt: Head = NULL ; Hình 5.3c biểu diễn DSLK với trỏ đầu Head Cần phân biệt rằng, trỏ Head trỏ ngoài, trỏ next thành phần trỏ DSLK, chúng làm nhiệm vụ “liên kết” liệu Trong nhiều trường hợp, để thao tác DSLK thuận lợi, trỏ đầu Head người ta sử dụng thêm trỏ khác trỏ tới thành phần cuối DSLK : trỏ đuôi Tail Hình 5.3d biểu diễn DSLK với hai trỏ Head Tail Trong trường hợp DSLK rỗng, hai trỏ Head Tail có giá trị NULL Sau xét phép toán DSLK Các phép toán sở cho nhiều thuật toán DSLK Chúng ta nghiên cứu phép tốn sau: • Xen thành phần vào DSLK • Loại thành phần khỏi DSLK • Đi qua DSLK (duyệt DSLK) Xen thành phần vào DSLK Giả sử có DSLK với trỏ ngồi Head (hình 5.3c), cần xen thành phần chứa liệu value vào sau (trước) thành phần trỏ tới trỏ P (ta gọi thành phần thành phần P) Việc cần phải làm tạo thành phần trỏ tới trỏ Q, đặt liệu value vào thành phần này: Node* Q; Q = new Node ; Q  data = value; Các thao tác cần tiến hành để xen thành phần phụ thuộc vào vị trí thành phần P, đầu hay DSLK, cần xen vào sau hay trước P Xen vào đầu DSLK Trong trường hợp này, thành phần xen vào trở thành đầu DSLK, giá trị trỏ Head cần phải thay đổi Trước hết ta cần “móc nối” thành phần vào đầu DSLK, sau cho trỏ Head trỏ tới thành phần mới, hình 5.4a Các thao tác thực lệnh sau: Q  next = Head; Head = Q; 143 Chúng ta có nhận xét rằng, thủ tục cho trường hợp DSLK rỗng, DSLK rỗng giá trị Head NULL giá trị trỏ next thành phần đầu xen vào NULL Xen vào sau thành phần P Giả sử DSLK không rỗng trỏ P trỏ tới thành phần DSLK Để xen thành phần Q vào sau thành phần P, cần “móc nối” thành phần Q vào “dây chuyền” có sẵn, hình 5.4b Trước hết ta cho trỏ next thành phần Q trỏ tới thành phần sau P, sau cho trỏ next thành phần P trỏ tới Q: Q  next = P  next; P  next = Q; Cũng cần lưu ý rằng, thủ tục làm việc tốt cho trường hợp P thành phần cuối DSLK Xen vào trước thành phần P Giả sử DSLK không rỗng, trỏ P trỏ tới thành phần đầu DSLK Trong trường hợp này, để xen thành phần vào trước thành phần P, cần biết thành phần trước P để “móc nối” thành phần vào trước P Giả sử thành phần trỏ tới trỏ Pre Khi việc xen thành phần vào trước thành phần P tương đương với việc xen vào sau thành phần Pre, xem hình 5.4c Tức cần thực lệnh sau: Q  next = P; (hoặc Q  next = Pre  next) Pre  next = Q; Head …… (a) value Q 144 P (b) … … value Q Pre (c) P …… … value Q Hình 5.4 a) Xen vào đầu DSLK b) Xen vào sau thành phần P c) Xen vào trước thành phần P Loại thành phần khỏi DSLK Chúng ta cần loại khỏi DSLK thành phần trỏ tới trỏ P Cũng phép toán xen vào, loại thành phần khỏi DSLK, cần quan tâm tới nằm đâu DSLK Nếu thành phần cần loại DSLK giá trị trỏ ngồi Head khơng thay đổi, ta loại đầu DSLK thành phần trở thành đầu DSLK, giá trị trỏ Head cần thay đổi thích ứng Loại đầu DSLK Đó trường hợp P trỏ tới đầu DSLK Để loại đầu DSLK, ta cần cho trỏ Head trỏ tới thành phần (xem hình 5.5a) Với thao tác thành phần đầu thực bị loại khỏi DSLK, song 145 cịn tồn nhớ Sẽ lãng phí nhớ, để nguyên thế, cần thu hồi nhớ thành phần bị loại trả cho hệ thống Như việc loại thành phần đầu DSLK thực lệnh sau: Head = Head  next; delete P; Loại thành phần đầu DSLK Trong trường hợp để “tháo gỡ” thành phần P khỏi “dây chuyền”, cần móc nối thành phần trước P với thành phần sau P (xem hình 5.5b) Giả sử thành phần trước thành phần P trỏ tới trỏ Pre Chúng ta cần cho trỏ next thành phần trước P trỏ tới thành phần sau P, sau thu hồi nhớ thành phần bị loại Do thủ tục loại thành phần P sau: Pre  next = P  next; delete P; Thủ tục loại cho trường hợp P thành phần cuối DSLK Head … P (a) Pre P … … (b) Hình 5.5 (a) Loại đầu DSLK (b) Loại thành phần P Đi qua DSLK (Duyệt DSLK) 146 Với DSLK kép vòng trịn có đầu giả, bạn thực phép tốn xen, loại mà khơng cần quan tâm tới vị trí đặc biệt Xen, loại vị trí hay cuối giống vị trí khác Giả sử bạn cần loại thành phần trỏ trỏ P, bạn cần tiến hành thao tác hình 5.9a, tức là: Cho trỏ next thành phần trước P trỏ tới thành phần sau P Cho trỏ precede thành phần sau P trỏ tới thành phần trước P Các thao tác thực dòng lệnh sau: P  precede  next = P  next; P  next  precede = P  precede; P (a) P Q (b) Hình 5.9 (a) Loại khỏi DSLK kép thành phần P (b) Xen thành phần Q vào trước thành phần P 153 Chúng ta xen vào DSLK kép thành phần trỏ trỏ Q vào trước thành phần trỏ trỏ P thao tác hình 5.9b Đặt trỏ next thành phần trỏ tới thành phần P Đặt trỏ precede thành phần trỏ tới thành phần trước P Đặt trỏ next thành phần trước P trỏ tới thành phần Đặt trỏ precede thành phần P trỏ tới thành phần Các dòng lệnh sau thực hành động trên: Q  next = P; Q  precede = P  precede; P  precede  next = Q; P  precede = Q; Việc xen thành phần vào sau thành phần định vị DSLK kép thực tương tự 5.4 CÀI ĐẶT DANH SÁCH BỞI DSLK Trong chương 4, nghiên cứu phương pháp cài đặt KDLTT danh sách mảng, tức danh sách (a 1, a2, … , an) lưu đoạn đầu mảng Mục trình bày cách cài đặt khác: cài đặt DSLK, phần tử danh sách lưu thành phần DSLK Để cho phép toán Append (thêm phần tử vào đuôi danh sách) thực dễ dàng, sử dụng DSLK với hai trỏ ngồi Head Tail (hình 5.10a) DSLK vịng trịn với trỏ ngồi Tail (hình 5.10b) Nếu lựa chọn cách thứ hai, tức cài đặt danh sách DSLK vịng trịn, cần trỏ ngồi Tail, ta truy cập tới đuôi đầu (bởi Tail  next) thuận tiện cho xử lý mang tính “lần lượt thành phần quay vòng” danh sách Sau cài đặt danh sách DSLK với hai trỏ Head Tail hình 5.10a Việc cài đặt danh sách DSLK vịng trịn với trỏ ngồi Tail (hình 5.10b) để lại cho độc giả xem tập 154 Head a1 Tail …… a2 an · (a) Tail a1 a2 …… an (b) Hình 5.10 Cài đặt danh sách (a1, a2, … , an) DSLK Cần lưu ý rằng, cài đặt DSLK với trỏ ngồi Head, cần xen vào danh sách phải từ đầu qua thành phần đạt tới thành phần cuối Chúng ta cài đặt KDLTT danh sách lớp khuôn phụ thuộc tham biến kiểu Item, với Item kiểu phần tử danh sách (như làm mục 4.2, 4.3) Danh sách cài đặt ba lớp: lớp thành phần DSLK (được đặt tên lớp LNode), lớp danh sách liên kết (lớp LList) lớp công cụ lặp (lớp LListIterator) Sau mô tả ba lớp Lớp LNode chứa hai thành phần liệu: biến data để lưu liệu có kiểu Item trỏ next trỏ tới thành phần sau Lớp chứa hàm kiến tạo để tạo thành phần DSLK chứa liệu value, giá trị trỏ next NULL Mọi thành phần lớp private Tuy nhiên, lớp LList LListIterator cần có khả truy cập trực tíêp đến thành phần lớp LNode Do đó, khai bào lớp LList LListIterator bạn lớp Định nghĩa lớp LNode cho hình 5.11 template class LList ; // khai báo trước lớp LList 155 template class LListIterator ; // khai báo trước template class LNode { LNode (const Item & value) { data = value ; next = NULL ; } Item data ; LNode * next ; friend class LList ; friend class LListIterator ; }; Hình 5.11 Lớp LNode Lớp LList Lớp chứa ba thành phần liệu: trỏ Head trỏ tới đầu DSLK, trỏ Tail trỏ tới đuôi DSLK, biến length lưu độ dài danh sách Lớp LList chứa hàm thành phần giống lớp Dlist (xem hình 4.3) với vài thay đổi nhỏ Chúng ta khai báo lớp LListIterator bạn LList, để truy cập trực tiếp tới thành phần liệu lớp LList Định nghĩa LList cho hình 5.12 template class LListIterator ; template class LList { friend class LListIterator ; public : LList( ) // khởi tạo danh sách rỗng { Head = NULL; Tail = NULL ; length = ; } LList (const LList & L) ; // Hàm kiến tạo copy ~ LList( ) ; // Hàm huỷ LList & operator = (const LList & L); // Toán tử gán bool Empty( ) const { return length = = 0; } int Length( ) const { return length ; } void Insert(const Item & x, int i); // xen phần tử x vào vị trí thứ i danh sách 156 void Append(const Item & x); // xen phần tử x vào đuôi danh sách void Delete(int i); // loại phần tử vị trí thứ i danh sách Item & Element(int i); // trả phần tử vị trí thứ i danh sách private : LNode * Head ; LNode * Tail ; int length ; }; Hình 5.12 Định nghĩa lớp LList Bây xét cài đặt hàm thành phần lớp LList Lớp LList chứa thành phần liệu cấp phát động, lớp này, hàm kiến tạo mặc định khởi tạo danh sách rỗng, cài đặt inline, cần phải đưa vào hàm kiến tạo copy, hàm huỷ toán tử gán Sau xét hàm Hàm kiến tạo copy Hàm thực nhiệm vụ tạo DSLK với trỏ Head Tail chứa liệu DSLK cho với trỏ ngồi L.Head L.Tail Nếu DSLK cho khơng rỗng, ta tạo DSLK gồm thành phần chứa liệu thành phần DSLK cho, sau xen vào DSLK thành phần chứa liệu thành phần DSLK cho Có thể cài đặt hàm kiến tạo copy sau: LList :: LList(const LList & L) { if (L.Empty( )) { Head = Tail = NULL ; length = ; } else { Head = new LNode (L.Head  data); Tail = Head ; LNode * P ; for (P = L.Head  next; P! = NULL ; P = P  next) Append (P  data) ; length = L.length ; } } 157 Hàm huỷ Hàm cần thu hồi tất nhớ cấp phát cho thành phần DSLK trả cho hệ thống đặt trỏ Head Tail NULL Muốn vậy, thu hồi thành phần kể từ thành phần DSLK Hàm huỷ cài đặt sau: LList :: ~ LList( ) { if (Head ! = NULL) { LNode * P ; while (Head ! = NULL) { P = Head ; Head = Head  next ; delete P; } Tail = NULL ; length = ; } } Toán tử gán Nhiệm vụ tốn tử gán sau: ta có đối tượng (được trỏ trỏ this) chứa DSLK với trỏ Head Tail đối tượng khác L chứa DSLK với trỏ L.Head L.Tail, phép gán cần phải làm cho đối tượng *this chứa DSLK DSLK đối tượng L Để thực điều đó, cần làm cho DSLK đối tượng *this trở thành rỗng (với hành động hàm huỷ), sau copy DSLK đối tượng L (giống hàm kiến tạo copy) Hàm operator = trả đối tượng *this Cài đặt cụ thể hàm toán tử gán để lại cho độc giả, xem tập Sau cài đặt hàm thực phép toán danh sách Hàm Append (xen phần tử x vào đuôi danh sách) Hàm đơn giản, cần sử dụng thao tác xen thành phần chứa liệu x vào đuôi DSLK void LList :: Append (const Item & x) { LNode * Q = new LNode (x) ; if (Empty( )) { Head = Tail = Q ; } else { 158 Tail  next = Q ; Tail = Q ; } length + + ; } Các hàm xen phần tử x vào vị trí thứ i danh sách, loại phần tử vị trí thứ i khỏi danh sách tìm phần tử vị trí thứ i danh sách có điểm chung cần phải định vị thành phần DSLK chứa phần tử thứ i danh sách Chúng ta sử dụng trỏ P chạy DSLK đầu, theo trỏ next thành phần DSLK, đến vị trí mong muốn dừng lại Một trỏ P trỏ tới thành phần DSLK chứa phần tử thứ i – danh sách, việc xen vào vị trí thứ i danh sách phần tử x tương đương với việc xen vào DSLK thành phần chứa liệu x sau thành phần trỏ trỏ P Việc loại khỏi danh sách phần tử vị trí thứ i có nghĩa loại khỏi DSLK thành phần sau thành phần trỏ P Các hàm xen loại cài đặt sau: Hàm Insert template void LList :: Insert(const Item & x, int i) { assert ( i > = && i < = length) ; LNode * Q = new LNode (x) ; if (i = = 1) // xen vào đầu DSLK { Q  next = Head ; Head = Q ; } else { LNode * P = Head ; for (int k = ; k < i – ; k + + ) P = P  next ; Q  next = P  next ; P  next = Q ; } length + + ; } Hàm Delete template void LList :: Delete (int i) 159 { assert ( i > = && i = && i < = length) ; LNode * P = Head; for (int k = ; k < i ; k + +) P = P  next ; return P  data ; } Bây cài đặt lớp công cụ lặp LListIterator Lớp chứa hàm thành phần giống hàm thành phần lớp DlistIterator (xem hình 4.4) Lớp LListIterator chứa trỏ LlistPtr trỏ tới đối tượng lớp LList, phép toán liên quan tới duyệt DSLK thực thuận tiện, đưa vào lớp LListIterator hai biến trỏ: trỏ current trỏ tới thành phần thời, trỏ pre trỏ tới thành phần đứng trước thành phần thời DSLK thuộc đối tượng mà trỏ LListPtr trỏ tới Định nghĩa lớp LListIterator cho hình 5.12 160 template class LListIterator { public : LListIterator (const LList & L) // Hàm kiến tạo { LListPtr = & L; current = NULL ; pre = NULL; } void Start( ) { current = LListPtr Head; pre = NULL: } void Advance( ) { assert (current ! = NULL); pre = current; current = current  next; } bool Valid( ) {return current ! = NULL; } Item & Current( ) { assert (current! = NULL); return current  data; } void Add(const Item & x); void Remove( ); private : const LList * LlistPtr; LNode * current; LNode * pre ; } Hình 5.12 Lớp cơng cụ lặp LListIterator Các hàm thành phần lớp LListIterator đơn giản cài đặt inline, trừ hai hàm Add Remove Cài đặt hai hàm chẳng có khó khăn cả, cần sử dụng phép tốn xen, loại DSLK trình bày mục 5.2.1 Các hàm cài đặt sau: Hàm Add template void LListIterator :: Add (const Item & x) { assert (current ! = NULL); LNode * Q = new LNode (x); if (current = = LListPtr  Head) { Q  next = LListPtr  Head; LListPtr Head = Q; 161 pre = Q; } else { Q next = current; pre  next = Q; pre = Q ; } LListPtr  length + + ; } Hàm Remove template void LListIterator :: Remove( ) { assert (current ! = NULL); if (current = = LListPtr  Head) { LListPtr  Head = current next; delete current ; current = LListPtr Head ; if (LListPtr Head = = NULL) LListPtr  Tail = NULL; } else { pre  next = current  next; delete current; current = pre  next; if (current = = NULL) LListPtr Tail = pre; } LListPtr length - - ; } 5.5 SO SÁNH HAI PHƯƠNG PHÁP CÀI ĐẶT DANH SÁCH Một KDLTT cài đặt phương pháp khác Trong chương 4, nghiên cứu cách cài đặt KDLTT danh sách mảng (mảng tĩnh mảng động) Mục 5.4 trình bày cách cài đặt danh sách DSLK Trong mục phân tích đánh giá ưu khuyết điểm phương pháp Dựa vào phân tích này, bạn đưa định lựa chọn cách cài đặt cho phù hợp với ứng dụng 162 Cài đặt danh sách mảng cách lựa chọn tự nhiên hợp lý: phần tử danh sách lưu thành phần liên tiếp mảng, kể từ đầu mảng Giả sử bạn cài đặt danh sách mảng tĩnh có cỡ MAX Nếu MAX số lớn, danh sách cịn phần tử, khơng gian nhớ rộng lớn mảng khơng sử dụng đến, lãng phí nhớ Khi danh sách phát triển, tới lúc có số phần tử vượt cỡ mảng Đó hạn chế cách cài đặt danh sách mảng tĩnh Bây giả sử bạn lưu danh sách mảng động Mỗi mảng đầy, bạn cấp phát mảng động có cỡ gấp đơi mảng động cũ Nhưng bạn lại phải thời gian để chép liệu từ mảng cũ sang mảng Sự lãng phí nhớ xảy mà danh sách ngắn mà cỡ mảng lớn Trong cách cài đặt danh sách DSLK, phần tử danh sách lưu thành phần DSLK, thành phần cấp phát động DSLK móc nối thêm thành phần loại bỏ thành phần trả cho hệ thống cần thiết Do cài đặt danh sách DSLK tiết kiệm nhớ Một ưu điểm cài đặt danh sách mảng ta truy cập trực tiếp tới phần tử danh sách Nếu ta cài đặt danh sách mảng A, phần tử thứ i danh sách lưu thành phần A[i - 1] mảng, thời gian truy cập tới phần tử danh sách O(1) Vì thời gian phép tốn tìm phần tử thứ i danh sách Element(i) O(1), với i Song sử dụng DSLK để cài đặt danh sách, khơng có cách truy cập trực tiếp tới thành phần DSLK chứa phần tử thứ i danh sách Chúng ta phải sử dụng trỏ P chạy DSLK đầu, qua thành phần để đạt tới thành phần chứa phần tử thứ i danh sách Do vậy, thời gian để thực phép toán tìm phần tử thứ i danh sách danh sách cài đặt DSLK phụ thuộc vào i O(i) Nếu danh sách cài đặt mảng A, để xen phần tử vào vị trí thứ i danh sách (hoặc loại khỏi danh sách phần tử vị trí thứ i), phải “đẩy” phần tử danh sách chứa thành phần mảng kể từ A[i] phía sau vị trí (hoặc đẩy lên trước vị trí phần tử chứa thành phần kể từ A[i + 1]) Do đó, phép tốn Insert(x,i) Delete(i) địi hỏi thời gian O(n – i), n độ dài danh sách Mặt khác, cài đặt danh sách DSLK để thực phép tốn xen, loại vị trí thứ i danh sách, lại phải thời gian để định vị thành phần DSLK chứa phần tử thứ i danh sách (bằng cách cho trỏ P chạy từ đầu DSLK) Do đó, thời gian thực phép toán xen, loại O(i) 163 Thời gian thực phép toán danh sách hai cách cài đặt mảng DSLK cho bảng sau, bảng n độ dài danh sách Phép toán Insert (x, i) Delete (i) Append (x) Element (i) Add (x) Remove ( ) Current ( ) 5.6 Danh sách cài đặt mảng O(n – i) O(n – i) O(1) O(1) O(n) O(n) O(1) Danh sách cài đăt DSLK O(i) O(i) O(1) O(i) O(1) O(1) O(1) CÀI ĐẶT TẬP ĐỘNG BỞI DSLK Trong mục 4.4, nghiên cứu phương pháp cài đặt tập động mảng Ở lớp tập động DSet cài đặt cách sử dụng lớp danh sách động Dlist lớp sỏ private Đương nhiên sử dụng lớp danh sách liên kết LList lớp sở private để cài đặt lớp DSet Song cài đặt phép tốn tìm kiếm tập động địi hỏi thời gian khơng phải O(n) sử dụng lớp DList Hàm tìm kiếm lớp DSet sử dụng lớp 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 phép toán Element(i) O(1) với i, thời gian phép tốn tìm kiếm O(n), với n độ dài danh sách Tuy nhiên lớp LList, thời gian Element(i) O(i), thời gian phép tốn tìm kiếm tập động cài đặt lớp DSet cách sử dụng lớp LList làm lớp sở private O(n2) Một cách tiếp cận khác để cài đặt tập động DSLK biểu diễn tập động List với List đối tượng lớp LList Lớp DSet chứa thành phần liệu List Cũng mục 4.4, giả thiết tập động chứa liệu có kiểu Item, Item cấu trúc chứa thành phần khoá (key) với kiểu keyType Lớp DSet định nghĩa sau: template class DSet { public: void DsetInsert (const Item & x); void DsetDelete (keyType k) ; 164 bool Search (keyType k) const ; Item & Max( ) const ; Item & Min( ) const ; private : LList List; }; Chú ý rằng, lớp DSet có hàm sau 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), kích hoạt hàm kiến tạo mặc định (hàm copy, tương ứng) lớp LList để khởi tạo đối tượng List • Tốn tử gán tự động, kích hoạt tốn tử gán lớp LList • Hàm huỷ tự động, kích hoạt hàm huỷ lớp LList Các phép toán tập động cài đặt cách sử dụng phép tốn cơng cụ lặp để duyệt DSLK List Chẳng hạn, hàm tìm kiếm cài đặt sau: template bool DSet :: Search(keyType k) { LListIterator It ; // Khởi tạo It đối tượng 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 liệu với khoá k cài đặt tương tự: duyệt DSLK List, gặp liệu cần loại sử dụng hàm Remove( ) công cụ lặp template void DSet :: DsetDelete (keyType k) { LListIterator It(List); for (It.Start( ); It.Valid( ); It.Advance( )) if (It.Current( ).key = = k) { It.Remove( ) ; break); } } 165 Hàm xen liệu x vào tập động thực cách gọi hàm Append lớp LList để xen x vào đuôi DSLK template void DSet :: DsetInsert(const Item & x) { if (! Search (x.key)) List.Append(x); } Các phép tốn tìm phần tử có khố lớn (hàm Max), tìm phần tử có khố nhỏ (hàm Min) để lại cho độc giả , xem tập Các phép toán công cụ lặp cần thời gian O(1), với cách cài đặt lớp DSet trên, tất phép tốn tập động địi hỏi thời gian O(n), với n số liệu tập động Chú ý Khi cài đặt tập động DSLK tìm kiếm Cho dù liệu tập động lưu DSLK theo giá trị khoá tăng dần, áp dụng kỹ thuật tìm kiếm nhị phân, lý đơn giản DSLK truy cập trực tiếp tới thành phần DSLK BÀI TẬP Cho DSLK đơn với trỏ head trỏ tới đầu DSLK, P trỏ trỏ tới thành phần DSLK Hãy viết mẫu hàm cài đặt hàm thực nhiệm vụ sau: a Xen thành phần chứa liệu d vào trước P b Loại thành phần P c In tất liệu DSLK d Loại khỏi DSLK tất thành phần chứa liệu d Cho hai DSLK, viết hàm kết nối hai DSLK thành DSLK vịng trịn, chẳng hạn với hai DSLK: head head • 166 ta nhận DSLK vòng tròn sau: tail Chú ý rằng, hai DSLK cho rỗng Cho DSLK vịng trịn với trỏ ngồi tail trỏ tới đuôi DSLK Hãy cài đặt hàm sau: a Xen thành phần chứa liệu d vào đuôi DSLK b Xen thành phần chứa liệu d vào đầu DSLK c Loại thành phần đầu DSLK Hãy cài đặt hàm kiến tạo copy, hàm huỷ, toán tử gán lớp Dlist hàm đệ quy Hãy cài đặt lớp Llist, danh sách cài đặt DSLK vịng trịn với trỏ tail 167 ... LIỆU DANH SÁCH LIÊN KẾT Trong mục này, trình bày cấu trúc liệu DSLK phép toán DSLK Danh sách liên kết đơn Danh sách liên kết đơn, gọi tắt danh sách liên kết (DSLK) tạo nên từ thành phần liên kết. .. thiết Do cài đặt danh sách DSLK tiết kiệm nhớ Một ưu điểm cài đặt danh sách mảng ta truy cập trực tiếp tới phần tử danh sách Nếu ta cài đặt danh sách mảng A, phần tử thứ i danh sách lưu thành phần... Cài đặt danh sách mảng cách lựa chọn tự nhiên hợp lý: phần tử danh sách lưu thành phần liên tiếp mảng, kể từ đầu mảng Giả sử bạn cài đặt danh sách mảng tĩnh có cỡ MAX Nếu MAX số lớn, danh sách phần

Ngày đăng: 01/07/2014, 21:20

Từ khóa liên quan

Mục lục

  • B = new double[size]l

    • Hình 5.10. Cài đặt danh sách (a1, a2, … , an) bởi DSLK

      • LList( ) // khởi tạo danh sách rỗng

        • Hàm Insert

        • P  next = Q ;

          • Hàm Delete

          • Hàm tìm phần tử ở vị trí thứ i

            • Hàm Add

            • Hàm Remove

Tài liệu cùng người dùng

Tài liệu liên quan