Đây là chuyên đề tin học về bảng băm (Hash table) của trường THPT chuyên Lê Khiết thành phố Quảng Ngãi. Trong tin học, bài toán tìm kiếm là một trong những bài toán phổ biến và xuất hiện nhiều trong thực tiễn cũng như trong các kì thi chọn học sinh giỏi các cấp. Các thuật toán tìm kiếm đều dựa vào việc so sánh giá trị khoá của phần tử cần tìm với giá trị khoá các phần tử trong một tập phần tử. Hầu hết các thuật toán tìm kiếm dựa trên việc so sánh giá trị khoá sẽ có thời gian thực hiện không nhanh và phụ thuộc vào kích thước của tập các phần tử. Chẳng hạn cho tập có n phần tử, với thuật toán tìm kiếm tuần tự ta có độ phức tạp tính toán là O(n), với thuật toán tìm kiếm nhị phân ta có độ phức tạp tính toán là O(logn), …. Một phương pháp giải quyết bài toán tìm kiếm tối ưu hơn, độ phức tạp tính toán là một hằng số và không phụ thuộc vào kích thước của tập dữ liệu là sử dụng bảng băm (Hash Table) để lưu trữ tập dữ liệu trên. Trong chuyên đề này trình bày cấu trúc dữ liệu bảng băm và ứng dụng của bảng băm để giải một số bài toán tìm kiếm và so khớp chuỗi.
SỞ GIÁO DỤC VÀ ĐÀO TẠO TỈNH QUẢNG NGÃI TRƯỜNG THPT CHUYÊN LÊ KHIẾT Chuyên đề CẤU TRÚC DỮ LIỆU BẢNG BĂM VÀ ỨNG DỤNG Quảng Ngãi, tháng năm 2016 I Lý lựa chọn chuyên đề Trong tin học, toán tìm kiếm toán phổ biến xuất nhiều thực tiễn kì thi chọn học sinh giỏi cấp Các thuật toán tìm kiếm dựa vào việc so sánh giá trị khoá phần tử cần tìm với giá trị khoá phần tử tập phần tử Hầu hết thuật toán tìm kiếm dựa việc so sánh giá trị khoá có thời gian thực không nhanh phụ thuộc vào kích thước tập phần tử Chẳng hạn cho tập có n phần tử, với thuật toán tìm kiếm ta có độ phức tạp tính toán O(n), với thuật toán tìm kiếm nhị phân ta có độ phức tạp tính toán O(logn), … Một phương pháp giải toán tìm kiếm tối ưu hơn, độ phức tạp tính toán số không phụ thuộc vào kích thước tập liệu sử dụng bảng băm (Hash Table) để lưu trữ tập liệu Trong chuyên đề trình bày cấu trúc liệu bảng băm ứng dụng bảng băm để giải số toán tìm kiếm so khớp chuỗi II Lý thuyết bảng băm (Hash Table) Phép băm Phép Băm phép biến đổi giá trị khóa phần tử liệu thành số sử dụng số để đánh địa cho phần tử liệu bảng Dựa bảng địa phép băm tham chiếu trực tiếp đến phần tử liệu Bảng băm (Hash Table) a Khái niệm Bảng băm T mảng lưu trữ địa định vị phần tử liệu có khóa phân biệt Bảng băm T có kích thước m địa (chỉ số) T đánh từ đến m-1, địa tương ứng với hoặc nhiều khóa Ví dụ: Cho tập liệu D có phần tử với giá trị khóa xâu “tra”, “cofe”, “banh”, “keo” Yêu cầu thực phép băm tập liệu để phục vụ cho việc tìm kiếm - Ta qui ước giá trị kí tự: 'a'=1, 'b'=2, , 'z'=26 - Lựa chọn thực phép băm cho tập liệu: Tính tổng giá trị số tương ứng kí tự có xâu - Kết phép băm sau: “tra” = 20 + 18 + = 39 “cofe” = + 15 + + =29 “banh” = + + 14 + = 25 Trang “keo” = 11 + + 15 = 31 - Tạo mảng T có phần tử kiểu xâu Đặt phần tử có khóa “tra”, “cofe”, “banh”, “keo” vào vị trí 39, 29, 25, 31 mảng T, vị trí lại mảng T rỗng - T bảng băm T … 25 banh … 29 cofe … 31 keo … 39 tra … - Sau xây dựng bảng băm T, cần kiểm tra phần tử có khóa xâu S có tập liệu D hay không ta việc tính giá trị băm khóa S truy xuất trực tiếp từ bảng băm T để kiểm tra Chẳng hạn: + Với S = ”gao” = + + 15 =23, T[23] rỗng phần tử cần tìm tập D + Với S = ”banh” = + + 14 + = 25, T[25] khác rỗng phần tử cần tìm có tập D b Phân loại bảng băm - Bảng băm đóng: Mỗi khóa ứng với địa chỉ, thời gian truy xuất số - Bảng băm mở: Một số khóa có địa chỉ, lúc mục địa danh sách liên kết phần tử có địa chỉ, thời gian truy xuất bị chậm đôi chút Hàm băm (Hash function): a Khái niệm Hàm băm ánh xạ h từ tập giá trị khoá K liệu vào tập số nguyên {0, 1,…, m -1}, m kích thước bảng băm, tức h: K {0, 1, …, m-1} Trang Với k ∈ K, ta có h(k) địa bảng băm Giá trị trả hàm băm gọi giá trị băm hay mã băm Khóa Hàm băm Mã băm K h h(K) Sự đụng độ (xung đột) Sự đụng độ khóa khác băm cho kết mã băm ki ≠ kj h(ki) = h(kj) Một hàm băm phải thỏa mãn điều kiện sau: - Tính toán nhanh - Các khóa phân bố bảng - Ít xảy đụng độ - Xử lý loại khóa có kiểu khác b Một số hàm băm thông dụng Hàm cắt bỏ Hàm cắt bỏ: Bỏ bớt phần khóa Ví dụ: Khóa số nguyên, k = 842615 Ta quy ước bỏ bớt chữ số hàng lẻ (1,3,5…), số lại 821 Vậy h(k) = h(842615) = 821 Nhận xét: Hàm cắt bỏ khó có phân bố Hàm gấp Hàm gấp: Chia số nguyên thành số đoạn tùy chọn, sau kết hợp phần lại theo quy ước Ví dụ: Khóa số nguyên, k = 842615 Số hàng lẻ: 465 Số hàng chẵn: 821 Vậy H(x)=465+821=1286 Nhận xét: Tính chất thứ hai thỏa mãn tốt Hàm phần dư Hàm phần dư: Lấy phần dư phép chia k/m Sử dụng hàm mod: Trang h(k) = k mod m Việc chọn m ảnh hưởng đến h(k), thông thường chọn m số nguyên tố Ví dụ: Bảng băm có 4000 mục ta nên chọn m = 4093 Nhận xét: Các cách lấy phần dư cho khả tránh tượng xung đột III Ứng dụng bảng băm giải số toán tìm kiếm so khớp chuỗi Lựa chọn hàm băm Viêc lựa chọn hàm băm có ảng hưởng lớn đến kết toán Lựa chọn hàm băm phải phù hợp, xác cao Khi lựa chọn hàm băm tiêu chí quan tâm hàm băm xảy đụng độ Trong chuyên đề lựa chọn hàm băm hàm phần dư để giải toán h(k) = k mod m - k khóa - m số nguyên tố Ta thấy: Nếu ki = kj h(ki) = h(kj), lập luận Nếu h(ki) = h(kj) ki = kj, lập luận sai Như nhận xét trên, cách lấy phần dư cho khả tránh tượng xung đột để sử dụng hàm phần dư giải toán phải chấp nhận lập luận sau ki = kj ⇔ h(ki) = h(kj) Ví dụ a Phát biểu toán Cho hai xâu A B gồm chữ thường Xâu A có x kí tự xâu B có y kí tự Yêu cầu: Hãy tìm tất vị trí mà B xuất A b Thuật toán Tóm tắt toán: - Xâu A gồm n kí tự: A[1 x] - Xâu mẫu B gồm y kí tự: B[1 y] - Xâu từ i đến j xâu A là: A[i j] - Chúng ta cần tìm tất vị trí i thỏa mãn (1≤i≤x-y+1) mà A[i i+y1]=B[1 y] Trang Xây dự hàm băm, bảng băm: - Tập chữ Latin thường gồm có 26 chữ {a, b,…, z} - Để biểu diễn xâu, thay dùng chữ cái, chuyển sang biểu diễn số hệ 26 - Chọn m số nguyên tố, chẳng hạn 109 + 2.109 + 11, … Ví dụ: Cho xâu s = “abcd” Tính giá trị h(S) Biểu diễn xâu S = “abcd” sang hệ có số 26: (‘a’-97)*26 3+ (‘b’97)*262+ (‘c’-97)*261 + (‘d’-97)*260, sau chuyển đổi số hệ số 10 tương ứng là: 0*263+1*262+2*26+3 = 731 h(S) = h(“abcd”) = 731 mod m Để B xuất vị trí A, cần duyệt qua vị trí xuất phát B A Giả sử vị trí i, kiểm tra A[i i + y − 1] có với B hay không việc đông nghĩa với cần tính kiểm tra mã Hash đoạn A[i i + y − 1] có mã Hash B hay không Chọn số chia m cho hàm phần dư m := 1000000000 + 7; Tính mã Hash xâu B HashB := 0; For i:=1 to y HashB:=(HashB*26+ord(B[i])-97) mod m; Tính mã Hash tất tiền tố Xâu A HashA[0]:=0; For i:=1 to x HashA[i]:=(HashA[i-1]*26+ord(A[i])-97) mod m; Tính mã Hash đoạn từ i đến j xâu A (1 ≤ i ≤ j ≤ x) Ví dụ: Cho xâu S= “abcdef” Tính mã Hash đoạn S[3 6] + Biểu diễn xâu S sang hệ số 26 + Mã Hash S[3 6] = (Mã Hash s[1 .6] - mã Hash S[1 2]) * 264 Vậy Mã Hash A[i j] =(( HashA[j] – HashA[i-1])*26j-i+1 +m*m)mod m Pow[0]:=1; For i:=1 to x Pow[i]:=Pow[i-1]*26 mod m; Function GetHash(i,j:longint):Int64; Begin Trang Exit((Hash[j]-Hash[i-1]*Pow[j-i+1] + m*m) mod m); End; Thuật toán giải toán For i:=1 to x-y+1 If Get(i,i+y-1)=HashB then Write(i,' '); Một số kinh nghiệm: - Chọn số chia m cho hàm phần dư: Chọn m số nguyên tố, chẳng hạn 109 + 2.109 + 11, … - Biểu diễn xâu sang số: Bảng kí tự sử dụng xâu gồm có b loại kí tự khác xâu chuyển sang số hệ số b Ví dụ: Các xâu sinh từ tập kí tự {a, b, , z} chuyển xâu sang số hệ số 26 Các xâu sinh từ tập kí tự {A,T,G,X} chuyển xâu sang số hệ số Các xâu sinh từ tập kí tự {-, } chuyển xâu sang số hệ số c Chương trình Uses Math; Const fi=''; fo=''; Var f:Text; A,B:Ansistring; Hash,Pow:array[0 1000000] of Int64; m,x,y,HashB:Int64; Procedure Build; Var i:longint; Begin Assign(f,fi); Reset(f); Readln(f,A); x:=length(A); Readln(f,B); y:=length(B); Close(f); m:=1000000007; Trang For i:=1 to x Hash[i]:=(Hash[i-1]*26+ord(A[i])-97) mod m; For i:=1 to y HashB:=(HashB*26+ord(B[i])-97)mod m; Pow[0]:=1; For i:=1 to x Pow[i]:=Pow[i-1]*26 mod m; End; Function Get(i,j:longint):Int64; Begin Exit((Hash[j]-Hash[i-1]*Pow[j-i+1] + m*m)mod m); End; Procedure Output; Var i:longint; Begin Assign(f,fo); ReWrite(f); For i:=1 to x-y+1 If Get(i,i+y-1)=HashB then Write(f,i,' '); Close(f); End; BEGIN Build; Output; END IV Bài tập áp dụng Bài Tiền tố hậu tố Xâu a gọi tiền tố xâu b xâu a trùng với phần đầu xâu b Ví dụ: pre tiền tố prefix Xâu a gọi hậu tố xâu b xâu a trùng với phần cuối xâu b Ví dụ: fix hậu tố suffix Cho hai xâu a, b gồm kí tự latin thường ('a' đến 'z') Hai xâu a b không thiết phải khác có độ dài không quá105 kí tự Yêu cầu: Tìm xâu c thỏa mãng: - Xâu a tiền tố xâu c - Xâu b hậu tố xâu c Trang - Độ xài xâu c ngắn Input: Bai1.inp Dòng 1: Xâu a Dòng 2: Xâu b Output: Bai1.out Một dòng xâu c Ví dụ: Bai1.inp Bai1.out abca cab abcab abc abc abc Chương trình tham khảo: uses math; CONST FI='’; FO=’’; MAXN=100000; vc=round(1e9); VAR A,B:ANSISTRING; ha,hb:array[0 maxn] of int64; Pow:array[0 maxn] of int64; n,m:longint; f:text; procedure doc; begin assign(f,fi); reset(f); readln(f,a); readln(f,b); close(f); end; procedure Build; VAR i:longint; Trang begin Ha[0]:=0; Hb[0]:=0; pow[0]:=1; n:=length(a); m:=length(b); for i:=1 to max(n,m) pow[i]:=(pow[i-1]*26) mod vc; for i:=1 to n Ha[i]:=(Ha[i-1]*26+ord(A[i])-97) mod vc; for i:=1 to m Hb[i]:=(Hb[i-1]*26+ord(B[i])-97) mod vc; end; function KT(len:longint):boolean; var v1,v2:int64; begin v1:=Hb[len]; v2:=(Ha[n]-Ha[n-len]*pow[len]+vc*vc)mod vc; if v1=v2 then exit(true); exit(false); end; procedure Process; var res,i:longint; kq:ansistring; begin assign(f,fo); rewrite(f); res:=0; for i:=1 to min(n,m) if KT(i) then res:=i; delete(b,1,res); writeln(f,a+b); close(f); end; Begin doc; Build; Process; End Trang 10 Bài GEN DNA thành phần cấu tạo thành genome sinh vật DNA bao gồm loại khác {A,X,T,G} Để nghiên cứu sinh vật mức độ phân tử, người ta tiến hành giải mã genome chúng Để giải mã genome sinh vật, máy giải mã hệ sinh N đoạn sở, đoạn sở dãy bao gồm 30 DNA Các đoạn sở ghép nối với để tạo thành genome hoàn chỉnh Ta nói đoạn DNA X bao phủ đoạn sở Y tồn đoạn Y trùng với X Giả sử k số nguyên dương, đoạn DNA X gọi đoạn tin tưởng cấp k X bao phủ kđoạn sở Yêu cầu: Cho N đoạn sở số nguyên dương k, tìm đoạn tin tưởng cấp k có độ dài lớn Input: Bai2.inp - Dòng đầu chứa hai số nguyên dương N k (0< k ≤ N ≤ 30.000) - N dòng tiếp theo, dòng chứa đoạn sở Output: Bai2.out - Là số nguyên xác định độ dài đoạn tin tưởng tìm (ghi -1 không tồn đoạn tin tưởng cấp k) Ví dụ: Bai2.inp Bai2.out 43 15 AAAAAAAAATAAAATAAAAAAAAAAAAATG AAAAAAAAAAAAAAAAAAAATAAATGAAAA AAAAAAAAAAAAAAAAAAATGAAAAAAAA A AAAAAAAAAAAAATGAAAAAAAGGGGAAA A Chương trình tham khảo Const fi=''; fo=''; Var f:Text; G:array['A' 'Z'] of Byte; N,K,VC:Int64; H:array[0 30000,0 30] of Int64; Trang 11 P:array[0 30] of Int64; D,C:array[0 1000000] of Longint; Procedure Build; Var i,j:Longint; X:String; Begin G['A']:=0; G['T']:=1; G['G']:=2; G['X']:=3; Assign(f,fi); Reset(f); Readln(f,N,K); P[0]:=1; VC:=1000000007; For i:=1 to N Begin Readln(f,X); For j:=1 to 30 H[i,j]:=(H[i,j-1]*4+G[X[j]]) mod VC; End; For i:=1 to 30 P[i]:=P[i-1]*4 mod VC; Close(f); End; Procedure QS(L,R:Longint); Var tg,x,y:Int64; i,j:Longint; Begin i:=L; j:=R; x:=C[(i+j) div 2]; y:=D[(i+j) div 2]; Repeat While (C[i]y)) Dec(j); Trang 12 If ij; If iL then QS(L,j); End; Function kt(x:Longint):Boolean; Var i,j,M,res:Longint; Y:Int64; Begin M:=0; res:=0; For i:=1 to N For j:=1 to 30 If j+x-1=K then Exit(True); Trang 13 Res:=1 End Else If D[i]D[i-1] then Inc(res); Exit(False); End; Procedure Process; Var l,r,tg:Longint; Begin Assign(f,fo); Rewrite(f); l:=1; r:=30; While lM then Break Else Begin X:=(H[i+C[j]-1]-H[i-1]*P[C[j]]+VC*VC) mod VC; If X=D[j] then L[i+C[j]-1]:=(L[i-1]+L[i+C[j]-1]) mod 1000000; End; Writeln(f,L[M]); Close(f); End; BEGIN Build; Process; END Bài Chuỗi xuất K lần Sau kỳ công chinh phục cấu trúc liệu đặc biệt, tình bạn piratevà duyhung123abc ngày trở nên khăng khít Rồi ngày nọ, duyhung123abc không lời từ biệt, để lại mẫu giấy cho pirate Mẩu giấy viết : "Em ơi, anh nặng nợ toán lý hóa anh, chưa thể lòng theo đuổi tin học Em làm nốt công việc mà anh em ta dang dở !" pirate đọc xong, nước mắt giàn giụa Nếu hai người gặp nhau, vui sướng Engels gặp Marx, giây phút chia ly này, lòng pirate đau đớn Đỗ Phủ tiễn người tri kỉ Lý Bạch lên đường Mất người anh cả, pirate thuyền phương hướng Cuối cùng, sau đêm không ngủ, anh định đợi duyhung123abc trả xong nợ công danh quay trở tiếp tục nghiên cứu cấu trúc liệu đặc biệt Còn bây giờ, đường mới, vào giới mới, giới Trang 18 THUẬT TOÁN VỀ CHUỖI Tuy cô độc mình, với niềm tin mình, pirate lên đường mà chút dự Nhưng trớ trêu thay, vạn khởi đầu nan Thử thách mà người trẻ tuổi gặp phải thật đau đầu Anh ta cho trước chuỗi S có độ dài N số K Thử thách hoàn thành anh đưa độ dài chuỗi dài xuất K lần chuỗi S Làm ! Vừa vực dậy sau cú sốc lớn, pirate cần giúp đỡ bạn để không nhiệt huyết mình! Một chuỗi A[1 m] gọi xuất chuỗi B[1 n] K lần tồn K vị trí i phân biệt cho A[1 m] = B[i i+m-1] Input: Bai5.inp - Dòng 1: Hai số nguyên N K (1 ≤ N ≤ 50000; ≤ K ≤ 200) - Dòng 2: Chuỗi S có độ dài N (gồm chữ in thương viết liên tiếp nhau) Output: Bai5.out Dữ liệu gồm dòng độ dài chuỗi dài xuất K lần chuỗi S Ví dụ: Bai5.inp Bai5.out 52 xxxxx Chương trình tham khảo: Uses Math; Const fi=''; fo=''; Var f:text; N,K:longint; X:Ansistring; T,Hash,P:array[0 1000000] of Int64; VC:Int64; Procedure Build; Var i:longint; Begin Assign(f,fi); Reset(f); Trang 19 Readln(f,N,K); Readln(f,X); VC:=1000000; For i:=1 to N Hash[i]:=(Hash[i-1]*26+ord(X[i])-97) mod VC; P[0]:=1; For i:=1 to N P[i]:=(P[i-1]*26) mod VC; Close(f); End; Function Get(i,j:longint):Int64; Begin Exit((Hash[j]-Hash[i-1]*P[j-i+1]+VC*VC) mod VC); End; Function kt(d:longint):Boolean; Var i,l:longint; x:Int64; Begin For i:=1 to N-d+1 Begin X:=Get(i,i+d-1); Inc(T[X]); If T[X]>=k then Exit(True); End; Exit(False); End; Procedure Access; Var d,c,tg:longint; Begin Assign(f,fo); Rewrite(f); d:=1; c:=N-K+1; While d[...]... đoạn tin tưởng cấp k nếu X được bao phủ bởi ít nhất kđoạn cơ sở Yêu cầu: Cho N đoạn cơ sở và số nguyên dương k, hãy tìm đoạn tin tưởng cấp k có độ dài lớn nhất Input: Bai2.inp - Dòng đầu chứa hai số nguyên dương N và k (0< k ≤ N ≤ 30.000) - N dòng tiếp theo, mỗi dòng chứa một đoạn cơ sở Output: Bai2.out - Là một số nguyên xác định độ dài của đoạn tin tưởng tìm được (ghi -1 nếu không tồn tại đoạn tin. .. j:=R; x:=C[(i+j) div 2]; y:=D[(i+j) div 2]; Repeat While (C[i]y)) do Dec(j); Trang 12 If ij; If iL then QS(L,j); End; Function kt(x:Longint):Boolean; Var i,j,M,res:Longint; Y:Int64; Begin M:=0;... Exit(1); End; Procedure QS(L,R:Longint); Var tg,x:Int64; i,j:Longint; Begin i:=L; j:=R; x:=C[(i+j) div 2]; Repeat While C[i]x do Dec(j); Trang 16 If ij; If iL then QS(L,j); End; Procedure Build; Var X,Y:String; i,j:Longint; Begin Assign(f,fi); Reset(f);... cách giải mã có nghĩa Yêu cầu: Bạn có trong tay một dãy đã mã hóa và một danh sách các từ có nghĩa, bạn hãy tính xem có bao nhiêu cách giải mã có nghĩa (Một cách giải có nghĩa là một cách chia đoạn code ban đầu thành các đoạn con liên tiếp sao cho mỗi đoạn là một từ có nghĩa) Input: Bai3.inp - Dòng thứ nhất ghi xâu đã mã hóa gồm không quá 10000 kí tự tích tè - Dòng thứ hai ghi số N là số các từ có... kt(tg) then l:=tg+1 Else r:=tg-1; Fillchar(C,sizeof(C),0); Fillchar(D,sizeof(D),0); End; If r=0 then r:=-1; Writeln(f,r); Close(f); End; BEGIN Build; Process; END Bài 3 MORSE Hiện nay, khi công nghệ thông tin phát triển, con người thường trao đổi với nhau bằng điện thoại, fax hay email Hãy quay ngược thời gian lại 100 năm, khi đó con người không có điện thoại hay fax, lại càng chẳng có email, người ta phải... khít Rồi bỗng một ngày nọ, duyhung123abc bỗng ra đi không một lời từ biệt, chỉ để lại một mẫu giấy cho pirate Mẩu giấy viết rằng : "Em ơi, anh còn nặng nợ toán lý hóa anh, chưa thể một lòng theo đuổi tin học Em hãy làm nốt công việc mà anh em ta còn dang dở !" pirate đọc xong, nước mắt giàn giụa Nếu khi hai người gặp nhau, vui sướng như khi Engels gặp Marx, thì trong giây phút chia ly này, lòng pirate... tiếp tục nghiên cứu các cấu trúc dữ liệu đặc biệt Còn bây giờ, anh ta sẽ đi một con đường mới, đi vào một thế giới mới, thế giới của Trang 18 các THUẬT TOÁN VỀ CHUỖI Tuy cô độc một mình, nhưng với niềm tin của mình, pirate đã lên đường ngay mà không có chút do dự Nhưng trớ trêu thay, vạn sự khởi đầu nan Thử thách đầu tiên mà con người trẻ tuổi này gặp phải thật đau đầu Anh ta được cho trước một chuỗi