Giáo trình Cấu trúc dữ liệu & thuật toán

1.1. Thuật toán.

1.1.1. Khái niệm thuật toán.

Thuật toán (algorithm) là một trong những khái niệm quan trọng nhất trong tin

học. Thuật ngữ thuật toán xuất phát từ nhà toán học A rập Abu Ja'far Mohammed ibn

Musa al Khowarizmi (khoảng năm 825). Tuy nhiên lúc bấy giờ và trong nhiều thế kỷ sau,

nó không mang nội dung nh ngày nay chúng ta quan niệm. Thuật toán nổi tiếng nhất, có

từ thời cổ Hy lạp là thuật toán Euclid, thuật toán tìm ớc chung lớn nhất của hai số

nguyên. Có thể mô tả thuật toán này nh sau :

Thuật toán Euclid.

Input : m, n nguyên dơng

Output : g, ớc chung lớn nhất của m và n

Phơng pháp :

Bớc 1 : Tìm r, phần d của phép chia m cho n

Bớc 2 : Nếu r = O, thì g ? n (gán giá trị của n cho g) và dừng lại. Trong trờng

hợp ngợc lại (r ? 0), thì m ? n, n ? r và quay lại bớc 1.

Chúng ta có thể quan niệm các bớc cần thực hiện để làm một món ăn, đợc mô

tả trong các sách dạy chế biến món ăn, là một thuật toán. Cũng có thể xem các bớc cần

tiến hành để gấp đồ chơi bằng giấy, đợc trình bầy trong sách dạy gấp đồ chơi bằng giấy,

là thuật toán. Phơng pháp thực hiện phép cộng, nhân các số nguyên, chúng ta đã học ở

cấp I cũng là các thuật toán.

Trong sách này chúng ta chỉ cần đến định nghĩa không hình thức về thuật toán :

Thuật toán là một dãy hữu hạn các bớc, mỗi bớc mô tả chính xác các phép toán

hoặc hành động cần thực hiện, để giải quyết một số vấn đề.

(Từ điểm Oxford Dictionary định nghĩa, Algorithm: set of well - defined rules for

solving a problem in a finite number of steps.)

Định nghĩa này, tất nhiên, còn chứa đựng nhiều điều cha rõ ràng. Để hiểu đầy đủ

ý nghĩa của khái niệm thuật toán, chúng ta nêu ra 5 đặc trng sau đây của thuật toán

(Xem D.E. Knuth [1968]. The Art of Computer Programming, vol. I. Fundamental

Algorithms).

1. Input. Mỗi thuật toán cần có một số (có thể bằng không) dữ liệu vào (input).

Đó là các giá trị cần đa vào khi thuật toán bắt đầu làm việc. Các dữ liệu này cần đợc

lấy từ các tập hợp giá trị cụ thể nào đó. Chẳng hạn, trong thuật toán Euclid trên, m và n là

các dữ liệu vào lấy từ tập các số nguyên dơng.

2. Output. Mỗi thuật toán cần có một hoặc nhiều dữ liệu ra (output). Đó là các giá

trị có quan hệ hoàn toàn xác định với các dữ liệu vào và là kết quả của sự thực hiện thuậtGT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường

Sưu Tầm Bởi : daihoc.com.vn 6

toán. Trong thuật toán Euclid có một dữ liệu ra, đó là g, khi thực hiện đến bớc 2 và phải

dừng lại (trờng hợp r = 0), giá trị của g là ớc chung lớn nhất của m và n.

3. Tính xác định. Mỗi bớc của thuật toán cần phải đợc mô tả một cách chính

xác, chỉ có một cách hiểu duy nhất. Hiển nhiên, đây là một đòi hỏi rất quan trọng. Bởi vì,

nếu một bớc có thể hiểu theo nhiều cách khác nhau, thì cùng một dữ liệu vào, những

ngời thực hiện thuật toán khác nhau có thể dẫn đến các kết quả khác nhau. Nếu ta mô tả

thuật toán bằng ngôn ngữ thông thờng, không có gì đảm bảo ngời đọc hiểu đúng ý của

ngời viết thuật toán. Để đảm bảo đòi hỏi này, thuật toán cần đợc mô tả trong các ngôn

ngữ lập trình (ngôn ngữ máy, hợp ngữ hoặc ngôn ngữ bậc cao nh Pascal, Fortran, C, .).

Trong các ngôn ngữ này, các mệnh đề đợc tạo thành theo các qui tắc cú pháp nghiêm

ngặt và chỉ có một ý nghĩa duy nhất.

4. Tính khả thi. Tất cả các phép toán có mặt trong các bớc của thuật toán phải

đủ đơn giản. Điều đó có nghĩa là, các phép toán phải sao cho, ít nhất về nguyên tắc có thể

thực hiện đợc bởi con ngời chỉ bằng giấy trắng và bút chì trong một khoảng thời gian

hữu hạn. Chẳng hạn trong thuật toán Euclid, ta chỉ cần thực hiện các phép chia các số

nguyên, các phép gán và các phép so sánh để biết r = 0 hay r ? 0.

5. Tính dừng. Với mọi bộ dữ liệu vào thoả mãn các điều kiện của dữ liệu vào (tức

là đợc lấy ra từ các tập giá trị của các dữ liệu vào), thuật toán phải dừng lại sau một số

hữu hạn bớc thực hiện. Chẳng hạn, thuật toán Euclid thoả mãn điều kiện này. Bởi vì giá

trị của r luôn nhỏ hơn n (khi thực hiện bớc 1), nếu r ? 0 thì giá trị của n ở bớc 2 là giá

trị của r ở bớc trớc, ta có n > r = n1 > r1 = n2 > r2 . Dãy số nguyên dơng giảm dần

cần phải kết thúc ở 0, do đó sau một số bớc nào đó giá trị của r phải bằng 0, thuật toán

dừng.

 

pdf 159 trang yennguyen 2960
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình Cấu trúc dữ liệu & thuật toán", để tải tài liệu gốc về máy hãy click vào nút Download ở trên

Tóm tắt nội dung tài liệu: Giáo trình Cấu trúc dữ liệu & thuật toán

