Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 42 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
42
Dung lượng
394,06 KB
Nội dung
PHÉPBIẾNĐỔIKHÓA–HÀMBĂMBăm là phương pháp rất thích hợp để cài đặt tập hợp có số phần tử lớn và thời gian cần thiết để thực hiện các phép toán từ điển, ngay cả trong trường hợp xấu nhất, là tỉ lệ với cỡ của tập hợp. Chúng ta sẽ đề cập tới hai phương pháp băm khác nhau. Một gọi là băm mở cho phép sử dụng một không gian không hạn chế để lưu giữ các phần tử của tập hợp. Phương pháp băm khác được gọi là băm đóng sử dụng một không gian cố định do đó tập hợp được cài đặt phải có cỡ vượt qua không gian cho phép $1. Khái niệm băm và hàm băm. 1.1. Băm mở và hàm băm. a. Băm mở. Nội dung cơ bản của băm mở là phân chia tập hợp đã cho thành một số cố định các lớp. Chẳng hạn ta muốn phân thành N lớp được đánh số 0,1, ,N-1. Ta sử dụng mảng T với chỉ số chạy từ 0 đến N-1. Mối thành phần T[i] cúa mảng được nói đến như một rổ đựng các phần tử của tập hợp thuộc lớp thứ i. Các phần tử của tập hợp thuộc mỗi lớp tổ chức dưới dạng một danh sách liên kết. Do T[i] sẽ chứa con trỏ trỏ đến danh sách của lớp i. b. Bảng băm. Ta gọi mảng T mà mỗi phần tử của nó như là một rổ đựng các phần tử của tập hợp thuộc lớp tương ứng là bảng băm. Việc phân chia các phần tử của tập hợp vào các lớp được thực hiện bởi hàmbăm h. c. Hàm băm. Nếu x là một giá trị khoá của phần tử nào đó của tập hợp thì h(x) là chỉ số nào đó của mảng T và ta gọi h(x) là giá trị băm (hash value) của x. Như vậy h là ánh xạ từ tập hợp các khoá K vào tập hợp { 0,1,…,N-1}. $.2. Các phương pháp lựa chọn, thiết kế hàmbăm và giải quyết va chạm. 2.1. Các phương pháp lựa chọn, thiết kế hàmbăm Có hai tiêu chuẩn chính để lựa chọn hàm băm. Trước hết nó phải cho phép tính được dễ dàng và nhanh chóng giá trị băm của mỗi khoá. Thứ hai, nó phải phân bố đều các khóa vào các rổ. Trên thực tế tiêu chuẩn thứ hai rất khó được thực hiện. Sau đây chúng ta đưa ra một số phương pháp thiết kế hàm băm. a. Phương pháp cắt bỏ. Giả sử khoá là số nguyên (nếu khoá không phải là số nguyên, ta xét đến các mã số của chúng). Ta sẽ bỏ đi một phần nào đó của khóa, và lấy phần còn lại lám giá trị băm của khoá. Chẳng hạn nếu khoá là các số nguyên gồm 10 chữ số và bảng băm gồm 1000 thành phần, khi đó ta có thể lấy chữ số thứ nhất, thứ ba và thứ bảy từ bên trái làm giá trị băm. Ví dụ: h(7103592810) = 720. Phương pháp cắt bỏ rất đơn giản nhưng nó thường không phân bố đều các khoá. b. Phương pháp gấp. Giả sử khoá là số nguyên. Ta phân chia khóa thành một số phần, sau đó kết hợp các phần lại bằng một cách nào đó (phép cộng hoặc phép nhân) để nhận giá trị băm. Nếu khoá là số nguyên 10 chữ số ta phân thành các nhóm ba ba hai hai chữ số từ bên trái, cộng các nhóm với nhau sau đó cắt cụt nếu cần thiết, ta sẽ nhận được giá trị của hàm băm. Ví dụ: số 7103592810 được biếnđổi thành 710+359+28+10 = 1107, do đó ta có giá trị băm là 107. Vì mọi thông tin trong khoá đều được phản ánh vào giá trị băm, nên phương pháp gấp cho phân bố đều các khoá hơn phương pháp cắt bỏ. c. Phương pháp sử dụng phép toán lấy dư. Giả sử khoá là số nguyên và ta muốn chia tập hợp các khoá thành n lớp. Chia số nguyên cho n rồi lấy phần dư làm giá trị băm. Điều này trong Pascal được thực hiện bằng phép toán mod. Tính phân bố đều các khoá của hàmbăm được xác định bằng phương pháp này phụ thuộc nhiều vào việc chọn n. Tốt nhất chọn n là số nguyên tố. Chẳng hạn thay cho việc chọn n = 1000 ta sẽ chọn n = 997 hoặc n = 1009. Ví dụ viết một hàmbăm trong Pascal để băm các khoá là các xâu kí tự có độ dài 10 thành các giá trị từ 0…n-1. Type keytype = string[10]; Function h(x: keytype):0 n-1; Var i, sum:integer ; Begin Sum:=0; For i=1 to 10 do Sum=sum + ord(x[i]); h:= sum mod n; end. Trong hàmbăm trên, ta đã chuyển đổi các xâu kí tự thánh các số nguyên bằng cách lấy tổng số của các mã số của từng kí tự trong xâu dùng hàm ord(c) để lấy mã số của kí tự c. 2.2. Bảng băm đóng. Trong bảng băm mở, mỗi thành phần T[i] của bảng lưu trữ con trỏ trỏ tới danh sách các phần tử của tập hợp được đưa vào lớp thứ i (i = 0,…,N-1). a. Băm đóng. Khác với bảng băm mở, trong bảng băm đóng, mỗi phần tử của tập lưu giữ trong chính các thành phần T[i] của mảng. Do đó ta có thể khai báo kiểu dữ liêụ từ điển được cài đặt bởi bảng băm đóng như sau: 0 1 2 Type Dictionary = array [0 N-1] of keytype; Keytype là kiểu dữ liệu của khoá của các phần tử trong từ điển. 2.3. Phân tích, đánh giá và minh họa phương pháp băm. a. Phân tich đánh giá. Trong tất cả những thuật toán được xét từ trước tới nay, việc định vị một mục được xác định bởi một dãy các so sánh. Trong mỗi trường hợp, mục cần tìm được so sánh nhiều lần với các mục ở những vị trí nào đó trong cấu trúc. Đối với nhóm n mục, phép tìm kiếm tuyến tính đòi hỏi O(n) lần so sánh, trong khi đó phép tìm kiếm nhị phân chỉ cần O(log n) lần so sánh. Trong một số trường hợp, những thuật toàn này thực hiện còn quá chậm. Ví dụ, bảng các kí hiệu được lập bởi bộ dịch lưu trữ các định danh và những thông tin về chúng. Vận tốc xây dựng và tìm kiếm trong bảng này quyết định vận tốc dịch. Một cấu trúc dữ liệu thực hiện tìm kiếm nhanh hơn, được gọi là bảng băm (hash T), trong đó vị trí cuả một mục được xác định trực tiếp bằng một hàm của chính nó chứ không phải bằng một dãy các so sánh thử và sai (trial-and-error). Thời gian gian cần thiết để định vị một mục trong bảng băm trong trường hợp tốt nhất là O(1), nghĩa là nó là một hằng số và không phụ thuộc vào số các mục được lưu trữ. b. Ví dụ minh họa. Để minh hoạ, giả sử rằng cần lưu trữ 25 số nguyên trên đoạn 0 999 trong bảng băm. Có thể cài đặt bảng băm này bằng một mảng T các số nguyên được đánh chỉ số trên đoạn 0 999, trong đó mỗi phần tử của mảng được khởi động tại một giá trị câm nào đó, chẳng hạn như tại 1. Nếu ta dùng mỗi số nguyên i trong tập hợp đó làm chỉ số, nghĩa là nếu ta lưu trữ i trong T[i], ta có thể định vị một số nguyên number cho trước chỉ bằng cách kiểm tra T[number] =number hay không. Hàm h được định nghĩa bởi h(i)=i xác định vị trí của mục i trong bảng băm. Hàmbăm trong vị dụ này thực hiện tìm kiếm một cách lý tưởng vì thời gian cần thiết để tìm kiếm một giá trị cho trước trong báng là hằng số; chỉ cần thử một vị trí. Như vậy, sơ đồ này hiêu quả về tính thời gian, nhưng không hiệu quả về không gian, chỉ 25 trong số 1000 vị trí khả dĩ được dùng để lưu trư các mục còn 975 vị trí còn lại không được dùng do đó đã lãng phí 97,5% không gian, chỉ dùng 2,5%. Vì có thể lưu trữ 25 giá trị ở 25 vị trí ta có thể sử dụng tốt hơn bằng cách dùng mảng T được đánh chỉ số trên đoạn từ 0 đến 24. Dĩ nhiên là không thể dùng hàmbăm ban đầu h(i)=i được nữa. Thay vào đó ta có thể dùng: h(i)=i mod 25 Vì hàm này luôn luôn có miền giá trị là những số nguyên trên đoạn từ 0 đến 24. Như vậy số nguyên 52 sẽ được lưu trữ ở T[2] vì h(52)= 52 mod 25 = 2. Tương tự với các số nguyên 129; 500 ; 73; 49 sẽ được lưu tại các vị trí 4, 0, 23 và 24 theo thứ tự đó. T[0] T[1] T[2] T[3] T[4] T[5] . . T[23] T[24] Với ví dụ trên nếu lấy hàm h[i]:=i mod 25 thì sẽ xẩy ra hiện tượng gọi là sự va chạm. vì nếu 77 được lưu trữ, nó sẽ được đặt vào vị trí h(77)= 77 mod 25 = 2, nhưng vị trí này đã bị chiếm bởi 52. Cũng bằng cách đó, nhiều giá trị khác có thể trùng vào một vị trí cho trước,ví dụ,2,27,102 và trong thực tế tất cả các số nguyên dạng 25k + 2 đều được lưu trữ tại vị trí 2. Dĩ nhiên là cần phải có một cách nào đó để giải quyết những va chạm này. $3. Các phương pháp xử lý va chạm. 3.1.Phương pháp thăm dò tuyến tính. a. Nội dung. Trong sơ đồ này, phép tuyến tính trong bảng bắt đầu từ chỗ có va chạm và tiếp tục dò đền khi tìm thấy một khe trống có thể lưu trữ được, va chạm với giá trị 52 ở vị trí 2, một cách đơn giản là ta đặt 77 ở giá trị 3; để chèn 102, 500 -1 52 -1 129 -1 . . 273 49 chúng ta đi theo dãy thăm dò gồm các vị trí 2, 3, 4 và 5 để xác định vị trí đầu tiên có thể dùng được và lưu trữ 102 ở T[5]. Khi đặt đến cuối bảng, ta lại tiếp tục ở vị trí đầu tiên. Ví dụ, 123 được lưu trữ ở vị trí 1 vì nó va chạm với 273 ở vị trí 23, và dãy thăm dò 23, 24, 0, 1 xác định khe rỗng đầu tiên ở vị trí 1. T[0] T[1] T[2] T[3] T[4] T[5] . . T[23] T[24] Để xác định một giá trị cho trước có nằm trong bảng băm này hay không, trước hết ta dùng hàmbăm để tính vị trí có thể tính trong bảng xẩy ra các khả năng: + Thứ nhất, nếu vị trí này rỗng (chứa -1), ta có thể kết luận ngay rằng giá tri này không có trong bảng. +Thứ hai, nếu vị trí này chứa giá trị nói trên, ta biết ngay rằng là đã tìm ra. +Thứ ba, vị trí này có một giá trị khác với giá trị đang tìm do sự va chạm gây ra bởi cách lập bảng. Trong trường hợp này ta thực hiện tìm kiếm tuyến tính “vòng” tại vị trí này, và tiếp tục cho đến khi tìm thấy giá trị đó hoặc đạt đến vị trí rỗng hay vị trí khởi đầu, điều này chỉ ra rằng mục cần tìm không có trong bảng. Như vậy thời gian tìm kiếm trong hai trường hợp đầu là hằng số, còn trong trường hợp thứ ba không phải thế. Nếu bảng gần đầy, ta phải thử hầu hết các vị trí trước khi tìm ra mục đó hay trước khi kết luận rằng mục đó không có trong bảng. Để làm tốt hơn việc tìm kiếm, ta có thể thực hiện ba việc dưới đây: - Mở rộng kích thước của bảng. - Dùng phương pháp khác để giải quyết va chạm. - Dùng một hàmbăm khác. b. Nhận xét. 500 123 52 77 129 102 . . 273 49 - Việc làm cho kích thước của bảng bằng số các mục được lưu trữ như trong ví dụ đầu tiên không thực tế lắm, nhưng nếu dùng bảng nhỏ hơn có khả năng dẫn tới va chạm. Trong thực tế, ngay cả khi có thể lưu trữ nhiều hơn hẳn số mục cần thiết vẫn có khả năng xảy ra va chạm. Ví dụ, với bảng băm gồm 365 vị trí lưu trữ 23 mục được chọn ngẫu nhiên, xác xuất xảy ra va chạm là lớn hơn 0.5 (Điều này liên quan đến bài toán sinh nhật: xác xuất để ít nhất 2 trong 23 người có cùng ngày sinh là lớn hơn 50%). Như vậy rõ ràng rằng việc chờ đợi một bảng băm hoàn thiện không bị va chạm là không xác đáng. Thay vào đó, chúng ta phải thoả mãn với với số va chạm đủ ít. Những nghiên cứu thực nghiệm cho phép chúng ta dùng những bảng có độ dài khoảng từ 1.5 đến 2 lần số mục cần lưu trữ. - Cách thứ hai để hoàn thiện việc tìm kiếm là thiết kế một phương pháp xử lý va chạm tốt hơn. Trong sơ đồ thăm dò tuyến tính, khi xảy ra va chạm, giá trị va chạm được lưu trữ ỏ những vị trí được để dành cho các mục băm trực tiếp đến các vị trí ấy. Lối tiếp cận “lấy của Peter để trả cho Paul” này làm tăng xác xuất xảy ra va chạm, như vậy làm cho vấn đề phức tạp hơn. 3.2. Phương pháp dây chuyền(chaining). Phương pháp này dùng các danh sách liên kết để lưu trữ các giá trị. Trong phương pháp này, ứng với một địa chỉ của bảng ta có một danh sách liên kết chứa các phần tử có khóa khác nhau mà có cùng một địa chỉ. Để minh họa, giả sử ta muốn lưu trữ một nhóm các tên. Ta có thể dùng mảng T chứa các con trỏ được đánh chỉ số trên đoạn ‘A’ ’Z’, lúc đầu là rỗng, và dùng hàmbăm h(Name) = Name[1]. Ví dụ ‘Adam,John’ và ‘Doe,Mary’ được lưu trữ trong các nút được chỉ bởi T[‘A’] và T[‘D']: T[‘A”] T[‘B’] T[‘C’] T[‘D’] T[‘E’] T[‘F’] T[‘Y’] T[‘Z’] . . Adams,Joh ● Doe, Mary ● Khi xảy ra va chạm, ta chèn mục mới vào vị trí thích hợp của danh sách liên kết . Ví dụ, vì h(‘Davis, Joe’) = h(‘Doe, Mary’) = ‘D’, va chạm xảy ra khi ta muốn lưu trữ tên ‘Davis, Joe’, ta đưa thêm một nút mới chứa tên này vào danh sách liên kết được ghi bởi T[‘D’]: T[‘A”] T[‘B’] T[‘C’] T[‘D’] T[‘E’] T[‘F’] . . T[‘Y’] T[‘Z’] a. Khai báo. Type ref=^node; Node=record Key:integer; Info:integer; Next:ref; end; Var heads : aray[0 M-1] of ref; t,z: ref; T[0] T[1] T[2] T[3] T[4] T[4] . . T[M-2] T[M-1] . . Adams,John ● Davis, Joe ● Doe, Mary ● . . ● ● ● ● ● ● ● ● ● ● ● ● ● z b. Khởi tạo bảng băm.Mảng heads chứa các con trỏ đấu chỉ vào danh sách liên kết. Phép khởi tạo bảng băm cho các phần tử đầu chỉ đến phần tử cầm canh z Procedure Initialize; Var i:integer; Begin New(z); Z^.next:=z; For i:=1 to M-1 do Begin New(heads[i]; heads[i]^.next:=z; end; end; c. Thêm một khóa mới vào bảng băm. - Hàm Them_moi(k,heads[k mod M]) thực hiện thêm phần tử có khóa k vào danh sách liên kết của bảng băm. - Các danh sách liên kết thuộc vùng tràn đã có thứ tự. - Nếu đã có nhiều phần tử cùng khóa thì phần tử mới là đầu bảng băm. - Giá trị của hàm là địa chỉ của phần tử mới thêm vào. Function THEM_MOI(k:integer; u:ref): ref; Var x,p:ref; Begin Z^.key:=k; { gán k cho phần tử cầm canh} {Timf khóa k trong danh sách liên kết có con trỏ đầu là p} p:=u^.next { u là phần tử đứng ngay trước p} while p.key<k do begin u:=p; p:=p^.next; end; new(x); x^.next:=p; u^.next:=x; x^.key:=k; . . ● z THEM_MOI:=x; End; x Ghi chú: - Nút màu vàng là nút cuối cùng có khóa nhỏ hơn k. - Nút màu xanh có khóa >= khoa k. - Nút màu đỏ là nút x có khoa k. d. Tìm kiềm khpóa trong bảng băm. - Hàm TIM_KHOA(k,heads[k mod M]) thực hiện tìm phần tử đầu tiên có khóa k. - Để tìm tiếp các phần tử tiếp có khóa k ta gán u:=TIM_KHOA(k,u) cho đến khi u=z. - Giá trị của hàm là địa chỉ của phần tử tìm thấy hoặc phần tử cầm canh z. Function TIM_KHOA(k:integer; u:ref): ref; Var x,p:ref; {Tìm một khóa trong ds liên kết} Begin Z^.key:=k; { gán k cho phần tử cầm canh} repeat u:=u^.next; until k<=u^.key; if k= u^.key then TIM_KHOA:=u else TIM_KHOA:=z; End; Ví dụ. Xét ví dụ sau với M=11, giả sử các khóa được đưa vào một cách tuần tự: Khóa: A S E A R C H I N G E X A M P L E HASH: 1 8 5 1 7 3 8 9 3 7 5 2 1 2 5 1 5 z A A A L z M X z . . ● ● ● ● ● ● ● ● ● ● ● ● ● z ● . . . PHÉP BIẾN ĐỔI KHÓA – HÀM BĂM Băm là phương pháp rất thích hợp để cài đặt tập hợp có số phần tử lớn và thời gian cần thiết để thực hiện các phép toán từ điển, ngay. được cài đặt phải có cỡ vượt qua không gian cho phép $1. Khái niệm băm và hàm băm. 1.1. Băm mở và hàm băm. a. Băm mở. Nội dung cơ bản của băm mở là phân chia tập hợp đã cho thành một số. một khóa vào bảng băm. - Hàm THEM_KHOA(k:integer) thực hiện việc thêm khóa k vào bảng băm. - Giá trị của hàm trả về vị trí của phần tử thêm vào hoặc 0 nếu bảng băm đầy. - Nếu đã có khóa