Cấu trúc dữ liệu và giải thuật
MỤC LỤC
Phần 1. Các cấu trúc dữliệu cơbản 12
Chương 1. Sựtrừu tượng hoá dữliệu 13
1.1. Biểu diễn dữliệu trong các ngôn ngữlập trình 13
1.2. Sựtrừu tượng hoá dữliệu 17
1.3. Kiểu dữliệu trừu tượng 21
1.3.1. Đặc tảkiểu dữliệu trừu tượng 21
1.3.2. Cài đặt kiểu dữliệu trừu tượng 23
1.4. Cài đặt kiểu dữliệu trừu tượng trong C 26
1.5. Triết lý cài đặt 30
Chương 2. Kiểu dữliệu trừu tượng và các lớp C ++ 34
2.1. Lớp và các thành phần của lớp 34
2.2. Các hàm thành phần 36
2.2.1. Hàm kiến tạo và hàm huỷ 36
2.2.2. Các tham biến của hàm 38
2.2.3. Định nghĩa lại các phép toán 41
2.3. Phát triển lớp cài đặt kiểu dữliệu trừu tượng 45
2.4. Lớp khuôn 55
2.4.1. Lớp côngtơnơ 55
2.4.2. Hàm khuôn 65
2.4.3. Lớp khuôn 67
2.5. Các kiểu dữliệu trừu tượng quan trọng 74
Chương 3. Sựthừa kế 77
3.1. Các lớp dẫn xuất 77
3.2. Hàm ảo và tính đa hình 84
3.3. Lớp cơsởtrừu tượng 88
Chương 4. Danh sách 98
6
4.1. Kiểu dữliệu trừu tượng danh sách 98
4.2. Cài đặt danh sách bởi mảng 101
4.3. Cài đặt danh sách bởi mảng động 109
4.4. Cài đặt tập động bởi danh sách. Tìm kiếm tuần tựvà tìm kiếm
nhịphân 117
4.4.1. Cài đặt bởi danh sách không được sắp.
Tìm kiếm tuần tự 117
4.4.2. Cài đặt bởi danh sách được sắp. Tìm kiếm nhịphân 120
4.5. Ứng dụng 126
Chương 5. Danh sách liên kết 137
5.1. Con trỏvà cấp phát động bộnhớ 137
5.2. Cấu trúc dữliệu danh sách liên kết 141
5.3. Các dạng danh sách liên kết khác 148
5.3.1. Danh sách liên kết vòng tròn 148
5.3.2. Danh sách liên kết có đầu giả 150
5.3.3. Danh sách liên kết kép 151
5.4. Cài đặt danh sách bởi danh sách liên kết 154
5.5. So sánh hai phương pháp cài đặt danh sách 162
5.6. Cài đặt tập động bởi danh sách liên kết 164
Chương 6. Ngăn xếp 168
6.1. Kiểu dữliệu trừu tượng ngăn xếp 168
6.2. Cài đặt ngăn xếp bởi mảng 169
6.3. Cài đặt ngăn xếp bởi danh sách liên kết 172
6.4. Biểu thức dấu ngoặc cân xứng 176
6.5. Đánh giá biểu thức sốhọc 178
6.5.1. Đánh giá biểu thức postfix 178
6.5.2. Chuyển biểu thức infix thành postfix 180
6.6. Ngăn xếp và đệquy 183
Chương 7. Hàng đợi 187
7.1. Kiểu dữliệu trừu tượng hàng đợi 187
7.2. Cài đặt hàng đợi bởi mảng 188
7
7.3. Cài đặt hàng đợi bởi danh sách liên kết 194
7.4. Mô phỏng hệsắp hàng 298
Chương 8. Cây 203
8.1. Các khái niệm cơbản 204
8.2. Duyệt cây 209
8.3. Cây nhịphân 213
8.4. Cây tìm kiếm nhịphân 220
8.4.1. Cây tìm kiếm nhịphân 220
8.4.2. Các phép toán tập động trên cây tìm kiếm nhịphân 223
8.5. Cài đặt tập động bởi cây tìm kiếm nhịphân 231
8.6. Thời gian thực hiện các phép toán tập động trên cây tìm kiếm
nhịphân 237
Chương 9. Bảng băm 242
9.1. Phương pháp băm 242
9.2. Các hàm băm 245
9.2.1. Phương pháp chia 245
9.2.2. Phương pháp nhân 246
9.2.3. Hàm băm cho các giá trịkhoá là xâu ký tự246
9.3. Các phương pháp giải quyết va chạm 248
9.3.1. Phương pháp định địa chỉmở 248
9.3.2. Phương pháp tạo dây chuyền 253
9.4. Cài đặt bảng băm địa chỉmở 254
9.5. Cài đặt bảng băm dây chuyền 260
9.6. Hiệu quảcủa phương pháp băm 265
Chương 10. Hàng ưu tiên 269
10.1. Kiểu dữliệu trừu tượng hàng ưu tiên 269
10.2. Các phương pháp đơn giản cài đặt hàng ưu tiên 270
10.2.1 . Cài đặt hàng ưu tiên bởi danh sách 270
10.2.2 . Cài đặt hàng ưu tiên bởi cây tìm kiếm nhịphân 271
10.3. Cây thứtựbộphận 272
10.3.1.Các phép toán hàng ưu tiên trên cây thứtựbộphận 273
8
10.3.2. Xây dựng cây thứtựbộphận 278
10.4. Cài đặt hàng ưu tiên bởi cây thứtựbộphận 282
10.5. Nén dữliệu và mã Huffman 287
Phần 2. Các cấu trúc dữliệu cao cấp 296
Chương 11. Các cây tìm kiếm cân bằng 297
11.1. Các phép quay 297
11.2. Cây AVL 298
11.2.1.Các phép toán tập động trên cây AVL 301
11.2.2.Cài đặt tập động bởi cây AVL 309
11.3. Cây đỏ- đen 315
11.4. Cấu trúc dữliệu tự điều chỉnh 327
11.5. Phân tích trảgóp 328
11.6. Cây tán loe 330
11.6.1.Các phép toán tập động trên cây tán loe 336
11.6.2.Phân tích trảgóp 338
Chương 12. Hàng ưu tiên với phép toán hợp nhất 341
12.1. Hàng ưu tiên với phép toán hợp nhất 341
12.2. Các phép toán hợp nhất và giảm khoá
trên cây thứtựbộphận 342
12.3. Cây nghiêng 342
12.3.1.Các phép toán hàng ưu tiên trên cây nghiêng 343
12.3.2.Phân tích trảgóp 348
Chương 13. Họcác tập không cắt nhau 352
13.1. Kiểu dữliệu trừu tượng họcác tập không cắt nhau 352
13.2. Cài đặt đơn giản 353
13.3. Cài đặt bởi cây 354
13.3.1.Phép hợp theo trọng số 357
13.3.2.Phép tìm với nén đường 360
13.4. Ứng dụng 362
9
13.4.1.Vấn đềtương đương 363
13.4.2.Tạo ra mê lộ 364
Chương 14. Các cấu trúc dữliệu đa chiều 367
14.1. Các phép toán trên các dữliệu đa chiều 367
14.2. Cây k - chiều 368
14.2.1.Cây 2 - chiều 369
14.2.2.Cây k - chiều 377
14.3. Cây tứphân 378
14.4. Cây tứphân MX 382
Phần 3. Thuật toán 388
Chương 15. Phân tích thuật toán 389
15.1. Thuật toán và các vấn đềliên quan 389
15.2. Tính hiệu quảcủa thuật toán 391
15.3. Ký hiệu ô lớn và biểu diễn thời gian chạy bởi ký hiệu ô lớn 394
15.3.1.Định nghĩa ký hiệu ô lớn 394
15.3.2.Biểu diễn thời gian chạy của thuật toán 395
15.4. Đánh giá thời gian chạy của thuật toán 398
15.4.1.Luật tổng 398
15.4.2.Thời gian chạy của các lệnh 399
15.5. Phân tích các hàm đệquy 402
Chương 16. Các chiến lược thiết kếthuật toán 409
16.1. Chia - để- trị 409
16.1.1.Phương pháp chung 409
16.1.1.Tìm max và min 411
16.2. Thuật toán đệquy 413
16.3. Quy hoạch động 418
16.3.1.Phương pháp chung 418
16.3.2.Bài toán sắp xếp các đồvật vào balô 419
16.3.3.Tìm dãy con chung của hai dãy số 421
10
16.4. Quay lui 422
16.4.1.Tìm kiếm vét can 422
16.4.2.Quay lui 424
16.4.3.Kỹthuật quay lui đểgiải bài toán tối ưu 430
16.5. Chiến lược tham ăn 432
16.5.1.Phương pháp chung 432
16.5.2.Thuật toán tham ăn cho bài toán người bán hàng 433
16.5.3.Thuật toán tham ăn cho bài toán balô 434
16.6. Thuật toán ngẫu nhiên 435
Chương 17. Sắp xếp 443
17.1. Các thuật toán sắp xếp đơn giản 444
17.1.1.Sắp xếp lựa chọn 444
17.1.2.Sắp xếp xen vào 446
17.1.3.Sắp xếp nổi bọt 447
17.2. Sắp xếp hoà nhập 448
17.3. Sắp xếp nhanh 452
17.4. Sắp xếp sửdụng cây thứtựbộphận 459
Chương 18. Các thuật toán đồthị 464
18.1. Một sốkhái niệm cơbản 464
18.2. Biểu diễn đồthị 466
18.2.1.Biểu diễn đồthịbởi ma trận kề 466
18.2.2.Biểu diễn đồthịbởi danh sách kề 468
18.3. Đi qua đồthị 469
18.3.1.Đi qua đồthịtheo bềrộng 469
18.3.2. Đi qu đồthịtheo độsâu 472
18.4. Đồthị định hướng không có chu trình và sắp xếp topo 477
18.5. Đường đi ngắn nhất 480
18.5.1.Đường đi ngắn nhất từmột đỉnh nguồn 480
18.5.2. Đường đi ngắn nhất giữa mọi cặp đỉnh 485
18.6. Cây bao trùm ngắn nhất 488
18.6.1.Thuật toán Prim 489
11
18.6.2.Thuật toán Kruskal 493
Chương 19. Các bài toán NP – khó và NP - đầy đủ 501
19.1. Thuật toán không đơn định 502
19.2. Các bài toán NP – khó và NP - đầy đủ 506
19.3. Một sốbài toán NP – khó 509
ó 8 cây 1 đỉnh, hình 13.4a. Thực hiện các phép hợp Union(0, 1), Union(2, 3), Union(4, 5), Union(6, 7) ta thu được các cây trong hình 13.4b. Thực hiện tiếp các phép hợp Union(0, 2), Union(4, 6) ta nhận được các cây trong hình 13.4c. Cuối cùng thực hiện phép hợp Union(0, 4) ta nhận được cây hình 13.4d. Cây này có độ cao 4 = ⎣log8⎦ + 1. (a) (b) (c) 0 1 2 3 4 5 6 7 0 1 2 4 6 3 5 7 0 1 2 3 4 5 6 7 360 (d) Hình 13.4. Cây trong trường hợp xấu nhất được tạo thành từ dãy phép hợp theo trọng số Từ định lý 13.1 ta suy ra rằng, nếu sử dụng phép hợp theo trọng số, thì thời gian thực hiện phép tìm trong trường hợp xấu nhất là O(logn), trong đó n là số phần tử của tập vũ trụ. Nếu chúng ta tiến hành một dãy phép toán gồm n phép hợp và m phép tìm, thì thời gian trong trường hợp xấu nhất là O(mlogn). Thay cho việc lưu trọng số của các cây, chúng ta có thể lưu độ cao của các cây và thực hiện phép hợp theo độ cao (union-by-height). Bạn đọc hãy đưa ra quy tắc thực hiện phép hợp theo độ cao và chứng minh khẳng định tương tự như trong định lý 13.1. Một câu hỏi được đặt ra là, liệu có thể cải thiện thời gian thực hiện phép tìm được nữa không? Thật đáng ngạc nhiên, câu trả lời là có. 13.3.2 Phép tìm với nén đường Nhằm cải thiện hơn nữa thời gian thực hiện phép tìm, khi thực hiện một phép tìm chúng ta sẽ thực hiện kèm theo một hành động được gọi là nén đường (path-compression). Khi biểu diễn một tập con bởi cây với phần tử đại biểu của tập được chứa trong gốc của cây, để thực hiện phép tìm Find(a) chúng ta cần phải đi từ đỉnh a theo các con trỏ lên gốc cây. Đường từ a lên gốc cây được gọi là đường tìm, chẳng hạn, nếu thực hiện Find(7) trong cây hình 13.4d thì đường tìm là 7 – 6 – 4 – 0. Nén đường có nghĩa là, khi thực hiện phép tìm Find(a), chúng ta thay đổi tất cả các con trỏ nằm trên đường tìm từ a lên gốc, cho chúng trỏ tới gốc 0 1 2 4 3 5 6 7 361 cây. Nén đường được minh hoạ trong hình 13.5, đường tìm được tìm ra khi thực hiện Find(a) được chỉ ra trong hình 13.5a, cây thu được sau khi nén đường được cho trong hình 13.5b. (a) (b) Hình 13.5. (a) Cây trong đó Find(a) sinh ra một đường tìm. (b) Cây kết quả sau khi nén đường. Cần lưu ý rằng, việc thực hiện nén đường kèm theo phép tìm không làm tăng thời gian thực hiện phép tìm, phép tìm vẫn chỉ đòi hỏi thời gian tỷ lệ với độ dài của đường tìm. Trực quan chúng ta thấy rằng, nén đường làm cho tất cả các đỉnh nằm trong các cây con gốc tại các đỉnh trên đường tìm trở nên gần gốc hơn. Chẳng hạn, các đỉnh trong các cây con T1, T2, T3 của cây e d c b a T5 T4 T3 T2 T1 e dcba T5 T4T3T2T1 362 trong hình 13.5b gần gốc hơn khi chúng ở trong cây hình 13.5a. Điều đó tạo điều kiện cho các phép tìm thực hiện sau sẽ hiệu quả hơn. Rõ ràng là việc thực hiện phép tìm với nén đường không ảnh hưởng gì đến thời gian thực hiện phép hợp. Thời gian chạy của phép hợp vẫn còn là O(1). Nếu phép hợp là phép hợp theo trọng số (hoặc phép hợp theo độ cao), phép tìm là phép tìm với nén đường, thì thời gian thực hiện phép tìm sẽ ra sao? Người ta đã chứng minh được rằng, thời gian chạy trả góp của phép tìm là rất gần O(1). Kết luận này được suy ra từ định lý sau đây. Định lý 13.2. Giả sử phép hợp theo trọng số và phép tìm với nén đường được thực hiện trên họ các tập con ban đầu gồm n tập một phần tử. Khi đó thời gian thực hiện một dãy m phép hợp và phép tìm, với m > n, là O(mlog*n), trong đó log*n là hàm được xác định như sau: log*n = min{i ≥ 0⎟ log(i)n ≤ 1} với log(i)n là log(i)n = log (log (… (logn))) i lần Chứng minh định lý 13.2. là cực kỳ phức tạp, chúng ta không đưa ra ở đây. Để hiểu rõ bản chất của khẳng định trong định lý 13.2, chúng ta cần biết đặc điểm của hàm log*n. Hàm này tăng cực kỳ chậm. Bạn sẽ thấy được điều này qua một số giá trị của hàm: log*2 = 1 log*4 = 2 log*16 = 3 log* 65536 = 4 log* 265536 = 5 Tức là để giá trị của hàm vượt quá 5 thì n cần phải lớn hơn 265536. Đây là con số cực kỳ khổng lồ, nó lớn hơn số các phần tử trong vũ trụ mà ta quan sát được! Đó là cơ sở để ta có thể xem rằng O(mlog*n) là O(m). 13.4 ỨNG DỤNG Các phép toán hợp và tìm trên họ các tập không cắt nhau được sử dụng trong thiết kế và cài đặt nhiều thuật toán cho các vấn đề khác nhau, chẳng hạn thuật toán tìm cây bao trùm ngắn nhất của đồ thị vô hướng (thuật toán Kruskal), thuật toán tìm tổ tiên chung gần nhất của hai đỉnh bất kỳ trong một cây (thuật toán này rất quan trọng trong các áp dụng của lý thuyết 363 đồ thị, trong Biomformatics). Trong mục này chúng ta sẽ minh hoạ cách sử dụng phép hợp và phép tìm để thiết kế thuật toán thông qua hai áp dụng. 13.4.1 Vấn đề tương đương Một quan hệ R trên tập S là một họ nào đó các cặp phần tử của S. Nếu cặp (a, b) ∈ R ta sẽ nói a có quan hệ R với b và ký hiệu aRb. Quan hệ R được gọi là quan hệ tương đương nếu nó thoả mãn các tính chất sau: • Tính phản xạ: aRa với mọi a ∈ S. • Tính đối xứng: aRb nếu và chỉ nếu bRa. • Tính bắc cầu: aRb và bRc kéo theo aRc. Chúng ta sẽ ký hiệu quan hệ tương đương bởi ~. Lớp tương đương của phần tử x ∈ S, ký hiệu là [x], gồm tất cả a ∈ S mà a ~ x (a tương đương với x). Dễ dàng chứng minh được rằng, các lớp tương đương tạo thành một phân hoạch của tập S. Đối với một quan hệ tương đương, vấn đề được đặt ra là: với hai phần tử bất kỳ a và b của S, chúng ta cần biết a có tương đương với b hay không? Nếu quan hệ tương đương đã hoàn toàn xác định trước, vấn đề được giải quyết rất đơn giản: đánh số của phần tử của S từ 0, 1, … đến n – 1, lưu quan hệ tương đương trong mảng boolean A[0… n-1], [ 0 … n-1]; truy cập thành phần A[i], [ j] ta sẽ biết i và j là tương đương hay không. Tuy nhiên, thông thường chúng ta chỉ biết trước môt tập nào đó các cặp tương đương (i, j). Chúng ta cần đưa ra câu trả lời nhanh cho câu hỏi: với cặp phần tử mới (a, b), chúng có tương đương hay không? Từ các cặp tương đương (i, j) đã cho, chúng ta sẽ tạo ra các lớp tương đương. Điều này được tiến hành như sau. Ban đầu mỗi phần tử của tập S là một lớp tương đương chỉ chứa một phần tử. Với mỗi cặp tương đương (i, j), ta sử dụng các phép tìm Find(i) và Find(j). Nếu chúng trả về các đại biểu x và y khác nhau, thì ta sử dụng phép hợp Union(x, y) để kết hợp hai lớp tương đương [x] và [y] thành một lớp mới và huỷ bỏ hai lớp đó. Còn nếu Find(i) và Find(j) trả về cùng một đại biểu thì có nghĩa là i và j đã thuộc cùng một lớp tương đương, và ta không cần phải làm gì với cặp (i, j) này. Sau khi đã tạo ra các lớp tương đương từ các cặp tương đưong (i, j) đã cho, để có câu trả lời cho cặp (a, b) được hỏi, chúng ta chỉ cần sử dụng các phép tìm Find(a) và Find(b). 364 13.4.2 Tạo ra mê lộ Hình 13.5 biểu diễn một mê lộ 5 x 6. Chúng ta quan niệm một mê lộ như một lâu đài hình chữ nhật gồm m x m phòng, mỗi phòng hoặc là có cửa thông sang phòng bên cạnh, hoặc là bị ngăn cách với phòng bên cạnh bởi bức tường (mỗi phòng kề với 4 phòng lân cận, trừ các phòng ở cạnh tường bao ngoài có số phòng kề ít hơn). Lâu đài có một cửa vào ở phòng góc trên bên trái và một lối ra ở phòng góc dưới bên phải (xem hình 13.5). Từ một phòng bất kỳ ta có thể đi tới một phòng bất kỳ khác trong lâu đài. Mê lộ cần được thiết kế sao cho người đi vào lâu đài tìm được lối ra khỏi lâu đài là hết sức khó khăn. Hình 13.5. Một mê lộ Sau đây chúng ta sẽ trình bày một thuật toán thiết kế mê lộ. Chúng ta đánh số các phòng từ 0 đến p. Ban đầu tất cả các phòng đều ngăn cách bởi bức tường với các phòng kề như trong hình 13.6. Điều này có nghĩa là ban đầu ta xem mỗi phòng ở trong một tập con riêng biệt. Tới một lúc nào đó, mỗi một tập con sẽ gồm tất cả các phòng liên thông với nhau, tức là từ một phòng bất kỳ trong tập ta có thể đi tới phòng bất kỳ khác trong tập. Tại mỗi bước chúng ta sẽ chọn ngẫu nhiên một bức tường ngăn cách 2 phòng a và b. Sử dụng phép tìm, nếu Find(a) = x, Find(b) = y và x ≠ y, thì điều đó có nghĩa là a và b không liên thông với nhau, và ta phá bức tường để cửa cho 2 phòng a và b thông với nhau. Điều này được thực hiện bằng cách sử dụng phép hợp Union(x, y) để hợp nhất tập con chứa a với tập con chứa b. Còn nếu a và b đã liên thông với nhau, tức Find(a) = Find(b), thì ta để nguyên bức tường đó. Lặp lại quá trình trên cho tới khi tất cả các phòng là liên thông với nhau. 365 0 6 12 18 24 25 26 27 28 29 Hình 13.6. Khởi tạo mỗi phòng ở trong một tập con riêng (mỗi phòng không có cửa thông sang phòng bên cạnh) Để thấy được thuật toán trên làm việc như thế nào, giả sử tới một giai đoạn nào đó chúng ta có trạng thái của các phòng như trong hình 13.7. Ở tình huống này, chúng ta có họ các tập các phòng liên thông với nhau như sau: {0, 1}, {2, 3, 4, 5, 8, 9, 10, 15, 16, 17}, {6}, {7, 12, 13, 18, 24}, {11}, {14, 19, 20, 25, 26}, {21, 27}, {22, 23, 28, 29}. Giả sử đại biểu của một tập con là phòng có số hiệu nhỏ nhất. Bây giờ chúng ta chọn ngẫu nhiên một bức chưa đặt cửa, chẳng hạn đó là bức tường ngăn cách phòng 9 và phòng 15. Chúng ta thấy rằng phòng 9 và phòng 15 đã liên thông với nhau, bởi vì Find(9) = Find(12) = 2, tức là chúng ở trong cùng một tập, và do đó ta để nguyên bức tường này. Chọn ngẫu nhiên một bức tường khác, chẳng hạn bức tường ngăn cách phòng 18 và phòng 19. Phép tìm Find(18) cho ta đại biểu của tập con chứa 18 là 7, phép tìm Find(19) cho ta đại biểu của tập con chứa 19 là 14, như vậy 18 và 19 thuộc các tập con khác nhau, và điều này có nghĩa là chúng không liên thông với nhau. Do đó ta cần đục cửa ở bức tường ngăn cách phòng 18 và 19. Sử dụng phép hợp để hợp nhất tập con chứa 18 và tập con chứa 19 thành một tập con. 1 2 3 4 5 7 8 9 10 11 13 14 15 16 17 19 20 21 22 23 366 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Hình 13.6. Một tình huống trong quá trình thực hiện thuật toán xây dựng mê lộ
File đính kèm:
- Cấu trúc dữ liệu và giải thuật.pdf