Giáo trình Cấu trúc dữ liệu & thuật toán
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 2 
Lời nói đầu 
 Sách này trình bày các cấu trúc dữ liệu (CTDL) và thuật toán. Các kiến thức 
về CTDL và thuật toán đóng vai trò quan trọng trong việc đào tạo cử nhân tin học. 
Sách này đựơc hình thành trên cơ sở các bài giảng về CTDL và thuật toán mà tôi 
đã đọc nhiều năm tại khoa Toán-Cơ-Tin học và khoa Công nghệ thông tin Đại học 
khoa học tự nhiên, Đại học quốc gia Hà nội. Sách được viết chủ yếu để làm tài liệu 
tham khảo cho sinh viên các Khoa Công nghệ thông tin, nhưng nó cũng rất bổ ích 
cho các độc giả khác cần có hiểu biết đầy đủ hơn về CTDL và thuật toán. 
 Chúng tôi mô tả các CTDL và các thuật toán trong ngôn ngữ Pascal, vì 
Pascal là ngôn ngữ được nhiều người biết đến và là ngôn ngữ được sử dụng nhiều 
để trình bày thuật toán trong sách báo. 
 Sách này gồm hai phần. Phần 1 nói về các CTDL, phần 2 nói về thuật toán. 
Nội dung của phần 1 gồm các chương sau đây. Chương 1 trình bày các khái niệm 
cơ bản về thuật toán và phân tích thuật toán. Chương 2 trình bày các khái niệm 
CTDL, mô hình dữ liệu, kiểu dữ liệu trừu tượng (DLTT). Chương 3 trình bày các 
mô hình dữ liệu, danh sách và các phương pháp cài đặt danh sách (bởi mảng và bởi 
CTDL danh sách liên kết). Hai kiểu DLTT đặc biệt quan trọng là hàng và ngăn xếp 
(stack) cũng được xét trong chương này. Chương này cũng trình bày một số ứng 
dụng của danh sách, hàng, ngăn xếp trong thiết kế thuật toán. Chương 4 trình bày 
mô hình dữ liệu cây, các phương pháp cài đặt cây, cây nhị phân, cây tìm kiếm nhị 
phân và cây cân bằng. Chương 5 nói về mô hình dữ liệu tập hợp, các phương pháp 
cài đặt tập hợp, từ điển và cài đặt từ điển bởi bảng băm, hàng ưu tiên và cài đặt 
hàng ưu tiên bởi heap. Chương 6 đề cập đến phương pháp cài đặt các dạng bảng 
khác nhau. Các CTDL ở bộ nhớ ngoài (file băm, file chỉ số, B-cây) được trình bày 
trong chương 7. 
 Tác giả 
 PTS Đinh Mạnh Tường. 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 3 
mục lục 
Chương I: Thuật toán và phân tích thuật toán 5 
1.1. Thuật toán 5 
1.1.1. Khái niệm thuật toán. 5 
1.1.2. Biểu diễn thuật toán. 7 
1.1.3. Các vấn đề liên quan đến thuật toán. 8 
1.2. Phân tích thuật toán 9 
1.2.1. Tính hiệu quả của thuật toán. 9 
1.2.2. Tại sao lại cần thuật toán có hiệu quả. 10 
1.2.3. Đánh giá thòi gian thực hiện thuật toán như thế náo. 11 
1.2.4. Ký hiệu ô lớn và đánh giá thời gian thực hiện 
 thuật toán bằng ký hiệu ô lớn. 12 
1.2.5. Các qui tắc đánh giá thời gian thực hiện thuật toán. 14 
1.2.6. Phân tích một số thuật toán. 17 
Chương II: Kiểu dữ liệu, cấu trúc dữ liệu và mô hình dữ liệu. 20 
2.1. Biểu diễn dữ liệu. 20 
2.2. Kiểu dữ liệu và cấu trúc dữ liệu. 21 
2.3. Hệ kiểu của ngôn ngữ Pascal. 23 
2.4. Mô hình dữ liệu và kiểu dữ liệu trừu tượng. 27 
Chương III: Danh sách 32 
3.1. Danh sách. 32 
3.2. Cài đặt danh sách bởi mảng. 34 
3.3. Tìm kiếm trên danh sách. 37 
3.3.1. Vấn đề tìm kiếm. 37 
3.3.2. Tìm kiếm tuần tự. 38 
3.3.3. Tìm kiếm nhị phân. 39 
3.4. Cấu trúc dữ liệu danh sách liên kết. 41 
3.4.1. Danh sách liên kết. 41 
3.4.2. Các phép toán trên danh sách liên kết. 42 
3.4.3. So sánh hai phương pháp. 47 
3.5. Các dạng danh sách liên kết khác. 47 
3.5.1. Danh sách vòng tròn. 47 
3.5.2. Danh sách hai liên kết. 51 
 3.6. ứng dụng danh sách. 53 
3.7. Stack. 57 
3.7.1. Stack. 57 
3.7.2. Cài đặt stack bởi mảng. 58 
3.7.3. Cài đặt stack bởi danh sách liên kết. 60 
3.8. Giá trị của một biểu thức. 63 
3.9. Hàng. 68 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 4 
3.9.1. Hàng. 68 
3.9.2. Cài đặt hàng bởi mảng. 68 
3.9.3. Cài đặt hàng bởi mảng vòng tròn. 71 
3.9.4. Cài đặt hàng bởi danh sách liên kết. 73 
Chương IV: Cây. 76 
4.1. Cây và các khái niệm về cây. 76 
4.2. Các phép toán trên cây. 79 
4.2.1. Các phép toán cơ bản trên cây. 79 
4.2.2. Đi qua cây(Diệt cây). 80 
4.3. Cài đặt cây. 84 
4.3.1. Biểu diễn cây bằng danh sách các con của mỗi đỉnh. 84 
4.3.2. Biểu diễn cây bằng con trưởng và em 
 liền kề của mỗi đỉnh. 90 
