1. Trang chủ
  2. » Công Nghệ Thông Tin

Tài liệu hỗ trợ môn cấu trúc dữ liệu 2

116 731 0

Đ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

Thông tin cơ bản

Định dạng
Số trang 116
Dung lượng 898,47 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Tài liệu này dành cho sinh viên, giáo viên khối ngành công nghệ thông tin tham khảo và có những bài học bổ ích hơn, bổ trợ cho việc tìm kiếm tài liệu, giáo án, giáo trình, bài giảng các môn học khối ngành công nghệ thông tin

Trang 1

Trương Hải Bằng-Cấu trúc dữ liệu 2

Trương Hải Bằng-Cấu trúc dữ liệu 2

Tµi liÖu tham kh¶o hç trî m«n häc

CÊu tróc d÷ liÖu 2

Trang 2

mục lục

Chương 1 Sắp xếp ngoại 4

1.1 Thao tác trên tệp bằng ngôn ngữ lập trình C++ 4

1.1.1 Định nghĩa tệp 4

1.1.2 Các kiểu truy cập tệp: nhị phân và văn bản 5

1.1.3 Các hàm thao tác trên tệp 6

1.2 Phương pháp trộn run có độ dài cố định 7

1.2.1 Mô tả thuật toán 7

1.2.2 Cài đặt chương trình 9

1.3 Phương pháp trộn run tự nhiên 14

1.3.1 Mô tả thuật toán 14

1.3.2 Cài đặt chương trình 15

1.4 Phương pháp trộn đa lối cân bằng (Balanced multiway merging) 20

1.4.1 Mô tả thuật toán 20

1.4.2 Cài đặt chương trình 21

1.5 Phương pháp trộn đa pha (Polyphase merge) 28

Chương 2 Bảng băm (Hash table) 29

2.1 Mở đầu 29

2.2 Các phương pháp tránh đụng độ 30

2.2.1 Dùng danh sách liên kết 30

2.2.2 Dùng danh sách kề ngoài 31

2.2.3 Phương pháp dò tuyến tính (linear probing method) 31

2.2.4 Phương pháp dò bậc hai (quadratic probing method) 32

2.3 Cài đặt bảng băm 32

2.3.1 Cài đặt bảng băm dùng danh sách liên kết ngoài 32

2.3.2 Cài đặt bảng băm dùng danh sách kề ngoài 37

2.3.3 Cài đặt bảng băm dùng liên kết trong 43

2.3.4 Vài nhận xét về bảng băm 45

Chương 3 Cây đỏ đen 46

3.1 Mở đầu 46

3.2 Cây nhị phân tìm kiếm 47

3.3 Cây 2-3-4 48

3.3.1 Định nghĩa cây 2-3-4 48

3.3.2 Tìm kiếm trên cây 2-3-4 49

3.3.3 Thêm khóa vào cây 2-3-4 49

3.3.4 Loại bỏ khóa trên cây 2-3-4 50

3.3.5 Phân tích các thuật toán trên cây 2-3-4 52

3.4 Cây đỏ đen (Red-black tree) 52

Trang 3

Trương Hải Bằng-Cấu trỳc dữ liệu 2

3.4.1 Định nghĩa 52

3.4.2 Sự tương đương giữa cây đỏ đen và cây 2-3-4 53

3.5 Cây cân bằng chiều cao (Height balanced tree) 53

3.5.1 Thao tác xoay cây nhị phân 53

3.5.2 Chỉ số cân bằng (balance factor) của một nút trên cây AVL 54

3.5.3 Cân bằng lại cây khi thêm nút 54

3.5.4 Xoá nút trên cây AVL 59

3.5.5 Vài nhận xét về cây AVL 62

3.5.6 Cài đặt cây AVL 63

3.5.7 Cài đặt cây AVL trên bộ nhớ ngoài 73

Chương 4 B - cây và bộ nhớ ngoài 74

4.1 Mở đầu 74

4.2 B - cây 74

4.2.1 Cây tìm kiếm nhiều nhánh (Multiway Search Tree) 74

4.2.2 Định nghĩa B - cây 75

4.2.3 Tìm kiếm trên B - cây 75

4.2.4 Thêm khóa vào B - cây 75

4.2.5 Loại bỏ khóa trên B - cây 76

4.2.6 Phân tích các thuật toán trên B - cây 78

4.3 Cài đặt B - cây 78

4.4 B - cây và bộ nhớ ngoài 87

Câu hỏi và bài tập 89

Chương 1 Sắp xếp ngoại 89

Chương 2 Bảng băm 89

Chương 3 Cây đỏ đen 90

Chương 4 B-cây 90

Các bài tập lớn dành cho sinh viên khá giỏi 90

Tài liệu tham khảo 92

Các câu hỏi lý thuyết và các bài thực hành chuẩn bị cho thi hết môn 93

A Phần lý thuyết 93

B Phần thực hành 95

Trang 4

Chương 1

Sắp xếp ngoại

Trong nhiều ứng dụng của tin học, ta phải sắp xếp các tập tin rất lớn Ví dụ tệp tin lưu thông tin nhân sự của một tập đoàn sản xuất lớn có thể chứa hàng chục ngàn bản ghi, mỗi bản ghi lại chứa rất nhiều thông tin: họ tên, quê quán, ngày sinh, quá trình công tác, Nếu cần sắp xếp theo một trường nào đó như ngày sinh chẳng hạn, nếu chỉ sử dụng phương pháp sắp xếp trong bộ nhớ (internal sorting) thì ta phải đổ tất cả dữ liệu vào bộ nhớ RAM và có thể bộ nhớ này không đủ để chứa dữ liệu Trong những trường hợp này người ta phải tìm cách sắp xếp tập tin mà chỉ đổ một phần rất nhỏ dữ liệu vào bộ nhớ còn phần lớn các thao tác được thực hiện trực tiếp trên tập tin

Cách sắp xếp như thế này được gọi là sắp xếp ngoại (external sorting) Khi sắp xếp một dãy các

phần tử trong bộ nhớ ta có thể truy xuất dễ dàng các thành phần của dãy Ví dụ ta có thể đổi chỗ các phần tử, dịch chuyển chúng bằng các phép gán gần như được thực hiện tức thời Việc truy xuất các phần tử trên tệp, nhất là tệp truy xuất tuần tự như băng từ chẳng hạn, sẽ khó khăn và tốn kém hơn nhiều so với truy xuất trong bộ nhớ Vì vậy việc sắp xếp ngoại phải xem việc hạn chế dịch chuyển và truy xuất trên tệp là một điều kiện quan trọng Trong các phương pháp truyền thống, người ta thấy rằng phương pháp trộn là thích hợp hơn cả cho công việc này Trong chương này chúng ta sẽ tìm hiểu một số phương pháp sắp xếp ngoại thường dùng là: trộn các run có độ dài cố định, trộn tự nhiên, trộn đa lối cân bằng và trộn đa pha Mặc dầu trong thực tế các phần tử của tập tin thường là có cấu trúc phức tạp, nhưng để đơn giản và lột tả được bản chất thuật toán, chúng ta chỉ xét tập tin nhị phân chứa các phần tử là các số thực (mỗi số thực được chứa trong 4 byte) Chúng tôi không chọn trường hợp đơn giản hơn nữa là các ký tự hoặc số nguyên, vì loại dữ liệu này dễ nhầm với chỉ số Trước khi tìm hiểu cụ thể các thuật toán, chúng tôi tóm tắt lại một số lệnh C++ liên quan đến tệp

1.1 Thao tác trên tệp bằng ngôn ngữ lập trình C++

Trong chương này chúng ta sẽ nghiên cứu và cài đặt các thuật toán sắp xếp trên tệp bằng ngôn ngữ C++ Để các bạn nghe giảng và đọc tài liệu được thuận lợi hơn, sau đây chúng tôi xin giới thiệu sơ lược về các thao tác trên tệp băng ngôn ngữ C++ Chúng tôi sẽ không giới thiệu chi tiết

cú pháp các lệnh, vì chúng ta cần dành thời gian cho nội dung chính là "Cấu trúc dữ liệu"

1.1.1 Định nghĩa tệp

Tệp thực chất là một tập hợp thông tin được đặt tên trên thiết bị nhớ ngoại vi như đĩa cứng,

đĩa mềm, đĩa CD, USB, băng từ (từ nay về sau ta gọi đơn giản là đĩa) Về mặt vật lý, thông tin trên tệp có thể không được lưu trữ một cách liên tục, nghĩa là các phần của tệp có thể nằm rải rác

ở nhiều vị trí trên đĩa, nhưng về mặt logic thì có thể xem tệp là một d∙y liên tiếp các byte trải dài liên tục từ đầu tệp đến cuối tệp Thường thì ở cuối tệp chứa một số byte có giá trị tối đa, tức là

tất cả giá trị trong các bit đều bằng 1 (như vậy mã ASCII mở rộng của byte là 11111111 = 255) Tuy nhiên các byte này không được tính vào độ lớn của tệp Nếu bạn mở một tệp và chưa nhập gì cả thì tệp có độ lớn = 0 byte, còn nếu bạn nhập vào một ký tự thì độ lớn tệp là 1 Hệ điều hành sử dụng bảng FAT (File Allocation Table) và bảng thư mục (Directory Table) được lưu trữ trên đĩa (thường là ở vùng đầu của đĩa) để lưu giữ các thông tin liên quan đến tệp Bảng thư mục chứa thông tin về tên tập tin, kích cỡ tính theo byte, ngày cập nhật cuối cùng, thuộc tính, vị trí bắt đầu trên đĩa, còn tệp FAT ghi lại thông tin về sự kết nối các vùng lưu trữ tệp trên đĩa Các byte trong tệp có thể có giá trị bất kỳ từ 0 - 255 Tuy nhiên nếu ta dùng các chương trình soạn thảo văn bản như NCEDIT hoặc C hay Pascal thì chỉ nhập được các ký tự có trên bàn phím do đó nội dung tệp trông như một văn bản có thể đọc được Nếu ta dùng chương trình soạn thảo văn bản để xem một tệp bất kỳ, ví dụ tệp có đuôi EXE thì sẽ thấy có nhiều ký tự lạ, vì khi đó chương trình soạn thảo văn bản đã cho hiện lên cả những ký tự không có trên bàn phím

