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
http://www.ebook.edu.vn 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 http://www.ebook.edu.vn 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 http://www.ebook.edu.vn Trng Hi Bng-Cu trỳc d liu 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 http://www.ebook.edu.vn Trng Hi Bng-Cu trỳc d liu 2 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 lu 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, nhng để đơ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 lu 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, nhng về mặt logic thì có thể xem tệp là một dy 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à cha 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 lu trữ trên đĩa (thờng là ở vùng đầu của đĩa) để lu 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 lu 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. http://www.ebook.edu.vn Trng Hi Bng-Cu trỳc d liu 2 5 1.1.2. Các kiểu truy cập tệp: nhị phân và văn bản Trong ngôn ngữ C++ ta có thể truy cập tệp bằng 2 cách: nhị phân hoặc văn bản. Với một tệp bất kỳ ta đều có thể mở và thao tác theo kiểu nhị phân hoặc văn bản. Hai cách thao tác này có một số khác biệt nh sau: 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: void ThuTep() {FILE * f = fopen("a.dat","w+t"); fputc('A',f); fputc(26,f); fputc('B',f); fputc(255,f); fputc(255,f); fputc(10,f); fclose(f); char c; f = fopen("a.dat","rb"); while(!feof(f)) {c = fgetc(f);printf("%d ",c);} fclose(f); f = fopen("a.dat","rt"); printf("\n"); while(!feof(f)) {c = fgetc(f);printf("%d ",c); } fclose(f); } Kết quả trên màn hình là: 65 26 66 -1 -1 13 10 -1 65 -1 Vì ta tạo tệp theo kiểu văn bản nên khi ghi lên tệp ký tự 10 thì ký tự 13 cũng đợc chèn vào phía trớc (vậy là trớc khi xuống dòng phải về đầu dòng). Nếu ta mở lại tệp theo kiểu nhị phân thì có thể đọc đợc tất cả các ký tự đã ghi vào. Còn nếu ta mở tệp theo kiểu văn bản thì khi gặp ký tự 26 chơng trình cho là đã hết tệp và gán c = -1. Cũng ví dụ trên nhng chúng ta thay lệnh f = fopen("a.dat","w+t"); bằng lệnh f = fopen("a.dat","w+b"); thì kết quả là: 65 26 66 -1 -1 10 -1 65 -1 Tức là ký tự 13 không bị chèn vào trớc ký tự 10 nh trờng hợp tệp văn bản. http://www.ebook.edu.vn Trng Hi Bng-Cu trỳc d liu 2 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 lu trữ trên đĩa, nhng 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 lu 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: Kiểu mở ý nghĩa Kiểu mở ý nghĩa wb Mở tệp nhị phân mới chỉ ghi (nh vậy nếu đã có tệp cũ cùng tên thì tệp cũ sẽ bị xóa) wt Mở tệp văn bản mới chỉ ghi (nh vậy nếu đã có tệp cũ cùng tên thì tệp cũ sẽ bị xóa) 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 rb Mở tệp nhị phân đã có chỉ đọc rt Mở tệp văn bản đã có chỉ đọc r+b Mở tệp nhị phân đã có đọc/ ghi r+t Mở tệp văn bản đã có đọc/ ghi Đóng tệp: Cú pháp: fclose(<Con trỏ tệp>); Ví dụ đoạn chơng trình sau sẽ tạo một tệp có tên là A.DAT và ghi vào 3 ký tự A,B,C rồi đóng tệp: FILE *f; f = fopen("A.DAT","wb"); fput('A',f); fput(66,f); fput('C',f); fclose(f). b. Ghi thông tin lên tệp 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à 65 32 32 49 50 46 53 66 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à lu giá trị của nó mà thôi. c. Đọc thông tin từ tệp và đa vào biến bộ nhớ http://www.ebook.edu.vn Trng Hi Bng-Cu trỳc d liu 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 nhng 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à lu 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. e. Hàm ftell() Hàm ftell(<con trỏ tệp>) cho ta độ lớn của tệp tính bằng byte tính từ đầu tệp đến vị trí hiện thời của con trỏ. Ví dụ giả sử tệp với con trỏ f chứa các số thực đợc lu theo kiểu nhị phân (tức là một số thực chiếm 4 byte) thì các lệnh sau sẽ cho ta n là số phần tử trên tệp. fseek(f,0,2); n = ftell(f)/sizeof(float); 1.2. Phơng pháp trộn run có độ dài cố định 1.2.1. Mô tả thuật toán Khi trình bày các thuật toán sắp xếp, đặc biệt là phơng pháp trộn, ta rất hay phải dùng thuật ngữ "dãy đã sắp xếp". Vì vậy ngời ta đã thay thuật ngữ này bằng một từ đơn giản là "Run". Chúng ta sẽ không dịch thuật ngữ này sang tiếng Việt vì rất khó tìm đợc từ thích hợp. Vì các phần tử của tập tin về mặt logic có thể xem là một dãy tuyến tính liên tục từ đầu đến cuối tệp, do đó ta có thể dùng một dãy các phần tử A = {a 0 , a 1 , , a n-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 a 0 cho B, a 1 cho C, a 2 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ó [ 2 n ]+1 run, còn trên C có [ 2 n ] run. http://www.ebook.edu.vn Trng Hi Bng-Cu trỳc d liu 2 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à số run là 2 n (hàm trần của 2 n . Nếu tập A chỉ gồm 2 phần tử 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 n>2 ta đặt p = p*2 = 2 và chuyển sang bớc 2. 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: A 1 2 9 8 7 6 5 B 1 9 7 5 C 2 86 A' 1 2 8 9 6 7 5 . . . Bớc k: Lúc này trừ run cuối cùng, các run trên A độ dài cố định p = 2 (k-1) (có tất cả [ p n ]+1 run). Ta sẽ lần lợt phân bổ các run cho 2 tệp B và C. 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. . . . Trong ví dụ trên tại bớc k = 3 ta có p = 4 và kết quả phân bổ và trộn đợc thể hiện trong bảng sau: A 1 2 8 9 5 6 7 B 1 2 8 9 C 5 6 7 A' 1 2 5 6 7 8 9 Khi đặt p = p* 2 = 8 ta thấy p n do đó A chỉ chứa một run và tệp A đã đợc sắp xếp. 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à cha 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. http://www.ebook.edu.vn Trng Hi Bng-Cu trỳc d liu 2 9 B3. Tạo mới tệp A sao cho trong A không chứa phần tử nào. Lần lợt trộn từng cặp run độ dài p (trừ run cuối cùng trên B hoặc C có thể có độ dài ngắn hơn) trên B và C rồi đa vào A cho đến khi hết run trên cả B và C. Lu ý là cặp run cuối cùng có thể có một run rỗng, lúc này phép trộn đợc hiểu là đa toàn bộ run khác rỗng vào A. 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à lu 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 #include <conio.h> #include <stdio.h> #define true 1 #define false 0 #define sz (sizeof(float)) int FileNodes(char *TenTep); int EoF(FILE *f); 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); //===================== void main() {clrscr(); char *TenTep="A.DAT"; CreateFile(TenTep); ViewFile(TenTep); printf("\n\nTep sau khi sap xep la:"); SortFile(TenTep); ViewFile(TenTep); http://www.ebook.edu.vn Trng Hi Bng-Cu trỳc d liu 2 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) {int k;float x; FILE *f = fopen(TenTep,"rb"); fseek(f,0,2);//Ve cuoi tep k = ftell(f)/sz; fclose(f); return(k); }; 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 cha 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 cha 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 cha 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. int EoF(FILE *f) {float x; if(fread(&x,sz,1,f)>0) {fseek(f,-1.0*sz,1); return(false); }; return(true); } Hàm void CreateFile(char *TenTep) : Tạo tệp và nhập vào n số thực void CreateFile(char *TenTep) {int i,m;float x; FILE* f; f = fopen(TenTep,"wb"); rewind(f); char ch; do {clrscr(); printf("\nNhap du lieu vao tep:"); printf("\n1. Nhap truc tiep"); printf("\n2. Tao ngau nhien"); [...]... A 2 12 11 10 9 8 7 6 5 4 3 Giả sử ta chọn 3 đờng cần bằng và phân bổ tệp A vào 3 tệp nguồn nh sau: F1 2 12 9 6 3 F2 11 8 5 F3 10 7 4 Lần lợt trộn các bộ 3 run ở các tệp và lần lợt ghi vào các tệp đích G1, G2, G3 ta đợc G1 2 10 11 12 G2 7 8 9 G3 3 4 5 6 Bây giờ ta lại xem các tệp G1, G2, G3 là các tệp nguồn và trộn các run vào các tệp đích F1, F2, F3 và đợc kết quả là: F1 2 4 5 6 7 8 9 10 11 12 F2 3... 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 độ http://www.ebook.edu.vn Cấu trúc dữ liệu 2 Chơng 2 Bảng băm 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... Fn-1+Fn -2) Fn -2 Fn-3 (= Fn-4+Fn-5) T2 T3 Fn-1 Fn -2 (= Fn-3+Fn-4) Fn-4 Fn-1 (= Fn -2+ Fn-3) Fn-3 Ví dụ với n = 7 ta có F7 = 13, F6 = 8 Quá trình trộn đợc mô tả trong bảng sau: T1 T2 T3 13 8 5 8 5 3 3 2 1 2 1 1 1 Giải thích Lúc đầu có 13 run trong T1 và 8 run trong T2 Trộn 8 run trong mỗi run vào T3, T1 còn 5 run Trộn 5 run vào T2, trong T3 còn 3 run Trộn 1 run trong T1 với 1 run trong T3 ở bớc trớc vào T2,... nhiên chúng ta sẽ không đi sâu tìm hiểu ở đây http://www.ebook.edu.vn 28 Chơng 2 Bảng băm (Hash table) 2. 1 Mở đầu Thông thờng các thao tác tìm kiếm, chèn hay xóa một phần tử là các thao tác thờng xuyên phải thực hiện trên một cấu trúc dữ liệu ở cấu trúc danh sách, các thao tác này có thời gian tính toán tuyến tính Khi chuyển sang cấu trúc cây cân bằng ta đã đạt đợc sự cải tiến đáng kể: thời gian tính... tả quá trình trên thông qua việc sắp xếp tệp A với các giá trị ban đầu ở trên nh sau: Bớc 1: A 1 2 9 8 7 6 5 B 1 2 9 7 5 C 8 6 A' 1 2 8 9 6 7 5 http://www.ebook.edu.vn 14 Trng Hi Bng-Cu trỳc d liu 2 Bớc 2: A B C A' 1 2 8 9 6 7 1 2 8 9 5 6 7 1 2 6 7 8 9 5 A B C A' 1 1 5 1 5 5 Bớc 3: 2 2 6 6 7 8 9 7 8 9 2 5 6 7 8 9 Tại bớc 3 ta thấy rằng tệp A đã đợc sắp xếp và kết thúc Nếu mô tả thuật toán trên bằng... đó tơng ứng với địa chỉ Thí dụ nếu khóa chúng ta là chuỗi các ký tự A -> Z, ta có thể ký hiệu A=1, B =2, , Z =26 và hiểu một chuỗi ký tự là một số trong 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, nhng ta lại gặp một trở ngại lớn: những khóa... 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 http://www.ebook.edu.vn 31 Cấu trúc dữ liệu 2 Chơng 2 Bảng băm 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 Lu ý là nếu dò đến cuối bảng thì ta lại quay lại từ đầu... i+ 12 , i +22 , i+ 32 , , i+j2 với lu ý là đến cuối bảng ta trở về dò từ đầu bảng, thực chất là dò ở vị trí (i+j2) % m Khi thấy vị trí trống đầu tiên thì ta đặt nút mới vào vị trí đó Khi tìm một khóa thì trớc hết ta tìm ở vị trí i = h(k), nếu không thấy thì tìm ở các vị trí hj(k) = (h(k)+j) % m , j=1 ,2, Khi rơi vào vị trí trống thì có nghĩa là không tìm thấy Chú ý: Bảng băm với phơng pháp dò bậc 2 nên... 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à lu 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:... 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ì . (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. http://www.ebook.edu.vn 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 . (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