4.3.3. Biểu diễn cây bởi cha của mỗi đỉnh. 92 
4.4. Cây nhị phân. 93 
4.5. Cây tìm kiếm nhị phân. 99 
4.6. Thời gian thực hiện các phép toán trên cây tìm kiếm nhị phân. 106 
4.7. Cây cân bằng. 109 
4.8. Thời gian thực hiện các phép toán trên cây cân bằng. 120 
Chương V: Tập hợp. 122 
5.1. Tập hợp và các phép toán tập hợp. 122 
5.2. Cài đặt tập hợp. 125 
5.2.1. Cài đặt tập hợp bởi vecto bit. 125 
5.2.2. Cài đặt tập hợp bởi danh sách. 126 
5.3. Từ điển. 131 
5.4. Cấu trúc dữ liệu bảng băm. Cài đặt từ điển bởi bảng băm. 131 
5.4.1. Bảng băm mở. 132 
5.4.2. Bảng băm đóng. 
 136 
5.5. Phân tích và đánh giá các phương pháp băm. 142 
5.6. Hàng ưu tiên. 145 
5.7. Heap và cài đặt hàng ưu tiên bởi heap. 146 
Chương VI: Bảng. 154 
6.1. Kiểu dữ liệu trừu tượng bảng. 154 
6.2. Cài đặt bảng. 155 
6.3. Bảng chữ nhật. 157 
6.3.1. Bảng tam giác và bảng răng lược. 158 
6.3.2. Bảng thưa thớt. 160 
6.4. Trò chơi đời sống. 162 
Chương VII: Các cấu trúc dữ liệu ở bộ nhớ ngoài. 172 
7.1. Mô hình tổ chức dữ liệu ở bộ nhớ ngoài. 172 
7.2. File băm. 
174 
7.3. File có chỉ số. 175 
7.4. B-cây. 178 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 5 
Chương I 
Thuật toán và phân tích thuật toán 
 1.1. Thuật toán. 
 1.1.1. Khái niệm thuật toán. 
 Thuật toán (algorithm) là một trong những khái niệm quan trọng nhất trong tin 
học. Thuật ngữ thuật toán xuất phát từ nhà toán học A rập Abu Ja'far Mohammed ibn 
Musa al Khowarizmi (khoảng năm 825). Tuy nhiên lúc bấy giờ và trong nhiều thế kỷ sau, 
nó không mang nội dung như ngày nay chúng ta quan niệm. Thuật toán nổi tiếng nhất, có 
từ thời cổ Hy lạp là thuật toán Euclid, thuật toán tìm ước chung lớn nhất của hai số 
nguyên. Có thể mô tả thuật toán này như sau : 
 Thuật toán Euclid. 
 Input : m, n nguyên dương 
 Output : g, ước chung lớn nhất của m và n 
 Phương pháp : 
 Bước 1 : Tìm r, phần dư của phép chia m cho n 
 Bước 2 : Nếu r = O, thì g  n (gán giá trị của n cho g) và dừng lại. Trong trường 
hợp ngược lại (r 0), thì m  n, n  r và quay lại bước 1. 
 Chúng ta có thể quan niệm các bước cần thực hiện để làm một món ăn, được mô 
tả trong các sách dạy chế biến món ăn, là một thuật toán. Cũng có thể xem các bước cần 
tiến hành để gấp đồ chơi bằng giấy, được trình bầy trong sách dạy gấp đồ chơi bằng giấy, 
là thuật toán. Phương pháp thực hiện phép cộng, nhân các số nguyên, chúng ta đã học ở 
cấp I cũng là các thuật toán. 
 Trong sách này chúng ta chỉ cần đến định nghĩa không hình thức về thuật toán : 
 Thuật toán là một dãy hữu hạn các bước, mỗi bước mô tả chính xác các phép toán 
hoặc hành động cần thực hiện, để giải quyết một số vấn đề. 
 (Từ điểm Oxford Dictionary định nghĩa, Algorithm: set of well - defined rules for 
solving a problem in a finite number of steps.) 
 Định nghĩa này, tất nhiên, còn chứa đựng nhiều điều chưa rõ ràng. Để hiểu đầy đủ 
ý nghĩa của khái niệm thuật toán, chúng ta nêu ra 5 đặc trưng sau đây của thuật toán 
(Xem D.E. Knuth [1968]. The Art of Computer Programming, vol. I. Fundamental 
Algorithms). 
 1. Input. Mỗi thuật toán cần có một số (có thể bằng không) dữ liệu vào (input). 
Đó là các giá trị cần đưa vào khi thuật toán bắt đầu làm việc. Các dữ liệu này cần được 
lấy từ các tập hợp giá trị cụ thể nào đó. Chẳng hạn, trong thuật toán Euclid trên, m và n là 
các dữ liệu vào lấy từ tập các số nguyên dương. 
 2. Output. Mỗi thuật toán cần có một hoặc nhiều dữ liệu ra (output). Đó là các giá 
trị có quan hệ hoàn toàn xác định với các dữ liệu vào và là kết quả của sự thực hiện thuật 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 6 
toán. Trong thuật toán Euclid có một dữ liệu ra, đó là g, khi thực hiện đến bước 2 và phải 
dừng lại (trường hợp r = 0), giá trị của g là ước chung lớn nhất của m và n. 
 3. Tính xác định. Mỗi bước của thuật toán cần phải được mô tả một cách chính 
xác, chỉ có một cách hiểu duy nhất. Hiển nhiên, đây là một đòi hỏi rất quan trọng. Bởi vì, 
nếu một bước có thể hiểu theo nhiều cách khác nhau, thì cùng một dữ liệu vào, những 
người thực hiện thuật toán khác nhau có thể dẫn đến các kết quả khác nhau. Nếu ta mô tả 
thuật toán bằng ngôn ngữ thông thường, không có gì đảm bảo người đọc hiểu đúng ý của 
người viết thuật toán. Để đảm bảo đòi hỏi này, thuật toán cần được mô tả trong các ngôn 
ngữ lập trình (ngôn ngữ máy, hợp ngữ hoặc ngôn ngữ bậc cao như Pascal, Fortran, C, ...). 
Trong các ngôn ngữ này, các mệnh đề được tạo thành theo các qui tắc cú pháp nghiêm 
ngặt và chỉ có một ý nghĩa duy nhất. 
 4. Tính khả thi. Tất cả các phép toán có mặt trong các bước của thuật toán phải 