Trang 5

• Trong tệp văn bản nếu ta ghi vào ký tự có mã là 26 (tức Ctrl+Z), hoặc -1 (chính là 255 nếu là ký tự không dấu unsigned char) thì khi ta dùng hàm fgetc() để đọc khi gặp giá trị này ta sẽ nhận được c = -1 (tức là giá trị của hằng EOF có sẵn trong C) Lúc này giá trị của hàm feof() sẽ nhận giá trị đúng (tức là giá trị # 0) và ta không thể đọc tiếp được các

ký tự sau đó

• Đối với tệp văn bản khi ta ghi vào tệp ký tự có mã 10 (LF = Line Feed tức là xuống dòng) thì C tự động thêm ký tự 13 (CR = Carriage Return, tức là đưa con chạy về đầu dòng) vào tệp phía trước ký tự 10 (Bạn có thể thử điều này: nếu tệp mở theo kiểu nhị phân thì khi đưa ký tự 10 vào tệp có độ lớn là 1, còn nếu mở tệp theo kiểu văn bản thì tệp lại có

độ lớn là 2 vì đã được ghi thêm ký tự 13)

• Với tệp nhị phân thì cho dù bên trong tệp có chứa một dãy liên tiếp các ký tự -1 thì C vẫn không coi đó là cuối tệp Như vậy với tệp nhị phân thì nhận biết cuối tệp không chỉ từ các ký tự ở cuối tệp

Để hiểu rõ hơn những điều trên đây bạn hãy chạy thử chương trình sau:

Trang 6

1.1.3 Các hàm thao tác trên tệp

Mặc dầu tệp là tập hợp thông tin được đặt tên và được lưu trữ trên đĩa, nhưng khi thao tác trên tệp thì tên tệp không được sử dụng trực tiếp C dùng một con trỏ tệp được lưu trong bộ nhớ để thao tác trên tệp Con trỏ này có kiểu FILE, là một kiểu cấu trúc được định nghĩa sẵn trong STDIO.H

a Mở và đóng tệp

Mở tệp:

Cú pháp: <Con trỏ tệp> = fopen(<Tên tệp>,<Kiểu mở>);

<Kiểu mở> là chuỗi gồm 2 ký tự được quy định như sau: ký tự đầu cho biết tệp được mở mới chỉ

đọc, mở mới có thể đọc ghi, mở tệp có sẵn chỉ đọc hay mở tệp có sẵn có thể đọc ghi; ký tự thứ 2 cho biết cách thức truy cập: b (binary) là nhị phân còn t (text) là văn bản Ta có thể tóm tắt các

<Kiểu mở> trong bảng sau:

w+b Mở tệp nhị phân mới đọc/ ghi w+t Mở tệp văn bản mới đọc/ ghi

Có thể sử dụng các hàm sau để ghi thông tin lên tệp:

- Ghi một ký tự lên vị trí hiện thời của tệp: fput(<ký tự>, <con trỏ tệp>); Ví dụ: fput('O',f);

- Ghi một chuỗi ký tự lên tệp: fputs(<chuỗi ký tự>, <Con trỏ tệp>); Ví dụ: fputs("Chao",f);

- Ghi các biểu thức lên tệp: fprintf(<Con trỏ tệp>,<Chuỗi khuôn dạng>,<Các biểu thức>);

Lệnh này ghi lên tệp mã ASCII của các ký tự Ví dụ lệnh: fprintf(f,"A%6.1fB", 12.5); Ghi lên tệp 8 ký tự là

Ký tự 'A' Dấu cách Dấu cách Số 1 Số 2 Dấu Số 5 Ký tự B

- Ghi các khối thông tin từ một địa chỉ trong bộ nhớ lên vị trí hiện thời của tệp:

fwrite(<Địa chỉ>, <Số byte một khối>, <Số khối>, <con trỏ tệp>);

Ví dụ: fwrite(&x, 12, 5, f); Có nghĩa là lấy thông tin trong 5 khối dữ liệu, mỗi khối 12 byte, tổng cộng là 12x5 = 60 byte bắt đầu từ địa chỉ của biến x và ghi lên vị trí hiện thời của tệp

Ví dụ này chỉ có ý nghĩa minh họa, vì nếu x là biến thực thì chỉ có 4 byte tại địa chỉ của nó là lưu giá trị của nó mà thôi

c Đọc thông tin từ tệp và đưa vào biến bộ nhớ

Trang 7

Trương Hải Bằng-Cấu trỳc dữ liệu 2

7

Cách giải thích các lệnh sau tương tự như trường hợp ghi lên tệp nhưng theo chiều ngược lại ở

đây chúng tôi chỉ nêu các ví dụ:

- char c = fgetc(f); Đọc một ký tự từ vị trí hiện thời của tệp và gán cho biến ký tự c

- fgets(<biến chuỗi ký tự>,<Số byte tối đa>, <con trỏ tệp>);//Lệnh này chỉ dùng cho tệp văn bản

Ví dụ fgets(s, 100, f); //Đọc tối đa là 100 ký tự từ dòng hiện thời (kết thúc bằng dấu xuống dòng) Như vậy nếu dòng có 70 ký tự thì chỉ đọc 70 ký tự này Còn nếu dòng có 120 ký tự chẳng hạn thì chỉ đọc 100 ký tự Vì một dòng trong tệp văn bản tối đa là 255 ký tự, nên lệnh fgets(s,255,f); sẽ bảo đảm đọc cả dòng văn bản

- fscanf(f,"%f", &x); Sẽ đọc số được ghi dưới dạng từng ký tự riêng biệt ở tại hoặc sau vị trí hiện thời của con trỏ tệp và gán cho biến x

- fread(<Địa chỉ>, <Số byte một khối>, <Số khối>, <con trỏ tệp>);

Ví dụ: fread(&x, 12, 5, f); Có nghĩa là lấy thông tin trong 5 khối dữ liệu, mỗi khối 12 byte, tổng cộng là 12x5 = 60 byte trên tệp bắt đầu từ vị trị hiện thời và gán cho biến x Ví dụ này chỉ có ý nghĩa minh họa, vì nếu x là biến thực thì chỉ có 4 byte tại địa chỉ của nó là lưu giá trị của nó mà thôi

d Dịch chuyển trên tệp

Khi môt tệp mở thì có một vị trí trên tệp được xem là con trỏ logic của tệp Thông tin đọc/ ghi trên tệp được thực hiện từ vị trí này

- Lệnh rewind(<Con trỏ tệp>); sẽ đưa con trỏ tệp về đầu tệp Ví dụ rewind(f);

- fseek(<Con trỏ tệp>, <Số byte cần dịch chuyển>, <Vị trí xuất phát>);

<Vị trí xuất phát> = 0 nếu xuất phát từ đầu tệp, 1 nếu xuất phát từ vị trí hiện thời, 2 nếu xuất phát từ cuối tệp

<Số byte cần dịch chuyển> > 0 sẽ dịch chuyển về cuối tệp, < 0 dịch chuyển về phía đầu tệp

Ví dụ: fseek(f,0,0); là chuyển con trỏ về đầu tệp; fseek(f,0,2); là chuyển con trỏ về cuối tệp

đó ta có thể dùng một dãy các phần tử A = {a0, a1, , an-1} để mô tả các phần tử trên tệp

Thuật toán này có thể mô tả như sau:

Bước 1: Ta xem tệp A gồm n run 1 phần tử (mỗi run có độ dài cố định p = 1) Ta sẽ lần lượt

phân bổ n run cho 2 tệp B và C theo cách a0 cho B, a1 cho C, a2 cho B cứ như thế cho đến khi A hết phần tử Có thể thấy rằng nếu n chẵn thì số run trên B và C như nhau, còn nếu n lẻ thì trên B có [

Trang 8

Tiếp theo ta lần lượt trộn từng cặp run trên B và C với nhau và ghi vào A Nếu số run trên B nhiều hơn thì run cuối cùng trên B sẽ được đưa vào A mà không phải trộn gì cả Trừ run cuối cùng có thể chỉ có 1 phần tử, các run trên A bây giờ đã có 2 phần tử Và

Giả sử dãy trong tệp A là các phần tử 1 2 9 8 7 6 5 ta có thể mô tả bước này trong bảng sau:

Tiếp theo ta lần lượt trộn từng cặp run trên B và C với nhau và ghi vào A

Với k = 2 trong ví dụ trên ta có p = 2 và kết quả được thể hiện trong bảng sau:

A 1 2 8 9 6 7 5

B 1 2 6 7

C 8 9 5

A' 1 2 8 9 5 6 7

Đặt p = p*2 Nếu p ≥ n thì trên A bây giờ chỉ có 1 run duy nhất và việc sắp xếp hoàn

tất Nếu p < n chuyển sang bước k+1

Thuật toán trên cũng có thể mô tả gọn hơn như sau:

Input: Tệp A có n phần tử thường là chưa sắp xếp

Output: Tệp A được sắp xếp

B1 Đặt p = 1 Chuyển sang bước B2

B2 Tệp A gồm các run độ dài p Ta tạo mới 2 tệp B và C rồi lần lượt phân bổ các run trên A sang B và C cho đến khi hết run trên A Chuyển sang bước B3

Trang 9

B4 Sau khi thực hiện bước B3 ta đã có trên A các run có độ dài 2*p (trừ run cuối cùng có thể ngắn hơn) Do đó ta đặt p = p*2 Nếu p ≥ n thì kết thúc Nếu không thì quay lại B2

Trong các thuật toán sắp xếp ngoại còn lại chúng tôi để lại cách mô tả thuật toán như trên đây cho các bạn sinh viên thực hiện Chúng tôi sẽ dùng cách mô tả dùng giả ngôn ngữ C (tức là ngôn ngữ gần giống với C, chủ yếu để hiểu được ý tưởng thuật toán) như sau:

Đặt p = 1

while (p<n)

{ - Tạo mới 2 tệp B và C rồi lần lượt phân bổ các run có độ dài p từ tệp A sang 2 tệp B

và C theo cách: một run sang B thì run tiếp theo sang C, run sau đó sang B, cho đến khi hết run trên A Như vậy tệp B có thể chứa nhiều hơn tệp C một run và run cuối cùng trên B có thể có độ dài < p (Nếu n chẵn thì run cuối cùng trên C có thể có độ dài < p)

- Tiếp theo tạo mới tệp A rồi trộn từng cặp run có độ dài p trên B và C thành một run

có độ dài p*2 và lưu vào tệp A Sau khi hoàn tất, tệp A chứa các run có độ dài p*2 (trừ run cuối cùng có thể có độ dài ngắn hơn)

- Đặt p = p*2 (và chuyển sang vòng lặp tiếp theo)

}

1.2.2 Cài đặt chương trình

Chương trình 11SXF.CPP sau đây cài đặt thuật toán sắp xếp một tệp bằng phương pháp trộn độ dài run cố định (hay còn gọi là trộn trực tiếp) trên tệp nhị phân

Phần khai báo chung, khai báo nguyên mẫu hàm và hàm main:

//11SXF.CPP Sap xep file bang phuong phap tron truc tiep tren tep nhi phan

void CreateFile(char *TenTep);

void ViewFile(char *TenTep);

void SplitFile(char *tepA,char *tepB, char *tepC, int p);

void MergeFile(char *tepB,char *tepC, char *tepA, int p);

void SortFile(char *TenTep);

Trang 10

getch();

};

Hàm int FileNodes(char *TenTep): Trả về số phần tử (tức là các số thực) có trong tệp

Hàm này thực hiện công việc sau: chuyển về cuối tệp Dùng hàm ftell() để biết được tổng số byte của tệp Lấy tổng số byte chia cho số byte của một số thực thì được số phần tử trong tệp

int FileNodes(char *TenTep)

Hàm int EoF(FILE *f) : Trả về giá trị true nếu con trỏ tệp đã đi qua số thực cuối cùng trong tệp

Sở dĩ ta phải dùng hàm này thay cho hàm feof() của C vì lý do sau:

Giả sử ta chạy đoạn chương trình sau:

float x; while(!feof(f)) {fread(&x,sz,1,f); cout<<x;}

giả sử phần tử cuối cùng trên tệp là 10 Khi đó có trường hợp sau khi đọc xong phần tử cuối cùng này thì hàm feof(f) vẫn chưa có giá trị true, nghĩa là !feof() vẫn nhận giá trị true và lệnh fread vẫn được thực hiện Tuy nhiên vì đã vượt qua số thực cuối cùng nên lệnh này không đọc được gì

và giá trị x vẫn là 10 Như vậy ta sẽ thấy trên màn hình giá trị 10 xuất hiện 2 lần Điều này là không đúng Hàm EoF() hoạt động như sau: nếu lệnh fread đọc được thông tin thì có nghĩa là vị trí hiện thời chưa vượt qua số thực cuối cùng trong tệp Điều này có nghĩa là vị trí trước khi gọi hàm chưa phải ở cuối tệp và hàm trả về giá trị false Tuy nhiên trước khi thoát khỏi hàm cần lùi lại một số thực để trở về đúng vị trí trước khi gọi hàm Còn nếu không đọc được thông tin thì có nghĩa là đã vượt qua số thực cuối cùng và như vậy đã ở cuối tệp Trong trường hợp này hàm trả

về giá trị true và cũng không cần lùi lại

Hàm void CreateFile(char *TenTep) : Tạo tệp và nhập vào n số thực

void CreateFile(char *TenTep)

printf("\nNhap du lieu vao tep:");

printf("\n1 Nhap truc tiep");

printf("\n2 Tao ngau nhien");

Trang 11

Hàm void ViewFile(char *TenTep) : Mở một tệp nhị phân đã có và đọc các số thực được lưu

trong tệp và cho hiện lên màn hình

void ViewFile(char *TenTep)

Hàm void SplitFile(char *tepA,char *tepB, char *tepC, int p)

Giả sử rằng trên tệp A gồm các run độ dài p (trừ run cuối cùng) Hàm này lần lượt đọc các run

từ tệp A và phân bổ vào 2 tệp B và C Vì A là tệp nguồn, còn B và C là các tệp đích, nên tệp A được mở chỉ đọc, còn 2 tệp B và C thì được mở mới chỉ ghi

Lệnh while(!EoF(a)) là điều kiện để kết thúc quá trình phân bổ Rõ ràng nếu trên tệp A còn phần tử (!EoF(a)) thì quá trình phân bổ chưa kết thúc Quá trính phân bổ run bao giờ cũng bắt

đầu bằng việc đọc một run trong tệp A và ghi vào tệp B Biến dem ghi lại số phần tử của run hiện thời đã được đọc và ghi vào B Nếu số phần tử còn trên A mà nhỏ hơn p thì quá trình kết thúc trước khi đọc đủ p phần tử Còn nếu không thì đọc đủ p phần tử ghi vào B sau đó chuyển sang đọc p phần tử tiếp theo ghi vào C Quá trình ghi vào tệp C cũng tương tự như quá trình ghi vào B

void SplitFile(char *tepA,char *tepB, char *tepC, int p)

{FILE *a,*b,*c;float x;int dem;

a = fopen(tepA,"rb");

b = fopen(tepB,"wb");

Trang 12

Hàm void MergeFile(char *tepB,char *tepC, char *tepA, int p)

sẽ trộn từng cặp run độ dài p từ tệp B và tệp C thành một run độ dài 2*p và đưa vào tệp A (trừ run cuối cùng)

Vì B và C là các tệp nguồn, còn A là tệp đích, nên 2 tệp B và C được mở chỉ đọc, còn tệp

A được mở mới chỉ ghi Vì quá trình trộn chỉ thực hiện khi cả hai tệp B và C đều còn phần tử Nếu một trong hai tệp hết phần tử thì có nghĩa là các phần tử còn lại ở tệp kia không còn run để trộn và được chuyển sang tệp A mà không cần so sánh gì cả Vậy lệnh

while(!EoF(b) && !EoF(c))

có nghĩa là quá trình trộn được tiếp tục nếu cả hai tệp A và B đều còn phần tử

Vì số phần tử trong mỗi run không vượt quá p nên ta dùng biến ix để ghi lại số phần tử trong run hiện thời của tệp B đã được ghi sang A Tương tự, biến iy là số phần tử trong run hiện thời của C đã được ghi sang A Khi mới bắt đầu thì cả hai run đều có phần tử nên ta đọc lần lượt phần tử đầu tiên của run trong B vào x, và phần tử đầu tiên trong run ở C vào y bằng các lệnh: fread(&x,sz,1,b);

fread(&y,sz,1,c);

Sau đó quá trình trộn run được tiến hành Quá trình này được thực hiện nếu điều kiện ix<p&&iy<p còn thỏa mãn Trong chương trình ta thấy lệnh:

while(ix<p && iy<p)

Ta bắt đầu so sánh hai phần tử đầu run bằng lệnh if(x<y) Nếu điều kiện này đúng thì ta ghi x vào A, tăng ix lên 1 Đồng thời kiểm tra xem nếu đã hết run trên B (tức là ix==p hoặc EoF(b) thì ghi y lên tệp A và thoát ra khỏi quá trình trộn 2 run hiện thời Nếu sau khi ghi x lên A

mà vẫn còn phần tử trong run ở B thì đọc phần tử tiếp theo gán vào x Trường hợp biểu thức x<y sai thì làm tương tự với tệp C

Khi thoát khỏi vòng lặp while(ix<p && iy<p) thì có thể run trên B hoặc trên C vẫn chưa hết

Do đó ta cần ghi nốt phần còn lại của run hiện thời vào tệp A bằng các lệnh:

//Chep phan con lai cua p phan tu tren b len a

while(ix<p && !EoF(b))

{fread(&x,sz,1,b);

fwrite(&x,sz,1,a);ix++;

}

//Chep phan con lai cua p phan tu tren c len a

while(iy<p && !EoF(c))

Trang 13

while(!EoF(b) && !EoF(c))

{ix = 0;//So phan tu cua b da ghi len a trong so 2*p phan tu can gi len a

iy = 0;//So phan tu cua c da ghi len a trong so 2*p phan tu can gi len a

if(ix==p || EoF(b)) {fwrite(&y,sz,1,a);iy++;break;}

//Chua du p phan tu va chua het file b

fread(&x,sz,1,b);

}

else //x>=y)

{fwrite(&y,sz,1,a);iy++;

if(iy==p || EoF(c)) {fwrite(&x,sz,1,a);ix++;break;}

//Chua du p phan tu va chua het file b

fread(&y,sz,1,c);

}

}//Het vong while(ix<p && iy<p)

//Chep phan con lai cua p phan tu tren b len a

while(ix<p && !EoF(b))

{fread(&x,sz,1,b);

fwrite(&x,sz,1,a);ix++;

}

//Chep phan con lai cua p phan tu tren c len a

while(iy<p && !EoF(c))

Trang 14

Hàm void SortFile(char *TenTep) sẽ sắp xếp tệp có tên TenTep theo các thuật toán đã trình

bày ở trên Các hàm quan trọng nhất đã được trình bày, ở đây ta chỉ cần gọi các hàm đó một cách hợp lý Chúng ta xuất phát từ p = 1, sau đó thực hiện quá trình phân bổ tệp A cho 2 tệp B và

C, rồi trộn B và C vào A Sau mỗi bước đặt p = p*2 và thực hiện quá trình lặp chừng nào p<n Nếu p<n nhưng 2*p ≥ n thì quá trình sẽ hoàn tất sau khi phân bổ A, rồi trộn B và C trở lại A

void SortFile(char *TenTep)

{int p,n;char *tepB="vutb.dat",*tepC="vutc.dat";

A và phân bổ chúng sang B và C Khi trộn các run của B và C vào A ta cũng xem xét các run thực tế trên B, C Quá trình này sẽ kết thúc khi chúng ta kiểm tra và thấy rằng chỉ có một run duy nhất trên A Cách trộn như thế này được gọi là phương pháp trộn tự nhiên

Thuật toán này có thể mô tả như sau:

Phép lặp: Ta xem tệp A gồm các run có thể có độ dài khác nhau Ta sẽ lần lượt phân bổ các

run cho 2 tệp B và C cho đến khi A hết phần tử

Tiếp theo ta lần lượt trộn từng cặp run trên B và C với nhau và ghi vào A Lưu ý rằng

số run có trên B nói chung nhỏ hơn số run mà A đã phân bổ cho B Vì nhiều khi nối hai run bất kỳ một cách tình cờ ta cũng có thể được một run Giả sử dãy trong tệp

A là các phần tử 1 2 9 8 7 6 5 ta có thể mô tả bước này trong bảng sau:

Trang 15

while (Số run trong tệp A >1)

{ - Lần lượt phân bổ các run tự nhiên từ tệp A sang 2 tệp B và C theo cách: một run sang

B thì run tiếp theo sang C, run sau đó sang B, cho đến khi hết run trên A Như vậy tệp

B có thể chứa nhiều hơn tệp C một run

- Tiếp theo trộn từng cặp run tự nhiên trên B và C thành một run và ghi vào tệp A

- Nếu A đã sắp xếp hay chỉ có một run thì kết thúc, nếu không quay lại vòng lặp

}

1.3.2 Cài đặt chương trình

Chương trình 12SXF.CPP sau đây cài đặt thuật toán sắp xếp một tệp bằng phương pháp trộn run

tự nhiên

Phần khai báo chung, khai báo nguyên mẫu hàm và hàm main:

//12SXF.CPP Sap xep file bang phuong phap tron tu nhien tren tep nhi phan

int FileNodes(char *TenTep);//So phan tu tren tep

int EoF(FILE *f);//Cuoi tep

int EoR(FILE *f);//Cuoi Run

void CreateFile(char *TenTep);

void ViewFile(char *TenTep);

void SplitFile(char *tepA,char *tepB, char *tepC);

void MergeFile(char *tepB,char *tepC, char *tepA);

int SortedFile(char *TenTep);

void SortFile(char *TenTep);

//=====================

Trang 17

while(!EoF(b) && !EoF(c))

Vì ta trộn 2 run, một ở trên B và một ở trên C Việc trộn hai run hiện thời chỉ được thực hiện chừng nào cả hai run vẫn còn phần tử Vì nếu một run hết phần tử thì chỉ cần ghi phần còn lại của run kia lên tệp A Chúng ta sẽ dùng 2 biến logic con_x và con_y để ghi lại trạng thái này con_x = true có nghĩa là vẫn còn phần tử trong run hiện thời trên B; con_y = true có nghĩa là vẫn còn phần tử trong run hiện thời trên C Vòng lặp thể hiện quá trình trộn hai run hiện thời thể hiên ở lệnh:

while(con_x && con_y)

Khi trộn ta so sánh x và y (x là của run trên B và y là phần tử của run trên C) Nếu x<y thì ta ghi x vào A Sau khi ghi x lên A thì ta kiểm tra xem còn phần tử trong run hiện thời trên B không Nếu không còn thì ghi nhận trạng thái này bằng lệnh con_x = false; nếu còn thì đọc một phần tử trong run vào x để thay thế x đã bị lấy đi Điều này thể hiện ở dòng lệnh:

if(EoR(b)) con_x=false; else fread(&x,sz,1,b);

Nếu x<y là sai, tức là x>=y thì làm tương tự với C

Thoát khỏi vòng lặp này có thể x hoặc y vẫn chưa được ghi lên a Ta ghi nốt phần tử chưa được ghi bằng các lệnh:

if(con_x) fwrite(&x,sz,1,a);

if(con_y) fwrite(&y,sz,1,a);

Sau khi trộn 2 run thì chỉ có thể một run vẫn còn phần tử chưa được ghi lên tệp A Trong trường hợp này ta ghi nốt phần tử sót lại lên A trước khi thực hiện việc trộn run tiếp theo Điều này thể hiện trong đoạn lệnh sau:

//Chep phan con lai cua run hien thoi tu b len a

while(!EoR(b))

Trang 18

Chó ý Kh¸c víi ph−¬ng ph¸p trén run trùc tiÕp, sau khi ph©n bæ, do c¸c run liÒn nhau cã thÓ t¹o

run míi nªn rÊt cã thÓ run tù nhiªn trªn b Ýt h¬n trªn c, nªn kh«ng ch¾c lµ c hÕt tr−íc

Sau ®©y lµ toµn v¨n hµm MergeFile:

void MergeFile(char *tepB,char *tepC, char *tepA)

{FILE *a,*b,*c;

float x,y;int con_x,con_y;

//con_x = true co nghia la con phan tu trong run tren b

//con_y = true co nghia la con phan tu trong run tren c

if(EoR(b)) con_x=false; else fread(&x,sz,1,b);

//Neu sau khi ghi x vao a ma het run tren file b thi con_x=false

}

else //x>=y)

{fwrite(&y,sz,1,a);

if(EoR(c)) con_y=false; else fread(&y,sz,1,c);

//Neu sau khi ghi y vao a ma het run tren file c thi con_y=false

}

}

//Het Run tren b hoac c

//Neu chua ghi x hoac y len a thi ghi not

if(con_x) fwrite(&x,sz,1,a);

if(con_y) fwrite(&y,sz,1,a);

Trang 19

Hàm SortedFile(char *TenTep) dùng phương pháp nổi bọt để kiểm tra xem một tệp đã sắp xếp

chưa Ta lần lượt đọc các phần tử trên tệp, mỗi lần đọc phần tử mới thì ghi nhận lại phần tử cũ bằng lệnh x = y; Ta thực hiện việc so sánh 2 phần tử gần kề và chỉ cần phát hiện ra một trường hợp 2 phần tử gần kề chưa sắp theo thứ tự tăng dần là ta kết thúc thuật toán và trả về giá trị false (chưa sắp) Nếu đi qua tệp mà không phát hiện ra trường hợp so le nào thì có nghĩa là tệp đã sắp xếp và ta trả về giá trị true

int SortedFile(char *TenTep)

{FILE *f;float x,y;

Cuối cùng là hàm void SortFile(char *TenTep) thực hiện việc sắp xếp một tệp đã cho theo đúng

thuật toán đã trình bày Nghĩa là thực hiện vòng lặp : phân bổ và trộn cho đến khi tệp A được sắp

void SortFile(char *TenTep)

{int i;char *tepB="vutb.dat",*tepC="vutc.dat";

while(!SortedFile(TenTep))

{SplitFile(TenTep,tepB,tepC);

MergeFile(tepB,tepC,TenTep);

}

Trang 20

bộ run được phân phối Thay vì trộn các run trở lại tệp A, người ta tạo ra các tệp G = {tepD[0], tepD[1], , tepD[m-1]} sau đó trộn từng bộ m run nằm ở đầu các tệp nguồn thành một run và lần lượt ghi vào các tệp đích Sau khi hoàn tất việc trộn, người ta hoán đổi vai trò của các tệp nguồn và tệp đích Các tệp đích trở thành các tệp nguồn và các tệp nguồn trở thành các tệp đích Thông thường sau khi trộn thì số tệp đích giảm xuống Cho đến khi số tệp đích là 1 thì quá trình sắp xếp hoàn tất

Giả sử dãy ban đầu các phần tử trong tệp A được cho như sau:

Trang 21

- Đặt m = số tệp đích đã được phân bổ run Nếu m = 1 thì kết thúc Nếu không thì đổi vai trò các tệp đích và nguồn: các tệp tepD[0], tepD[1], , tepD[m-1] trở thành các tệp nguồn, còn các tệp tepN[0], tepN[1], , tepN[m-1] trở thành các tệp đích rồi thực hiện lại vòng lặp

}

1.4.2 Cài đặt chương trình

Chương trình 13SXF.CPP sau đây cài đặt thuật toán sắp xếp một tệp bằng phương pháp trộn đa lối cân bằng vừa trình bày ở trên

Phần khai báo chung, khai báo nguyên mẫu hàm và hàm main:

//13SXF.CPP Sap xep file bang phuong phap tron da loi can bang

int FileNodes(char *TenTep);//So phan tu tren tep

int EoF(FILE *f);//Cuoi tep

int EoR(FILE *f);//Cuoi Run

void CreateFile(char *TenTep);

void ViewFile(char *TenTep);

void SplitFile(char *TenTep,char *TepNguon[], int &nWay);

void MergeFile(char *TepNguon[],char *TepDich[], int &nWay);

int SortedFile(char *TenTep);

void CopyFile(char *Tep1, char *Tep2);

void SortFile(char *TenTep);

/*TenTep la ten tep can sap xep, TepNguon[i] la ten cac tep nguon

TepDich[i] la ten cac tep dich

*/

//=====================

void main()

Trang 22

Hàm void SplitFile(char *TenTep,char *TepNguon[], int &nWay) phân bổ các run trên tệp

cần sắp xếp có tên là TenTep (ta tạm gọi đơn giản là tệp A) vào các tệp nguồn ban đầu có các tên là TepNguon[0], TepNguon[1], , TepNguon[nWay-1] nWay chính là số đường cân bằng ban đầu Ta khai báo biến nWay theo kiểu tham chiếu vì ta muốn rằng sự thay đổi giá trị của nWay bên trong hàm sẽ được giữ nguyên khi ra ngoài Ta có thể thấy rằng nếu số run trên tệp A

ít hơn số đường cân bằng thì sau khi chạy hàm này số đường cân bằng sẽ giảm xuống bằng số tệp nguồn được phân phối run Các lệnh

for(i=0;i<nWay && FileNodes(TepNguon[i])>0;i++);

nWay=i;

ở cuối hàm đếm các tệp ngưồn có chứa phần tử và gán cho nWay

Vì các tệp nguồn chỉ chứa các run được phân phối nên chúng được mở mới Tệp A dĩ nhiên là

được mở chỉ đọc, vì nó chứa các phần tử cần phân bổ Việc dùng lệnh rewind() ở đầu hàm có vẻ không cần thiết vì khi mới mở thì con trỏ tệp luôn ở đầu tệp, nhưng đôi khi thao tác này lại cần thiết như một sự kích hoạt vậy

Quá trình phân bổ chỉ kết thúc khi tệp A hết phần tử Lệnh

while(!EoF(a))

chỉ rõ điều này

Chúng ta hạn chế số đường trộn là nWay ≤ 10 Chúng ta dùng một mảng con trỏ tệp để tạo ra và thao tác với các tệp nguồn Lệnh khai báo mảng các con trỏ tệp thao tác các tệp nguồn và con trỏ tệp để mở tệp A là:

FILE *f[10], *a;

Bởi vì vị trí bắt đầu của một run mới trên A cũng là vị trí EoR của run trước đó, do đó khi bắt

đầu ghi một run lên tệp nguồn ta chưa kiểm tra ngay điều kiện EoR Vậy thao tác ghi một run lên tệp f[i] được bắt đầu bằng việc kiểm tra xem còn phần tử trên A không Nếu hết thì thoát khỏi vòng lặp và sau đó kết thúc việc phân phối (Điều kiện EoF(a) đúng tức là ! EoF(a) sai và vòng lặp kết thúc) Lệnh

for(i=0;i<nWay;i++)

chỉ ra quá trình phân phối run cho các tệp f[0], f[1], , f[nWay-1]

Khi bắt đầu ghi một run, ta kiểm tra và nếu còn phần tử trên A thì đọc phần tử đầu tiên của run trên A và ghi vào f[i] Điều này thể hiện trong các lệnh:

if(EoF(a)) break;

fread(&x,sz,1,a);

fwrite(&x,sz,1,f[i]);

sau khi ghi xong phần tử đầu tiên của run, ta tiếp tục đọc các phần tử trong run hiện thời trên A

và ghi vào tệp f[i]:

while(!EoR(a))

{fread(&x,sz,1,a);

fwrite(&x,sz,1,f[i]);

}

Trang 23

Cuối cùng ta đóng tất cả các tệp và tính lại nWay Thông thường nWay có xu hướng giảm dần

đến 1

void SplitFile(char *TenTep,char *TepNguon[], int &nWay)

{

FILE *f[10], *a;float x;int i,j,k;

for(i=0;i<nWay;i++) f[i] = fopen(TepNguon[i],"wb");

a = fopen(TenTep,"rb");

rewind(a);

for(i=0;i<nWay;i++) rewind(f[i]);

while(!EoF(a))

{//Chia xoay vong lan luot cac run tren a cho f[0],f[1], ,f[nWay-1]

//hoac chi den f[k] nao do neu het phan tu tren a

Hàm void MergeFile(char *TepNguon[10],char *TepDich[10], int &nWay) thực hiện lần lượt

trộn các bộ gồm nWay run lấy từ TepNguon[0], TepNguon[1], , TepNguon[nWay-1] và ghi vào các tệp đích TepDich[0], TepDich[1], , TepDich[nWay-1] Như ta đã thấy, các tệp nguồn

ở cuối có thể có số run ít hơn 1 so với số các run ở các tệp ở đầu, do đó lần trộn cuối có thể không có đủ nWay run

Thao tác trộn được thực hiện như sau: ta so sánh phần tử ở đầu các run hiện thời và chọn ra phần tử nhỏ nhất để ghi lên tệp đích hiện thời Để có thể so sánh, ta phải đổ các giá trị ở đầu các run vào bộ nhớ và lưu trong mảng x[0], x[1], , x[nWay-1] Tuy ở bước trộn cuối cùng có thể không có đủ nWay phần tử Ngay trong các bước trộn có đầy đủ run thì cũng có trường hợp một số run sẽ hết phần tử trước Ví dụ giả sử nWay = 3 và 3 run hiện thời là {1,2,3}, {4,5,6,7} và {8,9} thì ban đầu ta có x[0] = 1, x[1] = 4 và x[2] = 8 Ta thấy rằng x[0] là nhỏ nhất và ghi giá trị này lên tệp đích, sau đó đọc giá trị tiếp theo của run vào x[0] và nhận được x[0] = 2 ở bước thứ 2 ta cần chọn giá trị nhỏ nhất trong 3 số x[0] = 2, x[1] = 4 và x[2] = 8

Ta thấy rằng x[0] được chọn ghi lên tệp đích và giá trị tiếp theo của run được gán cho x[0] và

ta lại có x[0] = 3, x[1] = 4 và x[2] = 8 Tại bước này ta ghi x[0]=3 lên tệp đích, nhưng run hiện thời đã hết phần tử nên ta phải vô hiệu hóa giá trị x[0] mà chỉ so sánh x[1] với x[2] Để có thể

đánh dấu một run hiện thời nào đó đã hết phần tử, ta dùng một mảng con_x[i] (có nghĩa là còn x[i]) Nếu còn phần tử ở run hiện thời trên tệp nguồn i thì ta đặt con_x[i] = true, nếu đã hết thì

ta đặt con_x[i] = false

Bắt đầu quá trình trộn, chúng ta phải mở các tệp nguồn theo kiểu chỉ đọc, còn các tệp đích được tạo mới Biến CurrDich là chỉ số của tệp đích hiện thời Ban đầu CurrDich=0, tức là g[0] sẽ là

Trang 24

tệp đích đầu tiên như thuật toán đã chỉ ra Sau mỗi lần trộn và run được ghi vào tệp đích hiện thời thì CurrDich tăng lên 1, nghĩa là tệp đích hiện thời sẽ là tệp tiếp theo của tệp đích hiện thời trước

đó Tuy nhiên nếu tệp đích trước đó là tệp cuối cùng thì chu kỳ sẽ quay lại từ đầu, tức là CurDich=0

Để bắt đầu trộn một bộ run chúng ta sẽ đọc các phần tử đầu mỗi run và gán vào mảng x[i] Tuy nhiên như ta đã thấy, có thể chỉ có một số run còn phần tử, do đó ta phải kiểm tra xem việc đọc

có thành công không Nếu thành công thì gán con_x[i]=true, còn nếu không thì gán con_x[i]=false

Chúng ta dùng biến nNguon để đếm số lần đọc thành công Nếu nNguon=0 có nghĩa là tất cả các lần đọc đều không thành công, và như vậy thì có nghĩa là đã hết dữ liệu trên các file nguồn và quá trình trộn kết thúc

Đoạn lệnh sau thể hiện những điều vừa nói:

if(nNguon==0) break;//Da het cac phan tu tren cac file nguon

Sau bước khởi đầu này ta bắt đầu thực hiện vòng lặp thực hiện việc trộn các run hiện thời và ghi lên tệp đích hiện thời Điều kiện dừng của vòng lặp khá phức tạp nên ta tạm đặt là while(true) Bên trong thân vòng lặp sẽ có điều kiện để thoát khỏi vòng lặp

Khi bắt đầu trộn run thì có một số phần tử trong mảng x[i] Tuy nhiên trong quá trình trộn các phần tử được ghi vào tệp đích phần tử mới được đọc từ run tương ứng để thay thế nếu run vẫn còn Do vậy rất có thể đến lúc nào đó trong mảng x[i] sẽ không còn phần tử nào cả, khi đó việc trộn run hiện thời sẽ kết thúc

Để chọn phần tử ghi vào tệp đích hiên thời, ta cần chọn k sao cho x[k] là phần tử nhỏ nhất trong các phần tử của mảng x[i] đã được đọc (con_x[i] = true) Theo thông thường thì để xác

định min của một mảng đầu tiên ta chọn phần tử đầu tiên của mảng và coi là phần tử nhỏ nhất sau đó ta bắt đầu duyệt từ phần tử thứ 2 cho đến phần tử cuối cùng, mỗi lần gặp phần tử nhỏ hơn min hiện thời thì ta lại thay thế min hiện thời bằng phần tử đó Tuy nhiên trong trường hợp chúng ta đang xét thì có thể phần tử x[0], x[1] đã được ghi và không còn tham gia vào quá trình chọn (con_x[i]= false) Do đó chúng ta phải quét từ đầu mảng và tìm ra vị trí đầu tiên mà x[i] chưa sử dụng Nếu không tìm ra vị trí này thì có nghĩa là run đã hết CurrDich tăng lên 1, nghĩa

là tệp đích hiện thời sẽ là tệp tiếp theo của tệp đích hiện thời trước đó Lúc này ta kết thúc vòng lặp trộn run hiện thời và chuyển đến tệp đích tiếp theo Tuy nhiên nếu đó là tệp cuối cùng thì chu

kỳ sẽ quay lại từ đầu, tức là CurDich=0

Sau đây là đoạn lệnh thực hiện những điều vừa nói:

i=0;//Tim vi tri dau tien con phan tu trong run

while(!con_x[i] && i<nWay)i++;//Tim run dang con phan tu

if(i==nWay) //Het phan tu trong bo run, chuyen sang bo run khac

Trang 25

Quá trình trên đây đ−ợc thực hiện cho đến khi hết tất cả các phần tử trên các tệp nguồn

Khi việc trộn đã hoàn tất, có thể số tệp đích sẽ giảm xuống và ta cần tính lại Ta dùng lệnh

for(i=0;i<nWay && FileNodes(TepDich[i])>0;i++); nWay=i;

để thực hiện điều này ở đây ta vẫn cần điều kiện i<nWay để loại trừ khả năng có những tệp cùng tên nh−ng không phải là các tệp chúng ta quan tâm

void MergeFile(char *TepNguon[10],char *TepDich[10], int &nWay)

{FILE *f[10], *g[10];float x[10],t,t1;

int i,j,k,con_x[10],nNguon,CurrDich;

/*Neu run tren f[i] chua het thi con_x[i] =true

Neu het roi thi con_x[i] = false

nNguon la so file nguon co phan tu

nDich la so File dich da duoc cap phan tu

CurrDich la file dich hien thoi duoc ghi run

while(true) //Se ket thuc khi het cac phan tu tren file nguon

{//Vi cac run duoc phan phoi duoc phan bo tu i = 0 do do co the co

//mot so tep o phan cuoi co it hon 1 run

if(nNguon==0) break;//Da het cac phan tu tren cac file nguon

while(true) //Bat dau tron cac run va dua vao g[CurrDich]

{//Tim phan tu be nhat o dau run trong bo run

i=0;//Tim vi tri dau tien con phan tu trong run

while(!con_x[i] && i<nWay)i++;//Tim run dang con phan tu

if(i==nWay) //Het phan tu trong bo run, chuyen sang bo run khac

Trang 26

Hàm void CopyFile(char *Tep1, char *Tep2) sẽ copy một tệp gồm các phần tử số thực có tên

Tep1 sang tệp có tên là Tep2 Chúng ta lưu ý là hàm này chỉ copy một tệp chứa các số thực mà thôi Chúng ta cần hàm này để copy tệp đích cuối cùng vào tệp A ban đầu để nhận được tệp A

Hàm void SortFile(char *TenTep) sẽ thực hiện việc sắp xếp tệp TenTep theo phương pháp đa

lối cân bằng Chúng ta cần 2 mảng các chuỗi là TepNguon[10] và TepDich[10] Vì chúng ta hạn chế đường trộn là 10 nên chỉ để cỡ mảng 10 Trước hết ta cần nhập số đường trộn n Sau đó chương trình sẽ tạo ra các tệp nguồn và đích có dạng:

ZZN0,ZZN1,ZZN2, ZZD0,ZZD1,ZZD2,

Ví dụ nếu n = 4 ta sẽ có các tệp nguồn là ZZN0, ZZN1, ZZN2, ZZN3 và các tệp đích là ZZD0, ZZD1, ZZD2, ZZD3 Chúng ta đã sử sụng hàm

itoa(<số nguyên cần chuyển sang dạng chuối ký tự>, <chuỗi kết quả>, <Cơ số>);

<cơ số> = 10 có nghĩa là ta cần chuyển sang chuỗi ký tự biểu diễn số ở dạng thập phân Như vậy nếu <cơ số> = 10 thì khi chuyễn số 11 ta sẽ được chuỗi là "11", còn ở cơ số 2 thì được chuỗi là

"1011"

Hàm

strcpy(<chuỗi 1>, <chuỗi 2>);

thực hiện gán <chuỗi 2> cho <chuỗi 1> Ví dụ sau sẽ gán chuỗi s thành "Ha Noi"

char s[10]; strcpy(s,"Ha Noi");

Hàm

strcat(<chuỗi 1>, <chuỗi 2>);

thực hiện ghép <chuỗi 2> vào phía sau <chuỗi 1> Ví dụ đoạn lệnh

char s[] = "Ha "; strcat(s,"Noi");

sẽ cho kết quả là s ="Ha Noi"

Trang 27

Trương Hải Bằng-Cấu trỳc dữ liệu 2

27

với i = 5 chẳng hạn, sẽ cho ta giá trị chuỗi TepNguon[5] = "ZZN5.DAT"

chúng tôi sử dụng tên tệp bắt đầu bằng ZZ là có ý để các tệp này hiện ở cuối danh sách các tệp, không bị vướng khi xem các tệp khác

thực hiện vòng lặp trộn các tệp cho đến khi chỉ còn một tệp đích duy nhất Khi tệp đích chưa

được sắp xếp thì mỗi lần bắt đầu chu trình mới chúng ta phải hoán đổi vai trò các tệp nguồn và

đích ở đây chúng ta đã làm điều đó bằng cách hoán đổi tên

Cuối cùng ta copy tệp đích sang tệp ban đầu và xoá các tệp nguồn và đích trung gian đã sử dụng void SortFile(char *TenTep)

{TepNguon[i] = new char[12];

TepDich[i] = new char[12];

Trang 28

1.5 Phương pháp trộn đa pha (Polyphase merge)

Phương pháp sắp xếp kiểu đa lối cân bằng (hay nhiều đường cân bằng) đã có nhiều ưu điểm Tuy nhiên khi sử dụng các băng từ làm các đường cân bằng, ta thấy rằng một nửa số băng dùng cho các tệp nguồn và một nửa số băng dùng cho tệp đích Rõ ràng sau mỗi lần trộn thì các tệp

đích và các tệp nguồn đổi vai trò cho nhau và số lượng các băng so với số băng sử dụng lúc đầu giảm dần cho tới 1 Người ta nghĩ ra một cách để tăng số lượng các băng nguồn Tại mỗi thời

điểm chỉ có một băng đích, còn tất cả các băng khác là băng nguồn Khi có một băng nguồn bị cạn thì người ta lại sử dụng nó làm băng đích, băng đích ban đầu được sử dụng làm băng nguồn Quá trình chuyển đổi được xẩy ra một cách nhịp nhàng cho đến khi cả hệ thống chỉ còn một băng chứa dữ liệu đã sắp xếp Phương pháp sắp xếp kiểu này do R.L Gilstar nêu ra năm 1960 và

được gọi là trộn đa pha Có thể thấy ngay là để cho quá trình trộn đa pha thực hiện được thì dữ liệu ban đầu phải được phân bổ một cách thích hợp trên các băng nguồn Ví dụ nếu ta có 2 băng nguồn là T1, T2 và một băng đích là T3 Nếu số run trong T1 và trong T2 là các số Fibonacci liên tiếp thì ta có thể áp dụng phuơng pháp trộn đa pha như sau:

Ví dụ với n = 7 ta có F7 = 13, F6 = 8 Quá trình trộn được mô tả trong bảng sau:

13 8 Lúc đầu có 13 run trong T1 và 8 run trong T2

5 8 Trộn 8 run trong mỗi run vào T3, T1 còn 5 run

5 3 Trộn 5 run vào T2, trong T3 còn 3 run

3 2

1 2

1 1 Trộn 1 run trong T1 với 1 run trong T3 ở bước trước vào

T2, T3 còn lại 1 run

1 Trộn 1 run trong T2 với 1 run trong T3 ở bước trước vào

T1, chỉ còn lại một run, quá trình kết thúc

Các số Fibonacci thông thường như trên đây được gọi là các số Fibonacci cấp 1 Ta có thể định nghĩa các số Fibonacci cấp cao hơn và áp dụng trộn đa pha cho nhiều băng từ hơn

Tuy nhiên trong thực tế thì trong tệp ban đầu ta có số run không nhất thiết là tổng của các số Fibonacci liền nhau Người ta đã có các biện pháp khắc phục vấn đề này Tuy nhiên chúng ta sẽ không đi sâu tìm hiểu ở đây

Trang 29

hệ cơ số 32 Để tìm vị trí bộ nhớ của một chuỗi, thí dụ AKEY ta tính giá trị số tương ứng bằng công thức:

'Y'+32*('E'+32*('K'+32*'A')) = 25+32*(5+32*(11+32*1))

Bằng cách này, việc tính toán địa chỉ khá dễ dàng, nhưng ta lại gặp một trở ngại lớn: những khóa dài và bắt đầu bằng các ký tự ở cuối bảng chữ cái như x,y z sẽ có địa chỉ rất lớn, vượt ra ngoài phạm vi của bộ nhớ thông thường Các giá trị khóa thường có thể tạo nên tập hợp rất lớn,

ta không thể tạo nên ánh xạ 1 - 1 giữa các giá trị khóa và địa chỉ bộ nhớ được Ngoài ra ta còn gặp một số trở ngại khác, ví dụ giả sử mục tin của chúng ta chứa khá nhiều thông tin về nhân sự

và do đó mỗi mục có thể chiếm một vùng bộ nhớ khá lớn Như vậy nếu ta lưu trữ mục tin về nhân viên có tên “LAM” trong vùng bộ nhớ có địa chỉ là P = ‘L’*322+’A’*32 +’M’ thì ta phải lưu trữ nhân viên có tên “LAN” ở địa chỉ ở địa chỉ P+1 Rõ ràng địa chỉ này đã bị chiếm bởi nhân viên “LAM” Còn một vấn đề nữa, nếu ta ánh xạ khóa vào bộ nhớ thì rất có thể vùng bộ nhớ

đó đã bị chiếm bởi một chương trình khác và nếu ta cố tình sử dụng vùng này để lưu trữ dữ liệu thì có thể gây sự cố khôn lường Rõ ràng việc biến đổi khóa thành địa chỉ lưu trữ dữ liệu nói chung không khả thi, vì ta không thể biết được vùng địa chỉ nào còn trống Người ta đã tìm cách giải quyết vấn đề này bằng cách tạo ra một bảng chứa các phần tử là các địa chỉ của các mục dữ liệu Các khóa thay vì được biến đổi thành các địa chỉ sẽ được biến đổi thành các giá trị nguyên chỉ vị trí của địa chỉ trong bảng Bảng có cấu trúc và mục đích sử dụng như vừa nói được gọi là

bảng băm Hàm h(x) chuyển đổi khóa x thành vị trí trong bảng băm được gọi là hàm băm Bảng băm thông dụng nhất là một mảng gồm m thành phần H = (a 0 , a 1 , a m-1 ), trong đó a i

là các địa chỉ, tức là các con trỏ trỏ đến các bản ghi chứa dữ liệu Hàm băm h(x) là hàm biến

đổi khóa x thành vị trí trong bảng băm Nếu khóa là các số nguyên hoặc biễu diễn được bằng

số nguyên như các chuỗi ký tự, thì hàm băm được dùng nhiều nhất là hàm lấy giá trị phần dư khi

chia x cho m, có nghĩa là h(x)=x%m Người ta thường chọn m là số nguyên tố Như vậy hàm băm không cho giá trị địa chỉ trực tiếp mà chỉ cho vị trí của địa chỉ trong bảng băm Rõ ràng

với ánh xạ như vậy thì có nhiều giá trị khóa sẽ cho cùng một vị trí địa chỉ Hiện tượng này được gọi là hiện tượng đụng độ (collision) Khi xây dựng bảng băm, ta luôn chỉ rõ hàm băm tương ứng với nó và phương pháp tránh đụng độ

Phương pháp bảng băm thường cân đối được thời gian truy xuất và dung lượng bộ nhớ nên

được sử dụng khá rộng rãi trong các hệ thống máy tính nhằm cung cấp truy xuất nhanh đến một lượng lớn dữ liệu Sau đây ta sẽ khảo sát một số cách cài đặt bảng băm và các phương pháp tránh hiện tượng đụng độ

Trang 30

2.2 Các phương pháp tránh đụng độ

Khi có tập khóa K, ta phải xây dựng bảng băm, hàm băm sao cho tránh được hiện tượng đụng

độ Sau đây là một số phương pháp thường dùng:

2.2.1 Dùng danh sách liên kết

Cũng giống như trong phần cấu trúc danh sách chúng ta đã nói đến, danh sách liên kết là một tập hợp các bản ghi được sắp tuyến tính; mỗi bản ghi có 2 phần: phần chứa thông tin và phần chứa địa chỉ của bản ghi tiếp theo Thông thường bản ghi cuối cùng chỉ đến bản ghi rỗng tức là NULL

i Hình 2.1 sau đây minh họa bảng băm với hàm băm chia dư và m = 3

Hình 2.1 Bảng băm liên kết ngoài

ở đây các ô 0 được hiểu là địa chỉ nút đầu của danh sách các khóa đồng dư với 0 mod 3, ô 1

được hiểu là địa chỉ nút đầu của danh sách các khóa đồng dư với 1 mod 3

b Phương pháp liên kết trong

Ta vẫn dùng danh sách liên kết để tránh đụng độ, nhưng ta chỉ dùng các nút trong bảng H mà thôi Mỗi nút của H bây giờ có 2 trường: trường thứ nhất Pkey chỉ đến bản ghi chứa dữ liệu, còn trường thứ 2 là trường next chỉ đến nút tiếp theo trong danh sách các nút có giá trị khóa bị

đụng độ Khi khởi động các con trỏ Pkey chỉ tới NULL, các trường next = -1 để ám chỉ là không

có nút nào liên kết với nút đó Khi thêm một nút có khóa k trước hết ta tính hàm i = h(k) Nếu vị trí i chưa có nút, nghĩa là trường Pkey chỉ đến NULL thì ta đặt nút mới vào vị trí i Nếu nút đó đã

bị chiếm thì ta phân biệt 2 trường hợp:

a Nếu giá trị khóa trong bản ghi do H[i].Pkey chỉ tới (ví dụ khóa này là x) cùng họ với k (tức là h(x)=h(k)) ta lần theo trường next để đi tới bản ghi cuối cùng trong danh sách con; sau

đó ta tìm một nút trống cuối bảng để đặt nút mới vào đó Đặt con trỏ next của phần tử cuối cùng trước đó trỏ tới nút này và đặt con trỏ next của nút này bằng -1 và kết thúc

b Nếu giá trị khóa trong bản ghi do H[i].Pkey chỉ tới không cùng họ với k (tức là h(x)≠h(k)) ta tìm một nút trống cuối bảng để đặt nút mới vào đó Đặt con trỏ next của nút này bằng -1 và kết thức

Khi cần tìm kiếm khóa x trên bảng băm, trước hết ta đặt i = h(x) Nếu trường Pkey trong vị trí này chỉ đến nút có khóa = x việc tìm kiếm thành công và kết thúc; Còn nếu không như vậy thì ta xem khóa do ô này trỏ tới có cùng họ với x không Nếu có thì ta lần theo trường next , cho

đến khi gặp khóa x thì việc tìm kiếm thành công kết thúc; nếu gặp next = -1 mà vẫn không tìm

Trang 31

Hình sau biểu diễn cách thêm các nút 35, 15,45 vào bảng chia dư có m=10

ở đây ta ghi giá trị khóa thay cho địa chỉ của các nút thông tin chứa khóa Như vậy số 35 sẽ

được hiểu là địa chỉ của khóa 35

2.2.2 Dùng danh sách kề ngoài

Phương pháp này về cơ bản giống với phương pháp liên kết ngoài Ta cũng dùng một mảng H, nhưng các thành phần của H bây giờ là các danh sách kề cài đặt bằng mảng động Các danh sách con này tuy có cỡ cố định nhưng ta có thể dùng tác vụ grow để tăng lên khi cần thiết Như vậy khi khai báo, để khỏi lãng phí bộ nhớ ta chỉ cần đặt cỡ của danh sách khá nhỏ; khi cần thiết tác

vụ grow sẽ tự động tăng cỡ của danh sách lên

Có thể mô tả bảng băm dùng danh sách kề với kích thước và các khóa như ở hình 2.1 như sau:

ngoài

Khi dùng danh sách liên kết ngoài hoặc danh sách kề ngoài ta thấy rằng nếu cỡ của bảng băm

là M thì ta cần M danh sách Kinh nghiệm cho thấy rằng nên chọn M đủ lớn sao cho độ dài các danh sách chỉ khoảng 10 phần tử Như vậy M bằng khoảng 1/10 số khóa sử dụng

2.2.3 Phương pháp dò tuyến tính (linear probing method)

Bản chất của phương pháp này là nêu ra quy tắc để dùng các nút còn trống trong bảng để chứa các nút bị đụng độ Với một khóa k bất kỳ ta tính hàm băm i = h(k) và thực hiện các bước sau:

- Nếu nút i trống thì thêm nút mới tại địa chỉ này

- Nếu bị xung đột thì dùng hàm băm lại h1(k) = (h(k)+1)%m

- Nếu vẫn bị xung đột thì dùng hàm băm lại h2(k) = (h(k)+2)%m

Trang 32

Cứ như vậy đến bước thứ j ta dùng hàm băm lại hj(k) = (h(k)+j) % m cho đến khi tìm được

vị trí trống Lưu ý là nếu dò đến cuối bảng thì ta lại quay lại từ đầu (bản thân hàm hj(k) đã bao hàm điều này)

2.2.4 Phương pháp dò bậc hai (quadratic probing method)

Bảng băm dò tuyến tính bị hạn chế là các nút rải không đều Người ta dùng hàm băm lại bậc 2 với hy vọng rải các nút đều hơn Cách thực hiện như sau: Khi cần thêm nút có khóa k vào bảng, trước hết ta tính i = h(k), là một giá trị trong khoảng từ 0 đến m-1

Chú ý: Bảng băm với phương pháp dò bậc 2 nên chọn m là số nguyên tố

2.3 Cài đặt bảng băm

Sau đây chúng tôi giới thiệu 3 chương trình: cài đặt bảng băm dùng danh sách liên kết ngoài, bảng băm dùng danh sách kề ngoài và bảng băm dùng liên kết trong Phép băm dùng đến trong cài đặt là phép chia dư Bạn đọc có thể tự cài đặt các loại bảng băm khác

2.3.1 Cài đặt bảng băm dùng danh sách liên kết ngoài

Chúng ta sẽ tận dụng cài đặt danh sách liên kết trong phần trước và dùng lệnh #include để chèn vào đầu tệp chương trình nguồn cài đặt bảng băm Bảng băm giờ đây đơn giản là một mảng

mà mỗi phần tử là một cấu trúc danh sách

Khai báo và định nghĩa cấu trúc danh sách bằng phương pháp liên kết:

Chúng ta sẽ khai báo và định nghĩa một lớp danh sách và lưu trong tệp LIST_H.CPP Các phần tử của danh sách là các nút thông tin có cấu trúc được định nghĩa trong chương trình cài đặt bảng băm, trước khi có lệnh # include để đưa tệp LIST_H.CPP vào chương trình Cấu trúc nút thông tin có dạng:

struct node {int key; node* next; }

trong đó trường key chứa thông tin, còn trường next là địa chỉ của nút tiếp theo trong danh sách Chỉ có một số tác vụ cơ bản được định nghĩa trên danh sách như empty, display, dele, append, traverse, search, clear Bạn đọc có thể tìm hiểu trực tiếp từ chương trình

Trang 33

node* phead; //A pointer to the first item

node* pcurrent; //A pointer to the current item

int count; //So phan tu

List();//Khoi tao

~List();//Huy khoi tao

int empty();//Kiem tra danh sanh neu rong thi tra ve 1

void display();//Hien khoa hien thoi

void dele();//Xoa ban ghi hien thoi

void append(int);//Them mot phan tu cuoi danh sach co khoa da cho

void traverse();//Xem toan bo danh sach

int search(int);//Tim kiem khoa x

void clear();//Xoa danh sach, giai phong bo nho

Trang 35

Chúng ta dùng mảng động H để cài đặt bảng băm Mỗi phần tử của mảng bây giờ không phải

là số nguyên, ký tự hay nút thông tin như thường lệ mà là một đối tượng danh sách Biến max là

cỡ của bảng băm, cơ sở của phép chia dư Như vậy một khóa x bất kỳ sẽ được chèn vào phần tử thứ i = x%max của bảng băm, tức là chèn vào danh sách liên kết thứ i Con trỏ pcurrent là địa chỉ của nút hiện thời, là địa chỉ quy ước dùng trong các tác vụ tìm kiếm Các thao tác trên bảng băm khá đơn giản, vì chủ yếu các thao tác này gọi đến các tác vụ trên danh sách

int max; //size of Hash table

node* pcurrent; //current node of the hash table

int count; //So phan tu trong bang bam

Trang 36

10 Trong tác vụ này một mảng động gồm max danh sách được cấp phát bộ nhớ Tuy nhiên lúc

đầu các danh sách còn rỗng, nên thực ra max danh sách cũng chỉ là max con trỏ

ƒ Hủy khởi tạo bảng băm

Khi kết thúc sử dụng bảng băm ta giải phóng vùng bộ nhớ đã cấp cho bảng băm Lần lượt đi từ

đầu bảng đến cuối bảng, ta gọi tác vụ clear của danh sách để giải phóng vùng bộ nhớ tương ứng void HashTab::~HashTab()

{for(int i=0;i<max;i++) H[i].clear();

delete [] H;

return;

}

ƒ Tìm kiếm trên bảng băm

Để tìm kiếm một khóa x trên bảng băm, đầu tiên ta xác định danh sách mà trên đó có thể có

x Danh sách cần xác định chính là danh sách thứ i = x%max trên bảng băm Ta dùng tác vụ search của danh sách này để tìm kiếm khóa x Nếu tìm thấy thì tác vụ trả về giá trị true, con trỏ pcurrent chỉ nút tìm thấy trên danh sách; Nếu không tìm thấy thì tác vụ trả về giá trị false

printf("\Nhap du lieu vao bang bam:");

printf("\n1 Nhap truc tiep");

Trang 37

Cấu trúc dữ liệu 2 – Chương 2 Bảng băm

37

printf("\n2 Tao ngau nhien");

printf("\n\n Hay chon 1 hoac 2: ");

2.3.2 Cài đặt bảng băm dùng danh sách kề ngoài

Chúng ta sẽ tận dụng cài đặt danh sách kề trong phần trước và dùng lệnh #include để chèn vào đầu tệp chương trình nguồn cài đặt bảng băm Bảng băm giờ đây đơn giản là một mảng mà mỗi phần tử là một cấu trúc danh sách

Khai báo và định nghĩa cấu trúc danh sách kề:

Chúng ta sẽ khai báo và định nghĩa một lớp danh sách và lưu trong tệp LISTKE_H.CPP Các phần tử của danh sách có kiểu nguyên, cùng kiểu với khóa Trong các bài toán thực tế, các phần

tử này có thể có cấu trúc phức tạp hơn

Chỉ có một số tác vụ cơ bản được định nghĩa trên danh sách như empty, full, display, dele, append, traverse, sort, sorted, bsearch, insert, clear Bạn đọc có thể tìm hiểu trực tiếp từ chương trình

Trang 38

int max; //Starting size of storage array

int delta; //Increment size of storage array

int count; //number of items in list

int current; //currently indicated item (for search etc )

int grow(); //Function to increase list size

List(int); //Khoi tao mang chua danh sach co co size

~List(); //Huy khoi tao

int empty();//Kiem tra neu danh sach rong thi tra ve true

int full(); //Kiem tra neu danh sach day thi tra ve gia tri true

void display();

void dele();

void append(int);//Them mot phan tu cuoi danh sach co khoa da cho

void traverse(); //Duyet danh sach

void sort(); //Sap xep danh sach

int sorted(); //Kiem tra neu danh sach da sap xep thi tra ve true

int search(int);//Tim kiem tuyen tinh tren danh sach

int bsearch(int);//Tim kiem nhi phan tren danh sach

void insert(int);//Chen phan tu vao danh sach da sap xep

void clear(); //Xoa danh sach, giai phong bo nho

int* newa=new int [newmax];

if(!newa) return false; //Allocation failed

//Copy original list contents to new space

Trang 39

CÊu tróc d÷ liÖu 2 – Ch−¬ng 2 B¶ng b¨m

39

for(int i=0;i<max;i++) newa[i]=a[i];

delete [] a; //Get rid of old storage

a=newa; //Pointer to new list now in data

Trang 40

/*Tim thay thi dat current=vi tri tim thay va tra ve true, khong tim thay tra ve false*/

/*Tim kiem tuyen nhi phan khoa x

/*Tim thay thi dat current=vi tri tim thay va tra ve true, khong tim thay tra ve false*/

int List::bsearch(int x)

{//Kiem tra xem danh sach da sap xep chua

if(!sorted()) {cout<<"Danh sach chua sap xep";return false;}

int bottom,mid,top;

bottom=0,top=count-1;

while(bottom<=top)

{mid=(bottom+top)/2;

if(a[mid]==x) {current=mid;return true;};

if(x<a[mid]) top=mid-1; else bottom=mid+1;

if(full() && !grow()) {cout<<"Danh sach bi day";return;}

//Kiem tra xem danh sach da sap xep chua

if(!sorted()) {cout<<"Danh sach chua sap xep";return;}

for(i=0;x>a[i] && i<count;i++);

Ngày đăng: 20/10/2014, 06:49

HÌNH ẢNH LIÊN QUAN

Hình 2.1  Bảng băm liên kết ngoài. - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
Hình 2.1 Bảng băm liên kết ngoài (Trang 30)
Cấu trúc dữ liệu 2 – Ch−ơng 2. Bảng băm - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
u trúc dữ liệu 2 – Ch−ơng 2. Bảng băm (Trang 31)
Hình 2.2  Bảng băm liên kết trong. - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
Hình 2.2 Bảng băm liên kết trong (Trang 31)
Hình 3.2. Cây nhị phân đầy đủ - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
Hình 3.2. Cây nhị phân đầy đủ (Trang 47)
Hình 3.4 sau mô tả một  cây 2-3-4: - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
Hình 3.4 sau mô tả một cây 2-3-4: (Trang 49)
Hình  3.6.  Xóa khóa  32  trên  cây 2-3-4 - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
nh 3.6. Xóa khóa 32 trên cây 2-3-4 (Trang 51)
Hình  3.7.  Cây sau khi xóa khóa  32  Ta nhận thấy nút  F   ở  trong tình trạng cạn kiệt - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
nh 3.7. Cây sau khi xóa khóa 32 Ta nhận thấy nút F ở trong tình trạng cạn kiệt (Trang 51)
Hình  3.9.  Chuyển đổi cây 2-3-4 sang cây đỏ đen - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
nh 3.9. Chuyển đổi cây 2-3-4 sang cây đỏ đen (Trang 53)
Hình 3.12.a. Nhánh cây con nút gốc s tr−ớc khi thêm nút. - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
Hình 3.12.a. Nhánh cây con nút gốc s tr−ớc khi thêm nút (Trang 56)
Hình 3.14.a.  Bal(s1)=0, xoay phải  s, độ cao cây không đổi  (=n+1). - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
Hình 3.14.a. Bal(s1)=0, xoay phải s, độ cao cây không đổi (=n+1) (Trang 60)
Hình 3.14.c.1.  Bal(s1)=+1, bal(s2)=0,xoay kép, cây thấp hơn (n&lt;n+1). - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
Hình 3.14.c.1. Bal(s1)=+1, bal(s2)=0,xoay kép, cây thấp hơn (n&lt;n+1) (Trang 61)
Hình 4.1   Cây tìm kiếm  bậc  3. - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
Hình 4.1 Cây tìm kiếm bậc 3 (Trang 74)
Hình 4.2   B- cây cấp  2, tất cả các nút đều có  từ   2  đến 4 khoá - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
Hình 4.2 B- cây cấp 2, tất cả các nút đều có từ 2 đến 4 khoá (Trang 75)
Hình  4.4.  Xóa khóa  32  trên  B-cây - Tài liệu hỗ trợ môn cấu trúc dữ liệu 2
nh 4.4. Xóa khóa 32 trên B-cây (Trang 77)

TỪ KHÓA LIÊN QUAN

TRÍCH ĐOẠN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w