đủ đơn giản. Điều đó có nghĩa là, các phép toán phải sao cho, ít nhất về nguyên tắc có thể 
thực hiện được bởi con người chỉ bằng giấy trắng và bút chì trong một khoảng thời gian 
hữu hạn. Chẳng hạn trong thuật toán Euclid, ta chỉ cần thực hiện các phép chia các số 
nguyên, các phép gán và các phép so sánh để biết r = 0 hay r 0. 
 5. Tính dừng. Với mọi bộ dữ liệu vào thoả mãn các điều kiện của dữ liệu vào (tức 
là được lấy ra từ các tập giá trị của các dữ liệu vào), thuật toán phải dừng lại sau một số 
hữu hạn bước thực hiện. Chẳng hạn, thuật toán Euclid thoả mãn điều kiện này. Bởi vì giá 
trị của r luôn nhỏ hơn n (khi thực hiện bước 1), nếu r 0 thì giá trị của n ở bước 2 là giá 
trị của r ở bước trước, ta có n > r = n1 > r1 = n2 > r2 ... Dãy số nguyên dương giảm dần 
cần phải kết thúc ở 0, do đó sau một số bước nào đó giá trị của r phải bằng 0, thuật toán 
dừng. 
 Với một vấn đề đặt ra, có thể có một hoặc nhiều thuật toán giải. Một vấn đề có 
thuật toán giải gọi là vấn đề giải được (bằng thuật toán). Chẳng hạn, vấn đề tìm nghiệm 
của hệ phương trình tuyến tính là vấn đề giải được. Một vấn đề không tồn tại thuật toán 
giải gọi là vấn đề không giải được (bằng thuật toán). Một trong những thành tựu xuất sắc 
nhất của toán học thế kỷ 20 là đã tìm ra những vấn đề không giải được bằng thuật toán. 
 Trên đây chúng ta đã trình bày định nghĩa không hình thức về thuật toán. Có thể 
xác định khái niệm thuật toán một cách chính xác bằng cách sử dụng các hệ hình thức. 
Có nhiều hệ hình thức mô tả thuật toán : máy Turing, hệ thuật toán Markôp, văn phạm 
Chomsky dạng 0, ... Song vấn đề này không thuộc phạm vi những vấn đề mà chúng ta 
quan tâm. Đối với chúng ta, chỉ sự hiểu biết trực quan, không hình thức về khái niệm 
thuật toán là đủ. 
 1.1.2. Biểu diễn thuật toán. 
 Có nhiều phương pháp biểu diễn thuật toán. Có thể biểu diễn thuật toán bằng 
danh sách các bước, các bước được diễn đạt bằng ngôn ngữ thông thường và các ký hiệu 
toán học. Có thể biểu diễn thuật toán bằng sơ đồ khối. Tuy nhiên, như đã nói, để đảm bảo 
tính xác định của thuật toán, thuật toán cần được viết trong các ngôn ngữ lập trình. Một 
chương trình là sự biểu diễn của một thuật toán trong ngôn ngữ lập trình đã chọn. Để đọc 
dễ dàng các phần tiếp theo, độc giả cần làm quen với ngôn ngữ lập trình Pascal. Đó là 
ngôn ngữ thường được chọn để trình bày các thuật toán trong sách báo. 
 Trong sách này chúng ta sẽ trình bày các thuật toán bởi các thủ tục hoặc hàm 
trong ngôn ngữ tựa Pascal. Nói là tựa Pascal, bởi vì trong nhiều trường hợp, để cho ngắn 
gọn, chúng ta không hoàn toàn tuân theo các qui định của Pascal. Ngoài ra, có trường 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 7 
hợp, chúng ta xử dụng cả các ký hiệu toán học và các mệnh đề trong ngôn ngữ tự nhiên 
(tiếng Anh hoặc tiếng Việt). Sau đây là một số ví dụ. 
 Ví dụ 1 : Thuật toán kiểm tra số nguyên n(n > 2) có là số nguyên tố hay không. 
 function NGTO (n : integer) : boolean ; 
 var a : integer ; 
 begin NGTO : = true ; 
 a : = 2 ; 
 while a <= sqrt (n) do 
 if n mod a = 0 then NGTO : = false 
 else a : = a +1 ; 
 end ; 
 Ví dụ 2 : Bài toán tháp Hà Nội. Có ba cọc A, B, C. Lúc đầu, ở cọc A có n đĩa được 
lồng vào theo thứ tự nhỏ dần từ thấp lên cao. Đòi hỏi phải chuyển n đĩa từ cọc A sang cọc 
B, được quyền sử dụng cọc C làm vị trí trung gian, nhưng không được phép đặt đĩa lớn 
lên trên đĩa nhỏ. 
 Để chuyển n đĩa từ cọc A sang cọc B, ta thực hiện thủ tục sau : đầu tiên là chuyển 
n - 1 đĩa bên trên từ cọc A sang cọc C, sau đó chuyển đĩa lớn nhất từ cọc A sang cọc B. 
Đến đây, chỉ cần cuyển n - 1 đĩa từ cọc C sang cọc B. Việc chuyển n-1 đĩa từ cọc này 
sang cọc kia được thực hiện bằng cách áp dụng đệ qui thủ tục trên. 
 Procedure MOVE (n, A, B, C) ; 
 {thủ tục chuyển n đĩa từ cọc A sang cọc B} 
 begin 
 if n=1 then chuyển một đĩa từ cọc A sang cọc B 
 else 
 begin 
 MOVE (n-1, A, C, B) ; 
 Chuyển một đĩa từ cọc A sang cọc B ; 
 MOVE (n-1, C, B, A) ; 
 end 
 end ; 
 A B C 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 8 
 1.1.3. Các vấn đề liên quan đến thuật toán. 
 Thiết kế thuật toán. 
 Để giải một bài toán trên MTĐT, điều trước tiên là chúng ta phải có thuật toán. 
Một câu hỏi đặt ra là, làm thế nào để tìm ra thuật toán cho một bài toán đã đặt ra ? Lớp 
các bài toán được đặt ra từ các ngành khoa học kỹ thuật từ các lĩnh vực hoạt động của con 
ngươì là hết sức phong phú và đa dạng. Các thuật toán giải các lớp bài toán khác nhau 
cũng rất khác nhau. Tuy nhiên, có một số kỹ thuật thiết kế thuật toán chung như chia-để-
trị (divide-and-conquer), phương pháp tham lam (greedy method), qui hoạch động 
(dynamic programming), ... Việc nắm được các chiến lược thiết kế thuật toán này là hết 
sức cần thiết, nó giúp cho bạn dễ tìm ra các thuật toán mới cho các bài toán của bạn. Các 
đề tài này sẽ được đề cập đến trong tập II của sách này. 
 Tính đúng đắn của thuật toán. 
 Khi một thuật toán được làm ra, ta cần phải chứng minh rằng, thuật toán khi đựoc 
thực hiện sẽ cho ta kết quả đúng với mọi dữ liệu vào hợp lệ. Điều này gọi là chứng minh 
tính đúng đắn của thuật toán. Việc chứng minh một thuật toán đúng đắn là một công việc 
không dễ dàng. Trong nhiều trường hợp, nó đòi hỏi ta phải có trình độ và khả năng tư duy 
toán học tốt. 
 Sau đấy ta sẽ chỉ ra rằng, khi thực hiện thuật toán Euclid, g sẽ là ước chung lớn 
nhất của hai số nguyên dương m và n bất kỳ. Thật vậy khi thực hiện bước 1, ta có m = qn 
+ r, trong đó q là số nguyên nào đó. Nếu r = 0 thì n là ước của m và hiển nhiên n (do đó 
g) là ước chung lớn nhất của m và n. Nếu r 0, thì một ước chung bất kỳ của m và n 
cũng là ước chung của n và r (vì r = m - qn). Ngược lại một ước chung bất kỳ của n và r 
cũng là ước chung của m và n (vì m = qn + r). Do đó ước chung lớn nhất của n và r cũng 
là ước chung lớn nhất của m và n. Vì vậy, khi thực hiện lặp lại bước 1 với sự thay đổi giá 
trị của m bởi n giá trị của n bởi r (các phép gán m , nr ở bước 2) cho tới khi r = 0, ta 
sẽ nhận được giá trị của g là ước chung lớn nhất của các giá trị m và n ban đầu. 
 Phân tích thuật toán. 
 Giả sử đối với một bài toán nào đó chúng ta có một số thuật toán giải. Một câu 
hỏi mới xuất hiện là, chúng ta cần chọn thuật toán nào trong số các thuật toán đó để áp ...  trong đó b là địa chỉ của khối, còn v là giá trị 
khoá nhỏ nhất của các bản ghi trong khối b. Từ các khối của file chính, ta sẽ tạo ra file 
chỉ số (index file), file này gồm các chỉ số khối của file chính. Các chỉ số khối được sắp 
xếp theo thứ tự tăng dần của khoá vào một số khối cần thiết. Các khối này có thể được 
móc nối với nhau tạo thành một danh sách liên kết. Trong trường hợp này file chỉ số gồm 
một danh sách liên kết các khối, các khối chứa các chỉ số khối của file chính. Một cách 
khác ta cũng có thể sử dụng một bảng để lưu giữ địa chỉ của các khối trong file chỉ số. 
Hình 7.2 minh hoạ cấu trúc của file có chỉ số. 
file chỉ số 
file chính 
 Hình 7.2. Cấu trúc file có chỉ số 
 Sau đây chúng ta sẽ xét sự thực hiện các phép toán trên file được tổ chức dưới 
dạng file có chỉ số 
 Tìm kiếm : 
5 17 35 49 
5 9 12 17 21 33 35 37 42 49 51 56 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 155 
 Giả sử ta cần tìm bản ghi x với khoá v cho trước. Trước hết ta cần tìm trên file chỉ 
số một chỉ số (v1, b1) sao cho v1 là giá trị khoá lớn nhất trong file chỉ số thoả mãn điều 
kiện v1 v. Ta sẽ nói v1 phủ v. 
 Việc tìm kiếm trên file chỉ số một giá trị khoá v1 phủ giá trị khoá v cho trước có 
thể thực hiện bằng cách tìm kiếm tuần tự hoặc tìm kiếm nhị phân. 
 Trong tìm kiếm tuần tự, ta cần xem xét tất cả các bản ghi của file chỉ số cho tới 
khi tìm thấy một chỉ số (v1, b1) với v1 phủ v. Nếu v nhỏ hơn giá trị khoá của bản ghi đầu 
tiên trong file chỉ số thì điều đó có nghĩa là trong file chỉ số không chứa giá trị khoá phủ 
v. 
 Phương pháp có hiệu quả hơn là tìm kiếm nhị phân. Giả sử các bản ghi của file 
chỉ số được sắp xếp vào các khối được đánh số từ 1 đến m. Xét khối thứ m/2 . Giả sử 
(v2, b2) là bản ghi đầu tiên trong khối. So sánh giá trị khoá cho trước v với giá trị khoá v2. 
Nếu v < v2 ta tiến hành tìm kiếm trên các khối 1,2, . . ., m/2 - 1. Còn nếu v v2, ta tiến 
hành tìm kiếm trên các khối m/2 , m/2 . + 1, . . . , m. Quá trình trên được lặp lại cho 
tới khi ta chỉ cần tìm kiếm trên một khối. Lúc này ta chỉ việc lần lượt so sánh giá trị khoá 
v cho trước với giá trị khoá chứa trong khối này. 
 Để tìm ra bản ghi x với khoá v cho trước, trước hết ta tìm trên file chỉ số một chỉ 
số (v1, b1) với v1 phủ v. Sau đó lần lượt xét các bản ghi trong khối có địa chỉ b1 để phát 
hiện ra bản ghi có khoá v, hoặc không nếu trong khối không có bản ghi nào với khoá là v. 
Nếu trên file chỉ số không chứa giá trị khoá v1 phủ v, thì file chính không chứa bản ghi có 
khoá v. 
 Xen vào : 
 Giả sử ta cần thêm vào file bản ghi r với giá trị khoá v. 
 Giả sử file chính được chứa trong các khối B1, B2, . . . , B k và các giá trị khoá của 
các bản ghi trong khối Bi nhỏ hơn các giá trị khoá trong khối B i +1. 
 Trước hết ta cần tìm ra khối Bi cần phải xếp bản ghi r vào đó. Muốn vậy ta áp 
dụng thủ tục tìm kiếm trên file chỉ số để tìm ra chỉ số (v1, b1) với v1 phủ v. Nếu tìm thấy 
thì B i là khối có địa chỉ b 1, ngược lại B i là khối B 1. 
 Trong trường hợp B i chưa đầy và r còn chưa có ở trong khối B i thì ta xếp bản ghi r 
vào đúng vị trí của nó, tức là phải đảm bảo trật tự tăng dần theo khoá. 
 Nếu Bi là B1 thì sau khi thêm vào bản ghi r, nó trở thành bản ghi đầu tiên trong 
khối B1, do đó ta cần phải tiến hành sửa đổi chỉ số của khối B1 trong file chỉ số. 
 Trong trường hợp B i đã đầy, ta tiến hành xếp bản ghi r vào đúng vị trí của nó 
trong B i, khi đó còn thừa ra một bản ghi. Tìm đến khối B i + 1 (ta biết được địa chỉ của khối 
B i + 1 bằng cách tìm trong chỉ số). Nếu B i + 1 chưa đầy thì ta xếp bản ghi thừa ra của B i 
vào vị trí đầu tiên trong khối B i + 1 đồng thời sửa lại chỉ số của B i + 1 trong file chỉ số. 
Nếu khối cũng đầy hoặc không tồn tại khi B i là B k thì ta thêm vào file chính một khối 
mới và xếp bản ghi r vào khối mới này. Chỉ số của khối mới thêm vào cần phải được xen 
vào file chỉ số. 
 Loại bỏ : 
 Để loại bỏ bản ghi r với khoá v, ta cần áp dụng thủ tục tìm kiếm để định vị bản 
ghi trong file. Sau đó sẽ tiến hành xoá bỏ r bằng nhiều cách khác nhau, chẳng hạn có thể 
đặt lại giá trị của bit đầy/rỗng tương ứng với bản ghi cần xoá ở đầu khối. 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 156 
 Sửa đổi : 
 Giả sử ta cần sửa đổi bản ghi với khoá v. Nếu các giá trị cần sửa không là giá trị 
của các trường thuộc khoá, thì ta chỉ cần áp dụng thủ tục tìm kiếm để tìm ra bản ghi và 
tiến hành các sửa đổi cần thiết. Nếu các giá trị cần sửa thuộc khoá thì việc sửa đổi được 
thực hiện bằng cách loại bỏ bản ghi cũ, xen vào bản ghi mới. 
7.4. B - cây. 
 Mục đích của chúng ta là nghiên cứu các cấu trúc dữ liệu biểu diễn file sao cho 
các phép toán trên file được thực hiện hiệu quả, tức là với số lần thực hiện phép toán truy 
cập khối ít nhất có thể được, khi cần phải tìm kiếm, xen vào, loại bỏ hoặc sửa đổi các bản 
ghi trên file. B - cây là một cấu trúc dữ liệu đặc biệt thích hợp để biểu diễn file. Trong 
mục này chúng ta sẽ trình bày B - cây và các kỹ thuật để thực hiện các phép toán tìm 
kiếm, xen vào và loại bỏ trên B - cây. 
 Cây tìm kiếm đa nhánh ( Multiway Search Trees) 
 Cây tìm kiếm m nhánh là sự tổng quát hoá của cây tìm kiếm nhị phân, trong đó 
mỗi đỉnh của cây có nhiều nhất m con. Các đỉnh của cây được gắn với các giá trị khoá 
của các bản ghi. Nếu đỉnh a có r con ( r m) thì nó chứa đúng r - 1 khoá ( k1, k2, . . . , kr-
1), trong đó k1 < k2< . . . < kr-1 (Chúng ta giả thiết rằng các giá trị khoá được sắp xếp thứ 
tự tuyến tính). Tổng quát hoá tính chất về khoá gắn với các đỉnh của cây tìm kiếm nhị 
phân, cây tìm kiếm m nhánh phải thoả mãn tính chất sau đây. Nếu đỉnh a có r con và 
chứa các khoá ( k1, k2, . . . , kr-1) thì các khoá chứa trong các đỉnh của cây con thứ nhất 
của đỉnh a nhỏ hơn k1, còn các khoá chứa trong các đỉnh của cây con thứ i ( i = 2, . . . , r-
1) phải lớn hơn hoặc bằng k i - 1 và nhỏ hơn k i, các khoá chứa trong các đỉnh của cây con 
thứ r phải lớn hơn hoặc bằng k r-1. Mỗi lá của cây chứa một số khoá, tối đa là s. 
 Các phép toán tìm kiếm, xen vào và loại bỏ trên cây tìm kiếm m nhánh được thực 
hiện bằng các kỹ thuật tương tự như đối với cây tìm kiếm nhị phân. 
 B - cây ( B - Trees) 
 B - cây là một loại đặc biệt của cây tìm kiếm m nhánh cân bằng (xem lại khái 
niệm cây cân bằng trong mục 7, chương 4). Cụ thể, B - cây được định nghĩa như sau : 
 B - cây cấp m là cây tìm kiếm m nhánh thoả mãn các tính chất sau đây 
1. Nếu cây không phải là cây chỉ gồm có gốc thì gốc có ít nhất hai con và nhiều nhất m 
con. 
2. Mỗi đỉnh trong của cây, trừ gốc, có ít nhất m/2 con và nhiều nhất m con. 
3. Tất cả các lá của cây trên cùng một mức. (Nói cách khác, tất cả các đường đi từ gốc 
tới lá cây có cùng độ dài). 
 Tư tưởng của việc tổ chức file dưới dạng B - cây là như sau. Ta sắp xếp các bản 
ghi của file (file chính) vào một số khối cần thiết. Mỗi khối này sẽ là lá của B - cây. 
Trong mỗi khối các bản ghi được sắp xếp theo thứ tự tăng dần của khoá. Các chỉ số của 
các khối này (các lá) lại được sắp xếp vào một số khối mới. Trong mỗi khối này, các chỉ 
số được sắp xếp theo thứ tự tăng dần của khoá. Trong B - cây, các khối này sẽ là các đỉnh 
ở mức trên của mức các lá. Ta lại lấy chỉ số của các khối vừa tạo ra sắp xếp vào một số 
khối mới. Các khối này lại là các đỉnh ở mức trên của mức từ đó được tạo ra. Quá trình 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 157 
trên sẽ tiếp tục cho tới khi các chỉ số có thể xếp gọn vào một khối. Khối này là đỉnh của 
cây B - cây. 
 Như vậy, mỗi đỉnh của B - cây là một khối. Mỗi đỉnh trong của B - cây có dạng 
 (p0, v1, p1, v2, p2, . . . , vn, pn) 
trong đó v1 < v2 < . . . < và ( vi, pi), 0 i n, là chỉ số của một khối, tức là vi là giá trị 
khoá nhỏ nhất trong một khối, còn pi là con trỏ trỏ tới khối chứa khoá nhỏ nhất vi, tức là 
con trỏ trỏ tới đỉnh con thứ i của đỉnh trong đang nói tới. Cần lưu ý rằng, giá trị của khoá 
v0 không được lưu giữ ở mỗi đỉnh trong, lý do là để tiết kiệm bộ nhớ. 
 Ví dụ : Hình 7.3 biểu diễn một B - cây cấp 3. B - cây được tạo thành từ 11 khối 
được đánh số B1, B2, . . . , B 11. Mỗi khối là đỉnh trong chứa được 3 chỉ số. Mỗi khối là lá 
chứa được 3 bảng ghi ( 3 số nguyên). File ở đây là file các số nguyên được lưu giữ ở các 
khối từ B5 đến B11. 
 B1 
B2 B3 B4 
B5 B6 B7 B8 -- B9 B10 B11 
 Hình 7.3 B - cây 
 Sau đây chúng ta sẽ nghiên cứu sự thực hiện các phép toán tìm kiếm, xen vào và 
loại bỏ trên B - cây. 
 Tìm kiếm 
 Giả sử chúng ta cần tìm bản ghi r có khoá v cho trước. Chúng ta cần phải tìm 
đường đi từ gốc của B - cây tới lá, sao cho lá này cần phải chứa bản ghi r nếu nó có ở 
trong file. 
 21 143 
 9 - 58 92 195 _ . 
3 5 - 9 15 - 21 36 49 58 - 92 121 - 143 169 195 211 232 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 158 
 Trong quá trình tìm kiếm, giả sử tại một thời điểm nào đó ta đạt tới đỉnh B. Nếu 
khối B là lá, ta tìm trong khối B xem nó có chứa bản ghi r hay không. Nhớ lại rằng các 
bản ghi của file được xếp vào các khối theo thứ tự tăng dần của khoá, do đó sự tìm kiếm 
trong khối B có thể tiến hành bằng kỹ thuật tìm kiếm tuần tự hoặc tìm kiếm nhị phân. 
 Nếu B là một đỉnh trong chứa ( p0, v1, p1, . . . , vn, pn) thì ta cần xác định vị trí của 
giá trị khoá v trong dãy giá trị khoá v1, v2, . . . , vn. Nếu v < v1 thì ta đi xuống đỉnh được 
trỏ bởi po. Nếu vi v < v i + 1 thì ta đi xuống đỉnh được trỏ bởi pi ( i = 1, 2, . . . n - 1). Còn 
nếu vn < v thì đi xuống đỉnh được trỏ bởi pn. 
 Xen vào : 
 Giả sử ta cần phải xen vào B - cây một bản ghi r với khoá là v. Đầu tiên ta áp 
dụng thủ tục tìm kiếm để tìm ra khối B cần phải xen bản ghi r vào đó. 
 Nếu khối B còn đủ chỗ cho bản ghi r thì ta xếp bản ghi r vào khối B sao cho thứ tự 
tăng dần của khoá được bảo tồn. Chú ý rằng, r không thể là bản ghi đầu tiên của khối B, 
trừ khi B là lá ngoài cùng bên trái. Nếu B là lá ngoài cùng bên trái thì giá trị khoá nhỏ 
nhất trong khối B không có mặt trong các đỉnh là tiền thân của đỉnh B. Vì vậy trong 
trường hợp này chỉ cần thêm bản ghi r vào khối B là xong, không cần sửa đổi gì với các 
đỉnh là tiền thân của khối B. 
 Nếu khối B không còn đủ không gian để lưu giữ bản ghi r thì ta thêm vào B - cây 
một lá mới, khối B'. Chuyển một nửa số bản ghi ở cuối của khối B sang khối B'. Sau đó 
xếp bản ghi r vào khối B hoặc khối B' sao cho vẫn đảm bảo được tính tăng dần của các 
giá trị khoá. Giả sử Q là cha của B, ta có thể biết được Q nếu trong quá trình tìm kiếm, ta 
lưu lại vết của đường đi từ gốc tới B. Giả sử chỉ số của khối B' là (v', p'), trong đó v' là giá 
trị khoá nhỏ nhất trong B', còn p' là địa chỉ của khối B'. áp dụng thủ tục trên để xen _v', 
p') vào khối Q. Nếu khối Q không còn đủ chỗ cho (v', p') thì ta lại phải thêm vào B - cây 
một đỉnh mới Q', nó là em liền kề của Q. Sau đó lại phải tìm đến cha của đỉnh Q để đưa 
vào chỉ số của khối mới Q'. Quá trình có thể tiếp diễn và dẫn đến việc phải phân đôi số 
giá trị khoá ở gốc, nửa sau được chuyển vào khối mới. Trong trường hợp này, ta phải tạo 
ra một gốc mới có đúng hai con, một con là gốc cũ, một con là đỉnh mới đưa vào. 
 Ví dụ. Giả sử ta cần xen vào B - cây trong hình 7.3 bản ghi có khoá 32. Trước hết 
ta phải tìm khối cần phải đưa bản ghi này vào. Bắt đầu từ gốc B1, vì 21 < 32 < 143, ta đi 
xuống B3. Tại B3, 32 < 58, ta đi xuống B7. B7 là lá, vậy cần phải đưa bản ghi với khoá 32 
vào B7. Nhưng khối B7 đã đầy. Ta thêm vào khối mới B12 và xếp các bản ghi với khoá 21, 
32 vào khối B7, xếp các bản ghi với khoá 36, 49 vào khối B12. Chỉ số của khối B12 chứa 
giá trị khoá 36. Cần phải xếp chỉ số B12 vào cha của B7 là B3. Nhưng B3 cũng đầy. Thêm 
vào khối mới B13. Sau đó các chỉ số của các khối B7, B12 được xếp vào B3 còn các chỉ số 
của các khối B8, B9 được xếp vào B13. Bây giờ chỉ số của khối B13 là 58 và địa chỉ của khối 
B13 cần được xếp vào khối B1. Nhưng B1 cũng đầy thêm vào B - cây khối mới B14. Xếp 
các chỉ số của B2, B3 vào B1, các chỉ số của B 13, B4 vào B14. Vì B1 là gốc, ta phải thêm vào 
gốc mới, khối B15 và xếp các chỉ số của B1 và B14 vào B15. Kết quả là ta có B - cây trong 
hình 7.4. 
 B15 
 B1 B14 
 B2 B3 B13 B4 -- 
 58 - 
 21 - 143 - 
 9 - 36 - 92 - 195 - . 
3 5 - 9 15 - 21 32 - 36 49 - 58 - - 92 121 - 143169 -
169169 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 159 
 B5 B6 B7 B12 B8 B9 B10 
 Hình 7.4 B - cây sau khi thêm vào B - cây trong hình 7.3 
 bản ghi với giá trị khoá 32 
 Loại bỏ 
 Giả sử ta cần loại khỏi B - cây bản ghi r với khoá v. Đầu tiên áp dụng thủ tục tìm 
kiếm để tìm ra lá B chứa bản ghi r. Sau đó loại bỏ bản ghi r trong khối B. 
 Giả sử sau khi loại bỏ B không rỗng. Trong trường hợp này, nếu r không phải là 
bản ghi đầu tiên trong B, ta không phải làm gì thêm. Nếu r là bản ghi đầu tiên trong B, thì 
sau khi xoá r, chỉ số của B đã thay đổi. Do đó ta cần tìm đến đỉnh Q là cha của B. Nếu B 
là con trưởng của Q thì giá trị khoá v' trong chỉ số (v', p') của B không có trong Q. Trong 
trường hợp này ta cần tìm đến tiền thân A của B sao cho A không phải là con trưởng của 
cha mình A'. Khi đó giá trị khoá nhỏ nhất trong B được chứa trong A'. Do đó trong A', ta 
cần thay giá trị khoá cũ v bởi giá trị mới v'. 
 Giả sử sau khi loại bỏ bản ghi r, B trở thành rỗng. Loại bỏ lá B khỏi B - cây. 
Điều đó dẫn đến cần loại bỏ chỉ số của B trong đỉnh cha Q của B. 
 Nếu sau khi loại bỏ, số các con của đỉnh Q ít hơn m/2 thì ta tìm đến đỉnh Q' là 
anh em liền kề của đỉnh Q. Nếu Q' có nhiều hơn m/2 con thì ta phân phối lại các giá trị 
khoá trong Q và Q' sao cho cả hai có ít nhất m/2 con. Khi đó các chỉ số của Q hoặc 
Q' có thể thay đổi. Ta lại phải tìm đến các tiền thân của Q đến phản ánh sự thay đổi này. 
 Nếu Q' có đúng m/2 con, thì ta kết hợp hai đỉnh Q và Q' thành một đỉnh, một 
trong hai đỉnh bị loại khỏi cây, các khoá chứa trong đỉnh này được chuyển sang đỉnh còn 
lại. Điều này dẫn đến cần loại bỏ chỉ số của đỉnh bị loại ra khỏi cha của Q. Sự loại bỏ này 
được thực hiện bằng cách áp dụng thủ tục loại bỏ đã trình bày. 
 Quá trình loại bỏ có thể dẫn đến việc loại bỏ gốc cây, khi chúng ta cần kết hợp hai 
con của gốc thành một đỉnh, đỉnh này trở thành gốc mới của B-cây. 
 Ví dụ . Giả sử chúng ta cần loại bản ghi với khoá 58 khỏi B-cây trong hình 7.4. 
Đầu tiên tìm lá chứa khoá 58, đó là khối B8. Xoá bản ghi 58, khối B8 thành rỗng. Tìm đến 
cha của B8 là B13. Loại bỏ chỉ số của B8 khỏi B13, B13 chỉ còn một con. Số con của B13 ít 
hơn m/2 (ở đây m/2 = 3/2 = 2). Tìm đến em liền kề của B13 là B4, số con của 
B4 là hai. Kết hợp hai đỉnh này thành một đỉnh B13. Cần phải loại bỏ chỉ số của khối B4 
khỏi B14. B14 trở thành chỉ có một con. Tìm đến anh liền kề của B14 là B1. Số con của B1 là 
hai. Kết hợp B1 và B14 thành một đỉnh B1. B1 trở thành gốc mới của B - cây. Hình 7.5 
minh hoạ B-cây nhận được từ B-cây trong hình 7.4 sau khi loại đỉnh có khoá 58. 
195 211 232 
GT Cấu Trỳc Dữ Liệu – Thuật Toỏn PTS- Đinh Mạnh Tường 
Sưu Tầm Bởi : daihoc.com.vn 160 

File đính kèm:

  • pdfgiao_trinh_cau_truc_du_lieu_thuat_toan.pdf